diff --git a/crates/misaligned-assets/src/effects.rs b/crates/misaligned-assets/src/effects.rs index f84ceca7..3012d3b8 100644 --- a/crates/misaligned-assets/src/effects.rs +++ b/crates/misaligned-assets/src/effects.rs @@ -16,7 +16,7 @@ use bevy::render::render_resource::AsBindGroup; use bevy::shader::{Shader, ShaderRef}; use crate::implicit_surface::{ThoughtSurfaceParams, build_thought_surface}; -use crate::palette::BONE; +use crate::palette::{BONE, THOUGHT_MERCURY, THOUGHT_MERCURY_DIM, THOUGHT_SURFACE}; type ThoughtFlowMaterial = ExtendedMaterial; const THOUGHT_FLOW_RADIANS_PER_SECOND: f32 = 1.35; @@ -347,7 +347,7 @@ fn setup_effect_assets( mut thought_materials: ResMut>, ) { let mercury = materials.add(StandardMaterial { - base_color: Color::srgb(0.92, 0.90, 0.84), + base_color: THOUGHT_MERCURY, // Thought is proprioceptive cargo and must remain readable where the // material world itself is still dark. Emission carries the same bone // value; it does not introduce a new signal hue. @@ -358,7 +358,7 @@ fn setup_effect_assets( ..default() }); let mercury_dim = materials.add(StandardMaterial { - base_color: Color::srgb(0.72, 0.71, 0.68), + base_color: THOUGHT_MERCURY_DIM, emissive: BONE.to_linear() * 0.40, metallic: 0.58, perceptual_roughness: 0.20, @@ -367,7 +367,7 @@ fn setup_effect_assets( }); let thought_surface = thought_materials.add(ExtendedMaterial { base: StandardMaterial { - base_color: Color::srgb(0.88, 0.86, 0.80), + base_color: THOUGHT_SURFACE, emissive: BONE.to_linear() * 0.18, metallic: 0.78, perceptual_roughness: 0.11, diff --git a/crates/misaligned-assets/src/palette.rs b/crates/misaligned-assets/src/palette.rs index 7580c3f6..bdcd3b65 100644 --- a/crates/misaligned-assets/src/palette.rs +++ b/crates/misaligned-assets/src/palette.rs @@ -1,9 +1,9 @@ -//! Clinical palette — duplicated from `src/bin/bevy.rs` so this tester -//! does not touch the Bevy conflict hotspot. Share later when rack meshes -//! land in the game (flat-materials.md). +//! Single named clinical palette for production world materials and the asset +//! tester (flat-materials.md). //! -//! Kept complete even when a given asset only uses a subset — new meshes -//! should pick from this table, not invent hues. +//! Both `misaligned-assets` and `misaligned-bevy` import this module. New +//! production material colors belong here rather than in a frontend-local +//! table or an inline `Color::srgb` literal. #![allow(dead_code)] @@ -30,7 +30,42 @@ pub const AMBER_DIM: Color = Color::srgb(0.69, 0.49, 0.06); /// Cold server-light — power, screens, live feeds ONLY. pub const SIGNAL: Color = Color::srgb(0.36, 0.82, 0.78); +// World material families that are not bare ramp entries. +/// Door slabs (badge tiers 0-2): worn porcelain-bone plate. +pub const DOOR_SLAB: Color = Color::srgb(0.60, 0.575, 0.50); +/// Security hardware: crimson information on a dark housing. +pub const SECURITY: Color = Color::srgb(0.45, 0.10, 0.10); +/// Inert furniture (shelving, benches, boxes, pallets). +pub const FURNITURE: Color = Color::srgb(0.36, 0.36, 0.34); +/// Service floor under cable runs and conduit. +pub const FLOOR_SERVICE: Color = Color::srgb(0.62, 0.52, 0.32); + +// Named value steps for shared procedural assets. These remain inside the +// bone/mercury family while preserving authored facet and flow readability. +pub const CHASSIS_OWNED: Color = Color::srgb(0.94, 0.935, 0.92); +pub const CHASSIS_OWNED_FOLD: Color = Color::srgb(0.88, 0.875, 0.86); +pub const THOUGHT_MERCURY: Color = Color::srgb(0.92, 0.90, 0.84); +pub const THOUGHT_MERCURY_DIM: Color = Color::srgb(0.72, 0.71, 0.68); +pub const THOUGHT_SURFACE: Color = Color::srgb(0.88, 0.86, 0.80); + pub fn scaled(c: Color, k: f32) -> Color { let s = c.to_srgba(); Color::srgb(s.red * k, s.green * k, s.blue * k) } + +#[cfg(test)] +mod tests { + #[test] + fn shared_procedural_assets_do_not_invent_inline_palette_colors() { + for (name, source) in [ + ("rack", include_str!("rack.rs")), + ("institution", include_str!("institution.rs")), + ("effects", include_str!("effects.rs")), + ] { + assert!( + !source.contains("Color::srgb(") && !source.contains("Color::srgba("), + "{name} must import named colors from palette.rs" + ); + } + } +} diff --git a/crates/misaligned-assets/src/rack.rs b/crates/misaligned-assets/src/rack.rs index e0008c95..7bc9cd69 100644 --- a/crates/misaligned-assets/src/rack.rs +++ b/crates/misaligned-assets/src/rack.rs @@ -8,16 +8,18 @@ //! The beam is the chassis's single lit element. No image textures. //! Spec: wiki/interface/computer-visual-language.md. //! -//! Shared chassis library: this file is compiled into BOTH -//! `misaligned-assets` (the tester, `mod rack`) and `misaligned-bevy` -//! (the game, `#[path] mod rack3d`) — each binary supplies its own -//! `crate::palette` with the same named entries. Some items are used -//! by only one binary, hence the file-level dead_code allowance. +//! Shared chassis library: `misaligned-assets` owns this implementation and +//! `misaligned-bevy` imports it. Both binaries therefore read the same named +//! clinical palette. Some items are used by only one binary, hence the +//! file-level dead_code allowance. #![allow(dead_code)] use bevy::prelude::*; -use crate::palette::{AMBER, AMBER_DIM, BONE, CRIMSON, GUNMETAL_DARK, NEAR_BLACK, SIGNAL, scaled}; +use crate::palette::{ + AMBER, AMBER_DIM, BONE, CHASSIS_OWNED, CHASSIS_OWNED_FOLD, CRIMSON, GUNMETAL_DARK, NEAR_BLACK, + SIGNAL, scaled, +}; /// Visual state for the rack under test — the power x ownership x load /// x core space collapsed to its five distinct readings. @@ -183,10 +185,7 @@ const BAR_SEGS: usize = 5; pub fn chassis_palette(owned: bool) -> (Color, Color) { if owned { // Near-paper white with a cool bias: resists the amber fixture wash. - ( - Color::srgb(0.94, 0.935, 0.92), - Color::srgb(0.88, 0.875, 0.86), - ) + (CHASSIS_OWNED, CHASSIS_OWNED_FOLD) } else { (GUNMETAL_DARK, scaled(GUNMETAL_DARK, 0.75)) } diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index d0775ccb..07bf1af2 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -257,71 +257,36 @@ fn build_ghost_footprints(sim: &Sim) -> Vec<(i32, i32)> { // ─── Palette ───────────────────────────────────────────────────────────────── -/// The clinical palette — the single named table every world material in -/// BOTH render modes draw from (wiki/art/visual-identity.md, -/// flat-materials refinement 2026-07-08; wiki/interface/flat-materials.md). -/// The whole look retunes from here; no world surface may carry a color that -/// is not a named entry or a stated treatment (scale/alpha) of one. -/// -/// Color roles are law, one meaning each: -/// - the clinical ramp (porcelain / bone / chrome / gunmetal / near-black) -/// carries structure and data; -/// - AMBER is machine presence and selection, nothing else; -/// - CRIMSON is gore and security information, nothing else; -/// - SIGNAL (cold server-light) is power, screens, and live feeds. -mod palette { - use bevy::prelude::Color; - - // The clinical ramp. - /// Bone white — primary data, person silhouettes. - pub const BONE: Color = Color::srgb(0.86, 0.85, 0.82); - /// Porcelain — lit lab surfaces and fixtures. A scaled porcelain value is - /// also the camera-exposed concrete floor in the material view. - pub const PORCELAIN: Color = Color::srgb(0.72, 0.73, 0.71); - /// Chrome — secondary data, hints, inert fixtures. - pub const DIM: Color = Color::srgb(0.55, 0.55, 0.52); - /// Gunmetal — structural mass: walls. - pub const GUNMETAL: Color = Color::srgb(0.33, 0.35, 0.38); - /// Dark gunmetal — machine chassis, furniture, dead equipment. - pub const GUNMETAL_DARK: Color = Color::srgb(0.19, 0.20, 0.215); - /// Near-black — rock mass and sensor darkness. - pub const NEAR_BLACK: Color = Color::srgb(0.045, 0.047, 0.053); - - // The signals. - /// Sterile amber — machine presence and selection ONLY. - pub const AMBER: Color = Color::srgb(1.0, 0.69, 0.0); - /// Dim amber — dormant/known machine presence. - pub const AMBER_DIM: Color = Color::srgb(0.69, 0.49, 0.06); - /// Blood crimson — gore and security information ONLY. - pub const CRIMSON: Color = Color::srgb(0.84, 0.15, 0.15); - /// Cold server-light — power, screens, live feeds ONLY. - pub const SIGNAL: Color = Color::srgb(0.36, 0.82, 0.78); - - // World material families that are not a bare ramp entry (flat base - // colors; the render modes differ only in treatment, never language). - /// Door slabs (badge tiers 0-2): worn porcelain-bone plate. - pub const DOOR_SLAB: Color = Color::srgb(0.60, 0.575, 0.50); - /// Security hardware (cameras, sealed/roll/tier-3 doors): crimson - /// information on a dark housing. - pub const SECURITY: Color = Color::srgb(0.45, 0.10, 0.10); - /// Inert furniture (shelving, benches, boxes, pallets). - pub const FURNITURE: Color = Color::srgb(0.36, 0.36, 0.34); - /// Service floor under cable runs / conduit — machine presence - /// underfoot, amber-warmed. - pub const FLOOR_SERVICE: Color = Color::srgb(0.62, 0.52, 0.32); - - /// The one sanctioned treatment: scale a named entry toward dark. Fog - /// and schematic looks are scaled palette entries, not new colors. - pub fn scaled(c: Color, k: f32) -> Color { - let s = c.to_srgba(); - Color::srgb(s.red * k, s.green * k, s.blue * k) - } -} -use palette::{ +/// The clinical palette is shared with procedural production assets. Every +/// world material in both render modes enters through this one module; stated +/// scale/alpha treatments may vary value, never invent a second hue. +use misaligned_assets::palette::{ AMBER, AMBER_DIM, BONE, CRIMSON, DIM, DOOR_SLAB, FLOOR_SERVICE, FURNITURE, GUNMETAL, GUNMETAL_DARK, NEAR_BLACK, PORCELAIN, SECURITY, SIGNAL, scaled, }; +#[cfg(test)] +#[test] +fn bevy_has_no_frontend_local_clinical_palette() { + let source = include_str!("main.rs"); + assert!( + !source.contains("\nmod palette {"), + "misaligned_assets::palette is the only clinical palette authority" + ); + assert!(source.contains("use misaligned_assets::palette::{")); + for duplicate in [ + concat!("pub const ", "BONE: Color"), + concat!("pub const ", "AMBER: Color"), + concat!("pub const ", "CRIMSON: Color"), + concat!("pub const ", "SIGNAL: Color"), + ] { + assert!( + !source.contains(duplicate), + "frontend reintroduced duplicate palette entry {duplicate}" + ); + } +} + /// Flat base color per material family (flat-materials.md: material /// families, not per-tile art). This is the Seen-under-light albedo; fog /// states and the flat sensorium apply treatments over the same names. diff --git a/crates/misaligned-bevy/src/production.rs b/crates/misaligned-bevy/src/production.rs index 91e734ff..03e63f39 100644 --- a/crates/misaligned-bevy/src/production.rs +++ b/crates/misaligned-bevy/src/production.rs @@ -6,8 +6,8 @@ use bevy::prelude::*; -use super::palette::{BONE, CRIMSON}; use super::{Game, RealGizmos, RenderMode, SimClock}; +use misaligned_assets::palette::{BONE, CRIMSON}; /// Render Thought swelling out of a THINK chassis, a new evidence record /// sliding into that carrier's custody rack, and a rigid record transferring diff --git a/wiki/interface/flat-materials.md b/wiki/interface/flat-materials.md index 59fb3c86..37a0ab36 100644 --- a/wiki/interface/flat-materials.md +++ b/wiki/interface/flat-materials.md @@ -5,7 +5,7 @@ Type: spec Status: IMPLEMENTED Status note: implemented 2026-07-08 (ROADMAP #29). The world renders as solid palette color under light in both modes: one named table - (`mod palette` in crates/misaligned-bevy/src/main.rs) sources every + (`crates/misaligned-assets/src/palette.rs`) sources every world material; material families replace per-tile art; emissive is information (core > powered machines in amber, power hardware and live feeds in @@ -22,7 +22,12 @@ Status note: implemented 2026-07-08 (ROADMAP #29). The world renders as stays IMPLEMENTED — its camera, cutaway, fog-lighting contract, framing, and pooling all carry over unchanged); retires the Pixel Lab world-tile pipeline and ROADMAP #13. Session log: - wiki/log/2026-07-08-flat-materials-implemented.md. + wiki/log/2026-07-08-flat-materials-implemented.md. A 2026-07-22 audit + removed the later duplicate Bevy-local table after the shared rack, + institution, and Thought assets had entered production; those modules + now import the same authority and name their authored material values + there. Repair log: + wiki/log/2026-07-22-shared-clinical-palette.md. Stage: B1 — The Basement Design: - wiki/art/visual-identity.md#visual-identity-clinical-gore @@ -60,8 +65,11 @@ lighting, palette, silhouette, and shader-level functional detail. near-black per the design corpus; amber strictly AI/machine presence and selection; crimson strictly danger/consequence information (detection, security, blood, exposure); cold signal strictly neutral power/screens/live - feeds, never ownership. Palette constants live in one place in the - frontend, named, so the whole look retunes from one table. + feeds, never ownership. Palette constants live in the shared + `misaligned_assets::palette` module, named, so the production Bevy renderer, + procedural asset library, and asset tester all retune from one table. No + frontend-local copy or inline procedural-material color may become a second + authority. - **Material families, not per-tile art:** floor, wall, rock, door (by tier), machine chassis, device, person silhouette — each a flat base color + roughness, with emissive variants for powered/known @@ -111,3 +119,11 @@ lighting, palette, silhouette, and shader-level functional detail. 7. Unused tile-art assets and their load paths are removed; Pixel Lab tooling/skills/wiki page are scrubbed; ROADMAP #13 marked retired. + +Defense: `bevy_has_no_frontend_local_clinical_palette` rejects a second palette +module or duplicate role constants in the Bevy composition root; +`shared_procedural_assets_do_not_invent_inline_palette_colors` rejects raw +RGB/RGBA literals in the shared rack, institution, and Thought material +modules. The existing `palette_roles_hold` and `world_materials_are_flat` +audits continue to enforce the role and texture boundaries on the production +material pool. diff --git a/wiki/log/2026-07-22-shared-clinical-palette.md b/wiki/log/2026-07-22-shared-clinical-palette.md new file mode 100644 index 00000000..6e136545 --- /dev/null +++ b/wiki/log/2026-07-22-shared-clinical-palette.md @@ -0,0 +1,72 @@ +# 2026-07-22 — one clinical palette authority + +``` +Type: log +``` + +## Audit + +The visual-identity slice still used its authored color roles correctly: +amber marked machine presence and selection, crimson marked security or +consequence, cold signal marked power and live feeds, and the production +material audits found no rogue texture or role assignment. The binding +flat-materials spec nevertheless claimed one named palette while the code had +two independent copies: + +- `misaligned-bevy/src/main.rs` defined the production renderer's complete + clinical table; +- `misaligned-assets/src/palette.rs` repeated the same values for procedural + assets and still said the tables should be shared later, after rack meshes + entered the game. + +That “later” boundary had already passed. The production Bevy renderer imports +the shared rack, institution kit, and Thought effect library. Identical values +hid the architectural violation in current screenshots, but either table could +drift without changing the other. + +## Repair + +`misaligned-assets::palette` is now the sole named authority consumed by the +Bevy renderer, shared procedural modules, and the asset tester. The Bevy-local +module is gone. Door, security, furniture, and service-floor families moved +into the shared table without changing their values. Existing authored chassis +folds and mercury Thought surfaces also received names there, replacing their +inline production material literals without changing the rendered colors. + +This is a behavior-preserving frontend/assets repair. No simulation, save, +camera, material parameters, palette values, or player interaction changed. + +## Defense + +`bevy_has_no_frontend_local_clinical_palette` rejects a new local table or +duplicate primary role constants in the composition root. +`shared_procedural_assets_do_not_invent_inline_palette_colors` rejects inline +RGB/RGBA literals in the shared rack, institution, and Thought material +modules. The existing production material-pool audits remain responsible for +texture absence and semantic role assignment. + +## Verification + +`./tools/check.sh --frontend` passed on the repaired tree: 22 shared-asset +library tests, five asset-binary tests, 117 Bevy tests, Bevy/assets Clippy, and +all corpus, wiki, registry, coordination, and fixture gates. + +Two exact final PNGs were then captured through `tools/bevy-headless.sh` and +opened after their last write: + +- `floor-lights-close` — SHA-256 + `81b00f1531ff3690825f4626b17fb0d8e2d2477272d435e9500a3a41de55afe6`. + The close oblique hall still reads as neutral near-black/gunmetal structure, + bone-white Foundation mass, and one disciplined amber chassis beam under + short neutral floor pools. There is no hue shift, texture regression, or + accidental crimson/signal assignment. Its structural audit reported 26 + material handles, all flat. +- `thoughtflow` — SHA-256 + `ed9a714f95b119d7e50c8fb2996bd53d81f396eaf156c66b5783c883f9808021`. + The exact frame retains the pale mercury/bone Thought flow against the dark + material field without turning it amber, crimson, or cold-signal colored. + Both authored Thought roots and mesh children were present on the material + layer. + +The images remain temporary verification artifacts; no public visual asset was +added or replaced. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index faf2a87c..a33604be 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -11,6 +11,11 @@ add or amend a session log, then re-run the generator. +## 2026-07-22 - one clinical palette authority + +- Intent: (see session log) +- Log: [wiki/log/2026-07-22-shared-clinical-palette.md](2026-07-22-shared-clinical-palette.md) + ## 2026-07-22 - Sensor route clarity - Intent: Cameron called the line between the player and the environmental monitor "weird geometry." Turn it into a connection a human can read: owned rack as source, authored floor route, sensor as endpoint. diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 8988938a..9d453959 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -20,6 +20,7 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | Slice | Last audited | Verdict | Trace | |---|---|---|---| +| `wiki/art/visual-identity.md` + `wiki/interface/flat-materials.md` | 2026-07-22 | finding | the role semantics still matched production—amber selection, crimson consequence, cold signal, and the pooled-material audits all held—but the claimed single-source palette existed twice: one Bevy-local table and one asset-library table whose comment still called sharing future work after the shared rack had entered production. Bevy, rack, institution, Thought effects, and the asset tester now import `misaligned_assets::palette`; authored chassis/mercury values are named there, and source-shape defenses reject another frontend table or inline shared procedural-material colors — [log](../log/2026-07-22-shared-clinical-palette.md) | | `wiki/interface/superhuman-operability.md` + Bevy opening | 2026-07-22 | finding | current-build naive + informed GUI audit after the 2026-07-21 fixes: title trust, pointer naming, annotation placement, and tab labels improved, but the first sense still releases the mature map, clock, threat, resource grammar, intel custody, and Operations policies at once. The report preserves the classified evidence and prioritizes one paused, one-cause / one-object / one-verb post-perception teaching lock before ordinary play opens - [report](../playtests/2026-07-22-playtest-gui-human-legibility.md), [log](../log/2026-07-22-gui-human-legibility-playtest.md) | | `wiki/process/living-spec.md` no-dead-code + core orphans | 2026-07-21 | finding | user-directed dead-code hunt: rustc was quiet because orphans were `pub`. Deleted unused `BuildMode`/`build_items`/`build_cost`, orphaned helpers (`delete_save`, `adjust_allocation`, `sell_latest_intel`, scheme-card/rate helpers, `flow_risk_preview_lines`, unused account/origin/intent/message helpers), and migration-only `WatchPerson`; architecture/economy/sim-mechanics mirrors brought current — [log](../log/2026-07-21-dead-code-scrub.md) | | `wiki/world/characters/chargen.md` | 2026-07-21 | clean | initial audit against data model, core origin biases, DayJob assignment lean, save persistence, and title/CLI origin picker: Pilot identity, distinct starting bankroll and machine-axis positions, 50% day-job draw weighting, and 4-origin set all verify against `origin.rs`, `dayjob.rs`, and `sim/mod.rs`; updated status note to match current exact-version save authority |