diff --git a/crates/misaligned-bevy/src/material_view.rs b/crates/misaligned-bevy/src/material_view.rs index a0a2534f..4f1f9fe4 100644 --- a/crates/misaligned-bevy/src/material_view.rs +++ b/crates/misaligned-bevy/src/material_view.rs @@ -2249,26 +2249,12 @@ pub(super) fn restyle_3d( // language is separate from geometry. Blueprint/Remembered mass reads // in DIGITAL and overlays, never as matter the camera did not see. // Audio is channel evidence and never creates matter. - let show = match (t.part, fog) { - (_, Fog::Unknown) => false, - (TilePart::Floor, Fog::Seen) => true, - (TilePart::Floor, _) => false, - // Only camera-earned Seen form becomes physical structure. - (TilePart::Block | TilePart::Base | TilePart::Top, Fog::Seen) => blocky_tile(tile), - (TilePart::Block | TilePart::Base | TilePart::Top, _) => false, - // Foundation cameras and service hardware use the shared - // institution kit, never the legacy upright placeholder card. - (TilePart::Prop, _) - if institution_prop_kind_at(&game.sim, tile, t.x, t.y).is_some() => - { - false - } - // Chassis tiles render as real 3D machine meshes - // (sync_machines_3d) — never as billboards. - (TilePart::Prop, _) if chassis_3d_tile(tile) => false, - (TilePart::Prop, Fog::Seen) => prop_tile(tile), - (TilePart::Prop, _) => false, - }; + let show = material_tile_part_visible( + t.part, + fog, + tile, + institution_prop_kind_at(&game.sim, tile, t.x, t.y).is_some(), + ); if !show { *vis = Visibility::Hidden; continue; @@ -2310,6 +2296,25 @@ pub(super) fn restyle_3d( } } +/// REAL is a camera, not a floor plan: only locally Seen tile parts become +/// physical matter. Shared institution bodies and chassis have their own +/// renderers, so their legacy prop cards stay absent even after sight lands. +fn material_tile_part_visible( + part: TilePart, + fog: Fog, + tile: TileType, + shared_institution_body: bool, +) -> bool { + if !matches!(fog, Fog::Seen) { + return false; + } + match part { + TilePart::Floor => true, + TilePart::Block | TilePart::Base | TilePart::Top => blocky_tile(tile), + TilePart::Prop => !shared_institution_body && !chassis_3d_tile(tile) && prop_tile(tile), + } +} + /// Scan flicker on live feeds (flat-materials.md: functional shader-level /// detail only): the pooled live-feed materials' cold-signal emissive /// breathes, so a device actively feeding the player reads as a running @@ -3283,7 +3288,7 @@ mod flat_materials { use super::*; use std::collections::HashSet; - const ALL_TILES: [TileType; 33] = [ + const ALL_TILES: [TileType; 35] = [ TileType::Rock, TileType::Wall, TileType::Floor, @@ -3295,6 +3300,8 @@ mod flat_materials { TileType::SecurityDoor2, TileType::SecurityDoor3, TileType::Rack, + TileType::ForeignRack, + TileType::DeadRack, TileType::Ups, TileType::EnvCamera, TileType::Switch, @@ -3385,6 +3392,52 @@ mod flat_materials { } } + /// material-render.md criterion 4: REAL contains no plan geometry. + /// Unknown, Blueprint, and Remembered all keep every preallocated tile + /// part absent; only local camera-Seen state can admit physical matter. + #[test] + fn material_tile_parts_require_local_seen_fog() { + for tile in ALL_TILES { + for part in ALL_PARTS { + for fog in [Fog::Unknown, Fog::Blueprint, Fog::Remembered] { + for shared_institution_body in [false, true] { + assert!( + !material_tile_part_visible(part, fog, tile, shared_institution_body), + "{tile:?}/{part:?}/{fog:?}/shared={shared_institution_body} must stay absent in REAL" + ); + } + } + } + } + + assert!(material_tile_part_visible( + TilePart::Floor, + Fog::Seen, + TileType::Floor, + false + )); + assert!(material_tile_part_visible( + TilePart::Block, + Fog::Seen, + TileType::Wall, + false + )); + assert!(material_tile_part_visible( + TilePart::Prop, + Fog::Seen, + TileType::PowerCore, + false + )); + assert!( + !material_tile_part_visible(TilePart::Prop, Fog::Seen, TileType::EnvCamera, true), + "the institution kit, not a legacy card, owns shared hardware bodies" + ); + assert!( + !material_tile_part_visible(TilePart::Prop, Fog::Seen, TileType::Core, false), + "the machine renderer, not a legacy card, owns chassis bodies" + ); + } + /// Criterion 3: emissive is information. Powered vs dead machines, live /// feeds, and the core read differently; model state never glows. #[test] diff --git a/wiki/interface/material-render.md b/wiki/interface/material-render.md index 287948b5..a15ff125 100644 --- a/wiki/interface/material-render.md +++ b/wiki/interface/material-render.md @@ -33,6 +33,12 @@ Status note: implemented 2026-07-08 — the material render reached do not rebuild every display frame; owned-tile caches. The old Heard-noise walk was removed by the 2026-07-12 channel-evidence amendment; audio now costs only bounded device-local event pulses. + 2026-07-29 fog-contract audit: criterion 4 now follows the binding + dark-frame amendment exactly — Unknown, Blueprint, and Remembered mass are + 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. Stage: B1 — The Basement Design: - wiki/interface/presence.md#two-views-of-one-world-same-frame-digital-and-real-representations @@ -98,8 +104,9 @@ treatment.) 3. No stale-palette tiles remain in the b1 set under 3D light (door fixed; sweep recorded). 4. The fog contract holds and is asserted: unknown = no geometry, - blueprint/remembered = unlit, seen = lit (screenshot or debug-dump - verification recorded in the log). + blueprint/remembered = no geometry, seen = lit physical matter (screenshot + or debug-dump verification recorded in the log). Unlit blueprint remains a + 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 @@ -109,3 +116,10 @@ treatment.) 7. Material render reaches default-quality physical-view treatment as the REAL dialect; F3 selects it from DIGITAL home; README and bevy.md stay current; terminal unaffected. + +Defense: Criterion 4 is implemented at the production `restyle_3d` visibility +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. diff --git a/wiki/log/2026-07-29-material-fog-contract-defense.md b/wiki/log/2026-07-29-material-fog-contract-defense.md new file mode 100644 index 00000000..9ba38f7e --- /dev/null +++ b/wiki/log/2026-07-29-material-fog-contract-defense.md @@ -0,0 +1,47 @@ +# Material fog contract defense + +``` +Type: log +``` + +## Finding + +The material-render work order had one present-tense internal contradiction. +Its status note and Behavior section correctly carried the 2026-07-09 +dark-frame amendment: Blueprint and Remembered geometry is absent in REAL. +Acceptance criterion 4 still retained the superseded prototype wording that +called the same mass unlit. Production already obeyed the amendment, but the +visibility match was inlined in `restyle_3d` and only the development screenshot +harness audited the complete fog boundary directly. + +## Repair + +Criterion 4 now says what the binding dark-frame spec and live renderer say: +Unknown, Blueprint, and Remembered mass is absent; only local Seen admits +physical matter. The production visibility match is now one pure predicate. +A normal Bevy unit test runs every current tile kind and every preallocated +floor, block, base, top, and prop part through Unknown, Blueprint, and +Remembered and requires all of them to remain absent. Seen cases also pin the +ordinary floor, structural, standalone-prop, institution-kit, and chassis +ownership boundaries. + +No player-visible runtime behavior changed. The repair makes the public +contract internally consistent and moves its narrowest recurrence defense into +the ordinary test wall while retaining the screenshot harness as composed-frame +evidence. + +## Verification + +- `cargo test -p misaligned-bevy --bin misaligned-bevy + material_tile_parts_require_local_seen_fog`: one focused regression passed. +- `./tools/check.sh --frontend`: 25 shared-asset tests, seven asset-binary + tests, and 154 Bevy tests passed with Clippy and every corpus/fixture gate. +- `tools/bevy-headless.sh dark --output + /tmp/hourly-material-fog-contract-dark.png`: the production REAL frame + reported 5,832 tile entities with Unknown, Blueprint, and Remembered absent + and Seen lit. The exact final PNG (SHA-256 + `8eebe9c1694f8f309e35d927449ba85bcd5dfa13da80397276a2b37adf961dab`) was + opened after capture: the earned hall is lit material structure while the + unseen surrounding floor remains true black, with no plan mass or ghost + geometry crossing the boundary. +- Exact fresh-origin landing gate. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 3fce6cb4..4a381070 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -26,6 +26,11 @@ add or amend a session log, then re-run the generator. - Intent: (see session log) - Log: [wiki/log/2026-07-29-plot-policy-persona-authority.md](2026-07-29-plot-policy-persona-authority.md) +## 2026-07-29 - Material fog contract defense + +- Intent: (see session log) +- Log: [wiki/log/2026-07-29-material-fog-contract-defense.md](2026-07-29-material-fog-contract-defense.md) + ## 2026-07-29 - Link destinations fold behind one intention - Intent: (see session log)