From fb2906756d071c0b2c82391f11366bed5b9bda3d Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Wed, 29 Jul 2026 08:25:34 -0700 Subject: [PATCH] Make REAL material pooling Seen-only. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the unreachable fog variants behind the existing visibility boundary so the material constructor and cache can encode only physical Seen matter. Defense: material-render criterion 6. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- crates/misaligned-bevy/src/main.rs | 12 - crates/misaligned-bevy/src/material_view.rs | 263 +++++++----------- wiki/interface/material-render.md | 29 +- .../log/2026-07-29-seen-only-material-pool.md | 30 ++ wiki/log/DEVLOG.md | 5 + 5 files changed, 150 insertions(+), 189 deletions(-) create mode 100644 wiki/log/2026-07-29-seen-only-material-pool.md diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index b6ae079a..5245666e 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -425,18 +425,6 @@ fn flat_remembered_color(tile: TileType) -> Color { Color::srgba(c.red * 0.58, c.green * 0.58, c.blue * 0.60, 0.86) } -/// Remembered (model-state) treatment for 3D blocks and props: the family -/// language survives, desaturated toward the unlit snapshot. -fn remembered_tint(tile: TileType) -> Color { - if machine_tile(tile) { - scaled(AMBER_DIM, 0.80) - } else if danger_tile(tile) { - scaled(CRIMSON, 0.60) - } else { - scaled(DIM, 0.78) - } -} - fn blueprint_color(tile: TileType) -> Color { // Blueprint is room topology only (machine-work.md / cursor.md): walls, // doors, floors. Machine chassis are absent until intel or sight — never diff --git a/crates/misaligned-bevy/src/material_view.rs b/crates/misaligned-bevy/src/material_view.rs index 4f1f9fe4..7b64593d 100644 --- a/crates/misaligned-bevy/src/material_view.rs +++ b/crates/misaligned-bevy/src/material_view.rs @@ -348,15 +348,17 @@ pub(super) struct FloorFixture { #[derive(Default, Reflect, GizmoConfigGroup)] pub(super) struct RealGizmos; -/// Shared handles for the 3D scene's meshes plus a material cache keyed by -/// (tile, fog, part, live): tiles swap between a small set of pooled -/// materials instead of mutating thousands of unique ones every tick. The -/// `live` bit is the emissive-state variant (flat-materials.md criterion 3): -/// a device with a live feed to the player pools separately from the same +/// Shared handles for the 3D scene's Seen materials, keyed by +/// (tile, part, live): tiles swap between a small set of pooled materials +/// instead of mutating thousands of unique ones every tick. Fog is +/// deliberately absent from the key because the visibility boundary rejects +/// Unknown, Blueprint, and Remembered geometry before this pool can be read. +/// The `live` bit is the emissive-state variant (flat-materials.md criterion +/// 3): a device with a live feed to the player pools separately from the same /// device dark. #[derive(Resource, Default)] pub(super) struct Materials3d { - pub(super) cache: HashMap<(TileType, u8, u8, bool), Handle>, + pub(super) cache: HashMap<(TileType, u8, bool), Handle>, } /// Pooled block meshes for the material render: full-height and cutaway @@ -372,15 +374,6 @@ pub(super) struct Meshes3d { door_cut: Handle, } -fn fog_key(fog: Fog) -> u8 { - match fog { - Fog::Seen => 0, - Fog::Remembered => 1, - Fog::Blueprint => 2, - Fog::Unknown => 3, - } -} - fn part_key(part: TilePart) -> u8 { match part { TilePart::Floor => 0, @@ -1348,14 +1341,13 @@ fn prop_emissive(tile: TileType, live: bool) -> LinearRgba { } } -/// Build the material for one (tile, fog, part, live) combination: a solid -/// palette color under light — no image textures anywhere in the world -/// render (flat-materials.md criterion 1). The epistemic rule is carried by -/// lighting participation: only Seen surfaces are lit (they are camera -/// truth); remembered/blueprint surfaces are unlit model state, so -/// the fog contract stays legible in 3D. Emissive exists only on Seen -/// surfaces — model state never glows. -fn material_3d(tile: TileType, fog: Fog, part: TilePart, live: bool) -> StandardMaterial { +/// Build one Seen material: a solid palette color under light with no image +/// textures anywhere in the world render (flat-materials.md criterion 1). +/// This constructor cannot represent model-state material. The production +/// visibility boundary admits only local Seen before the pool calls it, so +/// Unknown, Blueprint, and Remembered remain geometry-free rather than +/// acquiring dormant material variants. +fn seen_material_3d(tile: TileType, part: TilePart, live: bool) -> StandardMaterial { let mut m = StandardMaterial { perceptual_roughness: 0.94, metallic: 0.0, @@ -1363,73 +1355,35 @@ fn material_3d(tile: TileType, fog: Fog, part: TilePart, live: bool) -> Standard ..default() }; match part { - TilePart::Floor => match fog { - Fog::Seen => { - // Eyes supplies an exposed image, so ordinary concrete must - // become visibly lit rather than preserving the sensorium's - // near-black ground. Keep enough value separation for the - // bone-white chassis to remain the brightest matter. Cable - // runs / conduit retain their amber-warm service read. - m.base_color = match tile { - TileType::CableRun | TileType::Conduit => family_color(tile), - // clinical-frame.md: the Seen floor is a medium-light - // matte porcelain field — broad and calm, still below - // chassis white (capture-validated). - _ => scaled(PORCELAIN, 0.74), - }; - } - Fog::Remembered => { - m.unlit = true; - m.base_color = scaled(family_color(tile), 0.45); - } - _ => { - // Blueprint: schematic plan surface. Lifted slightly from the - // 2D palette so the learned floor plan reads at 3D distance. - m.unlit = true; - let c = blueprint_color(tile).to_srgba(); - m.base_color = Color::srgb(c.red + 0.05, c.green + 0.055, c.blue + 0.07); - } - }, - TilePart::Block => match fog { - Fog::Seen => { - // clinical-frame.md: Seen walls are bone/ivory institutional - // faces (gunmetal stays on functional parts); the flat - // sensorium keeps family_color's schematic gunmetal. - m.base_color = match tile { - TileType::Wall => scaled(BONE, 0.84), - _ => family_color(tile), - }; - } - Fog::Remembered => { - m.unlit = true; - m.base_color = remembered_tint(tile); - } - _ => { - // Blueprint mass: schematic ghost volume. - m.unlit = true; - m.base_color = if danger_tile(tile) { - scaled(CRIMSON, 0.26) - } else { - scaled(GUNMETAL, 0.50) - }; - } - }, - TilePart::Base => match fog { - Fog::Seen => { - // Clinical-frame.md: a structural seam at the wall base - // establishes the floor/wall junction without consuming a - // signal color or implying unseen interior detail. - m.base_color = scaled(NEAR_BLACK, 1.35); - } - Fog::Remembered => { - m.unlit = true; - m.base_color = scaled(NEAR_BLACK, 1.1); - } - _ => { - m.unlit = true; - m.base_color = scaled(NEAR_BLACK, 1.05); - } - }, + TilePart::Floor => { + // Eyes supplies an exposed image, so ordinary concrete must + // become visibly lit rather than preserving the sensorium's + // near-black ground. Keep enough value separation for the + // bone-white chassis to remain the brightest matter. Cable runs / + // conduit retain their amber-warm service read. + m.base_color = match tile { + TileType::CableRun | TileType::Conduit => family_color(tile), + // clinical-frame.md: the Seen floor is a medium-light matte + // porcelain field — broad and calm, still below chassis white + // (capture-validated). + _ => scaled(PORCELAIN, 0.74), + }; + } + TilePart::Block => { + // clinical-frame.md: Seen walls are bone/ivory institutional + // faces (gunmetal stays on functional parts); the flat sensorium + // keeps family_color's schematic gunmetal. + m.base_color = match tile { + TileType::Wall => scaled(BONE, 0.84), + _ => family_color(tile), + }; + } + TilePart::Base => { + // Clinical-frame.md: a structural seam at the wall base + // establishes the floor/wall junction without consuming a signal + // color or implying unseen interior detail. + m.base_color = scaled(NEAR_BLACK, 1.35); + } TilePart::Top => { // Dedicated cap treatment (material-render.md debt 2): the block // family darkened per structural class, so tops read as poured @@ -1445,59 +1399,23 @@ fn material_3d(tile: TileType, fog: Fog, part: TilePart, live: bool) -> Standard _ => scaled(DOOR_SLAB, 0.56), // door slabs: worn plate } }; - match fog { - Fog::Seen => { - m.base_color = cap; - } - Fog::Remembered => { - m.unlit = true; - m.base_color = scaled(cap, 0.55); - } - _ => { - // Blueprint: schematic cap over the ghost mass. - m.unlit = true; - m.base_color = if danger_tile(tile) { - scaled(CRIMSON, 0.21) - } else { - scaled(GUNMETAL, 0.40) - }; - } - } + m.base_color = cap; } TilePart::Prop => { m.double_sided = true; m.cull_mode = None; - match fog { - Fog::Seen => { - // Flat chassis color; identity comes from the panel, and - // machine presence from emissive plus the point lights, - // never from decorative detail. - m.base_color = family_color(tile); - m.emissive = prop_emissive(tile, live); - } - Fog::Remembered => { - m.unlit = true; - m.base_color = remembered_tint(tile); - } - _ => { - m.unlit = true; - m.base_color = if machine_tile(tile) { - scaled(AMBER_DIM, 0.43) - } else if danger_tile(tile) { - scaled(CRIMSON, 0.28) - } else { - scaled(GUNMETAL, 0.42) - }; - } - } + // Flat chassis color; identity comes from the panel, and machine + // presence from emissive plus the point lights, never from + // decorative detail. + m.base_color = family_color(tile); + m.emissive = prop_emissive(tile, live); } } m } -fn pooled_material( +fn pooled_seen_material( tile: TileType, - fog: Fog, part: TilePart, live: bool, mats: &mut Assets, @@ -1505,8 +1423,8 @@ fn pooled_material( ) -> Handle { cache .cache - .entry((tile, fog_key(fog), part_key(part), live)) - .or_insert_with(|| mats.add(material_3d(tile, fog, part, live))) + .entry((tile, part_key(part), live)) + .or_insert_with(|| mats.add(seen_material_3d(tile, part, live))) .clone() } @@ -2282,14 +2200,13 @@ pub(super) fn restyle_3d( // Live-feed emissive state (flat-materials.md criterion 3): a Seen // device actively feeding the player scans in the cold signal color. // Telemetry without local Seen remains non-geometric in REAL. - let mat_fog = fog; + debug_assert!(matches!(fog, Fog::Seen)); let live = t.part == TilePart::Prop - && matches!(mat_fog, Fog::Seen) && game.sim.reach.known_at(t.x, t.y).is_some_and(|d| { game.sim.reach.feed_to(d.id, Party::Player, true) || game.sim.reach.feed_to(d.id, Party::Player, false) }); - let handle = pooled_material(tile, mat_fog, t.part, live, &mut mats, &mut cache); + let handle = pooled_seen_material(tile, t.part, live, &mut mats, &mut cache); if mat.0 != handle { mat.0 = handle; } @@ -2332,11 +2249,8 @@ pub(super) fn flicker_feeds( let t = time.elapsed_secs(); // Two incommensurate sines: an irregular instrument pulse, not a beacon. let w = 0.9 + 0.4 * (t * 9.0).sin() * (t * 2.3).sin(); - for ((_, fog, _, live), handle) in cache.cache.iter() { - if *live - && *fog == 0 - && let Some(m) = mats.get_mut(handle) - { + for ((_, _, live), handle) in cache.cache.iter() { + if *live && let Some(m) = mats.get_mut(handle) { m.emissive = SIGNAL.to_linear() * w; } } @@ -3325,7 +3239,6 @@ mod flat_materials { TileType::CableRun, TileType::Sump, ]; - const ALL_FOG: [Fog; 4] = [Fog::Seen, Fog::Remembered, Fog::Blueprint, Fog::Unknown]; const ALL_PARTS: [TilePart; 5] = [ TilePart::Floor, TilePart::Block, @@ -3374,22 +3287,45 @@ mod flat_materials { } /// Criterion 1: no world material references an image texture — the - /// whole (tile, fog, part, live) material space is solid color. + /// complete Seen-only (tile, part, live) pool space is solid color. #[test] fn world_materials_are_flat() { for tile in ALL_TILES { - for fog in ALL_FOG { - for part in ALL_PARTS { - for live in [false, true] { - let m = material_3d(tile, fog, part, live); - assert!( - m.base_color_texture.is_none() && m.emissive_texture.is_none(), - "{tile:?}/{fog:?} material references an image texture" - ); - } + for part in ALL_PARTS { + for live in [false, true] { + let m = seen_material_3d(tile, part, live); + assert!( + m.base_color_texture.is_none() && m.emissive_texture.is_none(), + "{tile:?}/{part:?}/live={live} material references an image texture" + ); + } + } + } + } + + /// material-render.md criterion 6: the material pool cannot encode a fog + /// variant. Fog eligibility has already resolved to Seen when this API is + /// reached, so repeated reads share one (tile, part, live) handle. + #[test] + fn material_pool_is_seen_only_and_stable() { + let mut mats = Assets::::default(); + let mut cache = Materials3d::default(); + for tile in ALL_TILES { + for part in ALL_PARTS { + for live in [false, true] { + let first = pooled_seen_material(tile, part, live, &mut mats, &mut cache); + let second = pooled_seen_material(tile, part, live, &mut mats, &mut cache); + + assert_eq!(first, second, "{tile:?}/{part:?}/live={live}"); + assert!(cache.cache.contains_key(&(tile, part_key(part), live))); + assert!( + !mats.get(&first).expect("pooled Seen material").unlit, + "{tile:?}/{part:?}/live={live} created an unlit model material" + ); } } } + assert_eq!(cache.cache.len(), ALL_TILES.len() * ALL_PARTS.len() * 2); } /// material-render.md criterion 4: REAL contains no plan geometry. @@ -3438,12 +3374,12 @@ mod flat_materials { ); } - /// Criterion 3: emissive is information. Powered vs dead machines, live - /// feeds, and the core read differently; model state never glows. + /// Criterion 3: emissive is information among Seen forms. Powered vs dead + /// machines, live feeds, and the core read differently; the visibility + /// boundary above prevents model state from acquiring any form to glow. #[test] fn emissive_is_information() { - let glow = - |t: TileType, live: bool| material_3d(t, Fog::Seen, TilePart::Prop, live).emissive; + let glow = |t: TileType, live: bool| seen_material_3d(t, TilePart::Prop, live).emissive; // The core outshines an ordinary rack; both carry amber (red-heavy). let (core, rack) = (glow(TileType::Core, false), glow(TileType::Rack, false)); assert!(core.red > rack.red && rack.red > rack.blue && rack.red > 0.0); @@ -3459,16 +3395,11 @@ mod flat_materials { assert!(cam_live.blue > cam_live.red && cam_live.blue > ups.blue); // Dead machines are dark: that is what dead looks like. assert_eq!(glow(TileType::DeadEquipment, false), LinearRgba::BLACK); - // Model state (remembered/blueprint) never glows and never lights. - for fog in [Fog::Remembered, Fog::Blueprint] { - let m = material_3d(TileType::Core, fog, TilePart::Prop, false); - assert!(m.unlit && m.emissive == LinearRgba::BLACK); - } } #[test] fn eyes_exposes_concrete_as_visual_geography() { - let seen = material_3d(TileType::Floor, Fog::Seen, TilePart::Floor, false); + let seen = seen_material_3d(TileType::Floor, TilePart::Floor, false); let seen_color = seen.base_color.to_srgba(); assert!( @@ -3489,9 +3420,7 @@ mod flat_materials { fn palette_roles_hold() { for tile in ALL_TILES { for part in ALL_PARTS { - let c = material_3d(tile, Fog::Seen, part, false) - .base_color - .to_srgba(); + let c = seen_material_3d(tile, part, false).base_color.to_srgba(); let crimson_dominant = c.red > c.green * 2.0 && c.red > c.blue * 2.0; if crimson_dominant && part != TilePart::Floor { assert!( diff --git a/wiki/interface/material-render.md b/wiki/interface/material-render.md index a15ff125..d8ca19f8 100644 --- a/wiki/interface/material-render.md +++ b/wiki/interface/material-render.md @@ -11,10 +11,10 @@ Status note: implemented 2026-07-08 — the material render reached screenshot comparison), dedicated darkened top caps on all extruded boxes, palette sweep of the b1 set (door/UPS/HVAC/switch hue-remapped off the stale purple/teal lair palette), fog contract asserted by a - dev-harness audit (unknown=absent, model=unlit, seen=lit), zoom - bounds tightened to hold the framing floor, and (tile, fog, part) - material pooling with handle swaps (37 pooled handles for ~4600 tile - entities in the audit run). The old person-billboard debt was retired by the + dev-harness audit (non-Seen=absent, Seen=lit), zoom bounds tightened to hold + the framing floor, and Seen-only (tile, part, live) material pooling with + handle swaps (37 pooled handles for ~4600 tile entities in the audit run). + The old person-billboard debt was retired by the 2026-07-10 volumetric articulated institution silhouettes; current REAL stand-ins are tracked in the [placeholder registry](../art/placeholders.md). Prototype findings inventory: wiki/log/2026-07-07-hd2d-prototype.md; @@ -38,7 +38,9 @@ Status note: implemented 2026-07-08 — the material render reached absent in REAL; only local Seen state admits physical tile matter. A regular Bevy unit test now sweeps every tile kind and preallocated tile part across that boundary instead of leaving the rule defended only by the screenshot - harness. + harness. The same audit then removed the unreachable non-Seen variants from + the material constructor and pool: visibility resolves to Seen before either + API is called, and the cache has no fog dimension or model-state material. Stage: B1 — The Basement Design: - wiki/interface/presence.md#two-views-of-one-world-same-frame-digital-and-real-representations @@ -84,8 +86,10 @@ treatment.) 4. **Framing:** the visual-floor rules (fit-to-known-content, no postage stamp, no void-dominant frame) hold in material mode at all zoom levels. -5. **Perf:** materials pooled by (tile, fog, part) and handle-swapped; - no per-tile unique material mutation (prototype finding 5). Wall- +5. **Perf:** after the visibility boundary admits local Seen, materials are + pooled by (tile, part, live) and handle-swapped; the constructor and cache + cannot encode Unknown, Blueprint, or Remembered variants, and there is no + per-tile unique material mutation (prototype finding 5). Wall- clock pacing must not dirty the Game resource every display frame — restyle_3d, map recolor, and the sidebar rebuild only on sim steps (or explicit dirty), not at 60 Hz from the tick timer alone. @@ -109,8 +113,10 @@ treatment.) DIGITAL treatment only; REAL never draws plan mass. 5. Framing floor holds in material mode (fit, zoom bounds, no void dominance) — checked at min/default/max zoom. -6. Material pooling implemented; tile-count-scaled material churn is - gone (no per-tile unique materials in the frame loop). Full map +6. Seen-only material pooling is implemented; tile-count-scaled material churn + and the unreachable fog-key space are gone (no per-tile unique materials in + the frame loop, and no model-state material variants after the production + visibility gate). Full map restyle is not driven by the wall-clock timer alone — only by sim steps or an explicit dirty flag (2026-07-10). 7. Material render reaches default-quality physical-view treatment as the REAL @@ -122,4 +128,7 @@ boundary through one pure tile-part predicate. Its ordinary Bevy regression sweeps every current tile kind and every preallocated floor/block/base/top/prop part and rejects Unknown, Blueprint, or Remembered geometry before material or lighting choices can expose it. The screenshot harness remains the composed -frame audit; it is no longer the only executable defense of the rule. +frame audit; it is no longer the only executable defense of the rule. Criterion +6 is defended by `material_pool_is_seen_only_and_stable`: production material +construction is named and typed for Seen state only, repeated reads share the +same `(tile, part, live)` handle, and the cache key has no fog field. diff --git a/wiki/log/2026-07-29-seen-only-material-pool.md b/wiki/log/2026-07-29-seen-only-material-pool.md new file mode 100644 index 00000000..3c810a51 --- /dev/null +++ b/wiki/log/2026-07-29-seen-only-material-pool.md @@ -0,0 +1,30 @@ +# REAL's material pool now contains only visible matter + +``` +Type: log +Date: 2026-07-29 +Subject: Seen-only material construction and pooling +``` + +The production REAL visibility boundary already rejected Unknown, Blueprint, +and Remembered tile parts before spawning physical geometry. The material +constructor and pooled cache nevertheless still modeled all four fog states, +leaving dormant non-Seen colors and an unreachable fog dimension behind that +gate. + +The constructor is now explicitly `seen_material_3d`. It accepts only tile, +part, and live-feed state, and has no branch for model-state material. The pool +uses the same `(tile, part, live)` identity. Production first requires local +`Fog::Seen`, then requests that Seen material; it cannot manufacture a dim +Blueprint or Remembered body by selecting another cached handle. + +The existing Cartesian visibility defense still sweeps every tile kind, every +preallocated floor/block/base/top/prop part, every non-Seen fog state, and both +shared-institution body paths. `material_pool_is_seen_only_and_stable` adds the +second half of the boundary: repeated requests share one handle in a cache +whose key has no fog field. The material-focused Bevy tests passed with 27 +tests and no failures after removing the now-dead remembered-material helper. + +Owner: [material-render.md](../interface/material-render.md), criterion 6. +Composed visibility law: [material-dark-frame.md](../interface/material-dark-frame.md), +criterion 4. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 4a381070..1413b316 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-29 - REAL's material pool now contains only visible matter + +- Intent: (see session log) +- Log: [wiki/log/2026-07-29-seen-only-material-pool.md](2026-07-29-seen-only-material-pool.md) + ## 2026-07-29 - Routed records walk their persisted custody - Intent: (see session log) -- 2.51.2