From f02fb4ec280045b8e0a24965ac9aa677eb2409f8 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Mon, 27 Jul 2026 11:11:15 +0000 Subject: [PATCH] Keep generated cameras physical and inward. Defense: sensor-network criteria 1 and 3 require every generated sight device to have a fog-gated REAL body on a walkable interior origin, while badges remain a physical-worker gate rather than a TAP gate. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- wiki/log/2026-07-27-sensor-population-review-repair.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ wiki/log/DEVLOG.md | 5 +++++ wiki/mechanics/sensor-network.md | 28 ++++++++++++++++++++-------- crates/misaligned-bevy/src/main.rs | 2 +- crates/misaligned-bevy/src/material_view.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- crates/misaligned-bevy/src/shot_harness.rs | 22 +++++++++++++++++++++- crates/misaligned-core/src/reach.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- wiki/log/decisions/2026-07-26.md | 8 +++++--- 8 file(s) changed, 241 insertion(s)(+), 24 deletion(s)(-) diff --git a/wiki/log/2026-07-27-sensor-population-review-repair.md b/wiki/log/2026-07-27-sensor-population-review-repair.md new file mode 100644 --- /dev/null +++ b/wiki/log/2026-07-27-sensor-population-review-repair.md @@ -0,0 +1,59 @@ +# Generated cameras keep physical, inward bodies + +``` +Type: log +``` + +## Finding + +Review of the topology-generated B1 sensor landing found two runtime defects and +one stale mirror. + +The west Foundation T2 door's approach camera began at `(14, 19)`, a perimeter +Wall. `Room::contains` includes the room boundary, so selecting a neighboring +coordinate by membership alone accepted the wall and let the camera's sight ray +begin on the wrong side of the access boundary. + +The generated devices also existed only in ReachNet on ordinary Floor tiles. +REAL created institution geometry solely from authored map tile kinds, so those +new cameras supplied sight without physical camera bodies. Finally, two corpus +phrases grouped badge tier with the digital subscription gates even though the +standing law says badges constrain bodies and never packets. + +## Change + +Each hall-door approach now chooses candidates toward the hall center first and +accepts only a different coordinate that is both inside the hall and walkable. +All three T2 approach cameras therefore begin on the interior Floor side of +their door; the west camera moves to `(15, 18)`. + +REAL now resolves an institution prop from both authorities at one coordinate: +an authored institution tile remains primary, while any seeing ReachNet device +on ordinary floor resolves a Camera body. Initial spawn, live fog/visibility +sync, legacy-placeholder suppression, and the screenshot audit all use that +same resolver. The audit also requires exactly one institution root for every +coordinate classified from map fixtures plus sight devices, and requires every +root to contain material-layer meshes with inherited visibility matching fog. + +The sensor spec and its adopted decision mirror now state the exact access +boundary: knowledge, digital reach, and segment gate TAP; badge tier applies +only to a body-having worker's future installation, maintenance, or rewiring. + +## Defense + +This implements sensor-network criterion 1's physical population and criterion +3's occlusion-bounded sight without changing sight's definition. The core +regression proves every hall-door camera starts on an inward walkable tile. The +Bevy regression proves every seeing device has a unique coordinate and resolves +a Camera body there. A deterministic `hall-material` REAL capture then audits +all institution hierarchies and fog visibility before writing the PNG. + +Observed pre-reconciliation receipt: + +- `foundation_hall_door_cameras_begin_inside_on_walkable_tiles`: pass; +- `every_sight_device_has_a_real_camera_body_kind`: pass; +- `hall-material`: REAL, `30/30` institution bodies visible and fog-gated, + visual inspection pass, PNG SHA-256 + `d5519c8a1a74260792a882e1f325259e264a23ddc5f0b48b4f01abe0a7ec8c56`. + +Owner: [sensor-network.md](../mechanics/sensor-network.md). diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -31,6 +31,11 @@ - Intent: (see session log) - Log: [wiki/log/2026-07-27-tangled-cli-path.md](2026-07-27-tangled-cli-path.md) +## 2026-07-27 - Generated cameras keep physical, inward bodies + +- Intent: (see session log) +- Log: [wiki/log/2026-07-27-sensor-population-review-repair.md](2026-07-27-sensor-population-review-repair.md) + ## 2026-07-27 - The basement gains an ambient sensor population - Intent: (see session log) diff --git a/wiki/mechanics/sensor-network.md b/wiki/mechanics/sensor-network.md --- a/wiki/mechanics/sensor-network.md +++ b/wiki/mechanics/sensor-network.md @@ -3,7 +3,11 @@ ``` Type: spec Status: IN PROGRESS -Status note: 2026-07-27 — deterministic ~30-device population foundation +Status note: 2026-07-27 — post-landing review keeps every hall-door camera's + body and sight origin on the first inward walkable hall tile; every seeing + device, including topology-generated devices backed by ordinary Floor tiles, + now resolves one same-coordinate Camera body in REAL. The deterministic + ~30-device population foundation landed: rule-generated B1 sensor population (exactly 30 sensor-class devices: 24 sight/hearing, 4 badge-reader nodes, 2 facility meters) with a toy floor proving the rule travels (criterion 2). Sight definition unchanged @@ -79,6 +83,10 @@ - Sensors are **populated by rule, not hand-listed per room**, so a later floor inherits the coverage logic instead of needing a new authored inventory. This is the same reason prefabs are data (basement-map.md). +- A seeing device is physical infrastructure. In REAL it has one camera body at + the device's coordinate even when the population rule placed it on an ordinary + Floor tile rather than an authored camera tile. That body appears only when + live sight has earned its tile, under the ordinary fog contract. - The scarce resource is **access**, not existence. The player may know a sensor is there long before it can be subscribed: knowledge of the graph and reach over a segment gate digital packets. Badge tiers separately gate the @@ -95,7 +103,7 @@ | Class | Rule | Count | |---|---|---| | Corridor nodes | one `camera_node` roughly every eight tiles along both service galleries | ~8 | -| Hall aisle cameras | one per cold aisle, plus the hall's door approaches | ~5 | +| Hall aisle cameras | one per cold aisle, plus the first inward walkable hall tile at each door approach | ~5 | | Room cameras | one per enclosed room: closet, electrical, HVAC, both storages, janitor, wet lab, dock, stairwell, elevator | ~10 | | Badge readers | one per security door; a reader **sees who passed** even where no camera looks | ~6 | | Facility meters | the existing UPS and HVAC meters (power and thermal senses) | 2 | @@ -182,11 +190,13 @@ The opening is unchanged: the player starts owning nothing and sees nothing, and the Eyes beat is still the first sight it earns. What changes is everything -after that beat — sight grows sensor by sensor as reach, badges, and segments -open, and the subscription list becomes something the player curates rather than -maximizes. Both frontends must surface, for every subscription, its standing -drain and a one-action UNTAP, because a cost the player cannot see is a cost it -cannot learn from. +after that beat — sight grows sensor by sensor as knowledge, digital reach, and +segments open. Badge tiers enter only when a body-having worker installs, +maintains, or rewires physical hardware; they never gate a digital TAP. The +subscription list becomes something the player curates rather than maximizes. +Both frontends must surface, for every subscription, its standing drain and a +one-action UNTAP, because a cost the player cannot see is a cost it cannot learn +from. ## Acceptance criteria @@ -195,7 +205,9 @@ and badge readers as a sensing class. No enclosed room is sensorless. (partial: badge-reader nodes exist but do not yet emit passage telemetry) — `basement_sensor_population_is_exactly_thirty`, - `room_cameras_exist_for_every_enclosed_room` + `room_cameras_exist_for_every_enclosed_room`, + `foundation_hall_door_cameras_begin_inside_on_walkable_tiles`, + `every_sight_device_has_a_real_camera_body_kind` 2. ✅ Sensor placement is generated from a rule, not a hand-listed per-room inventory, and a second toy floor built from the same rule proves it travels. — `sensor_rule_travels_to_toy_floor`, `corridor_camera_nodes_space_evenly` diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -82,7 +82,7 @@ data_hall_infrastructure_applies, data_hall_infrastructure_specs, data_hall_infrastructure_visible, evidence_carrier_visible, evidence_has_index, evidence_literal_count, evidence_marker_text, flicker_feeds, floor_fixture_visible, - grid_to_world_3d, institution_prop_kind, known_tile, material_feel_route, owned_machine_at, + grid_to_world_3d, institution_prop_kind_at, known_tile, material_feel_route, owned_machine_at, owned_machine_tiles, rectilinear_floor_route, render_audio_events_3d, render_build_beam, render_evidence_carriers_3d, render_people_3d, render_real_links, render_service_carriers_3d, restyle_3d, reveal_first_eyes, service_carrier_visible, setup_3d, diff --git a/crates/misaligned-bevy/src/material_view.rs b/crates/misaligned-bevy/src/material_view.rs --- a/crates/misaligned-bevy/src/material_view.rs +++ b/crates/misaligned-bevy/src/material_view.rs @@ -653,6 +653,24 @@ } } +/// Resolve the physical institution body at one coordinate. Authored +/// fixtures live in map tiles; coverage-generated cameras are ReachNet +/// devices on ordinary walkable floor and must not need a second map marker. +pub(super) fn institution_prop_kind_at( + sim: &Sim, + tile: TileType, + x: i32, + y: i32, +) -> Option { + institution_prop_kind(tile).or_else(|| { + sim.reach + .devices + .iter() + .any(|device| device.sees && (device.x, device.y) == (x, y)) + .then_some(InstitutionPropKind::Camera) + }) +} + /// Named signal state for one institution prop. Service cabinets carry the /// cold powered fact; a camera stays neutral until one of its feeds actually /// reaches the player. Visibility remains a separate Seen/fog decision. @@ -1604,7 +1622,7 @@ }, ChildOf(root), )); - if let Some(kind) = institution_prop_kind(tile) { + if let Some(kind) = institution_prop_kind_at(&game.sim, tile, x, y) { let scale = if kind == InstitutionPropKind::Hvac { 0.86 } else { @@ -2091,7 +2109,12 @@ for (entity, mut prop, mut visibility) in props.iter_mut() { let visible = mode.material && matches!(game.sim.fog_at(prop.x, prop.y), Fog::Seen) - && institution_prop_kind(known_tile(&game, prop.x, prop.y)) == Some(prop.kind); + && institution_prop_kind_at( + &game.sim, + known_tile(&game, prop.x, prop.y), + prop.x, + prop.y, + ) == Some(prop.kind); *visibility = if visible { Visibility::Inherited } else { @@ -2155,7 +2178,11 @@ (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(tile).is_some() => false, + (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, @@ -3176,6 +3203,7 @@ #[cfg(test)] mod flat_materials { use super::*; + use std::collections::HashSet; const ALL_TILES: [TileType; 33] = [ TileType::Rock, @@ -3220,6 +3248,45 @@ TilePart::Top, TilePart::Prop, ]; + + #[test] + fn every_sight_device_has_a_real_camera_body_kind() { + let sim = Sim::new(); + let sight_devices = sim + .reach + .devices + .iter() + .filter(|device| device.sees) + .collect::>(); + assert!( + sight_devices.len() > 20, + "the B1 population should include coverage-generated cameras" + ); + let positions = sight_devices + .iter() + .map(|device| (device.x, device.y)) + .collect::>(); + assert_eq!( + positions.len(), + sight_devices.len(), + "one coordinate may not silently collapse multiple sight devices into one body" + ); + for device in sight_devices { + assert_eq!( + institution_prop_kind_at( + &sim, + sim.map().get_tile(device.x, device.y), + device.x, + device.y, + ), + Some(InstitutionPropKind::Camera), + "sight device {} at ({}, {}) has no REAL camera body", + device.name, + device.x, + device.y + ); + } + } /// Criterion 1: no world material references an image texture — the /// whole (tile, fog, part, live) material space is solid color. diff --git a/crates/misaligned-bevy/src/shot_harness.rs b/crates/misaligned-bevy/src/shot_harness.rs --- a/crates/misaligned-bevy/src/shot_harness.rs +++ b/crates/misaligned-bevy/src/shot_harness.rs @@ -1782,10 +1782,28 @@ "evidence-proof has {visible_evidence_carriers} visible evidence racks; expected one exact carrier" )); } + let expected_institution_roots = (0..game.sim.map().height) + .flat_map(|y| (0..game.sim.map().width).map(move |x| (x, y))) + .filter(|&(x, y)| { + institution_prop_kind_at(&game.sim, game.sim.map().get_tile(x, y), x, y).is_some() + }) + .count(); + let institution_roots = institution_props.iter().count(); + if institution_roots != expected_institution_roots { + violations.push(format!( + "{institution_roots} institution roots; expected {expected_institution_roots} from map fixtures plus sight devices" + )); + } + let mut visible_institution_roots = 0usize; for (entity, prop, vis) in institution_props.iter() { let expected = mode.material && matches!(game.sim.fog_at(prop.x, prop.y), Fog::Seen) - && institution_prop_kind(known_tile(&game, prop.x, prop.y)) == Some(prop.kind); + && institution_prop_kind_at( + &game.sim, + known_tile(&game, prop.x, prop.y), + prop.x, + prop.y, + ) == Some(prop.kind); if vis.get() != expected { violations.push(format!( "({}, {}) institution {:?} visible={} but earned={expected}", @@ -1795,6 +1813,7 @@ vis.get() )); } + visible_institution_roots += usize::from(vis.get()); let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); if audit.meshes == 0 || audit.material_layer != audit.meshes { violations.push(format!( @@ -2002,6 +2021,7 @@ println!( "fog audit OK: {checked} tile entities (unknown/blueprint/remembered=absent, \ seen=lit, owned telemetry=lit), {} people silhouettes coverage-gated, \ + {visible_institution_roots}/{institution_roots} institution bodies visible and fog-gated, \ {visible_service_carriers}/{} service carts carried-and-seen, \ {visible_evidence_carriers}/{} evidence racks acquired-and-seen, \ {visible_fixtures}/{} floor-fixture entities visible and fog-gated, \ diff --git a/crates/misaligned-core/src/reach.rs b/crates/misaligned-core/src/reach.rs --- a/crates/misaligned-core/src/reach.rs +++ b/crates/misaligned-core/src/reach.rs @@ -353,14 +353,11 @@ for y in hall_room.y..hall_room.y + hall_room.h { for x in hall_room.x..hall_room.x + hall_room.w { if map.get_tile(x, y) == TileType::SecurityDoor2 { - // Place a camera just outside the door (one tile toward - // the hall interior). - let (ix, iy) = if hall_room.contains(x, y + 1) { - (x, y + 1) - } else if hall_room.contains(x, y - 1) { - (x, y - 1) - } else { - (x, y) + // The body and its ray origin stay on a walkable hall tile + // just inside the door. A perimeter wall also satisfies + // `Room::contains`, so membership alone is not enough. + let Some((ix, iy)) = hall_door_camera_position(map, hall_room, (x, y)) else { + continue; }; let occupied = devices.iter().any(|d| d.x == ix && d.y == iy); if occupied { @@ -373,6 +370,26 @@ } } } +} + +fn hall_door_camera_position( + map: &GameMap, + hall: &crate::prefab::Room, + door: (i32, i32), +) -> Option<(i32, i32)> { + let center = (hall.x + hall.w / 2, hall.y + hall.h / 2); + let toward_x = (door.0 + (center.0 - door.0).signum(), door.1); + let toward_y = (door.0, door.1 + (center.1 - door.1).signum()); + [ + toward_x, + toward_y, + (door.0 + 1, door.1), + (door.0 - 1, door.1), + (door.0, door.1 + 1), + (door.0, door.1 - 1), + ] + .into_iter() + .find(|&(x, y)| (x, y) != door && hall.contains(x, y) && map.is_walkable(x, y)) } /// Create one rule-generated facility sensor on the security segment. @@ -1644,6 +1661,41 @@ !badge_readers.is_empty(), "badge readers present as a sensing class" ); + } + + #[test] + fn foundation_hall_door_cameras_begin_inside_on_walkable_tiles() { + let map = GameMap::new(0, 0); + let n = ReachNet::basement(&map); + let hall = map.room_named("server_room").unwrap(); + let doors = map + .tiles_of_type(TileType::SecurityDoor2) + .into_iter() + .filter(|&(x, y)| { + [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)] + .into_iter() + .any(|(nx, ny)| hall.contains(nx, ny)) + }) + .collect::>(); + assert_eq!(doors.len(), 3); + + for door in doors { + let position = hall_door_camera_position(&map, hall, door).unwrap(); + assert!(hall.contains(position.0, position.1)); + assert!(map.is_walkable(position.0, position.1)); + assert_eq!( + n.devices + .iter() + .filter(|device| { + device.sees + && device.name.contains("hall door camera") + && (device.x, device.y) == position + }) + .count(), + 1, + "tier-2 door {door:?} needs one inward camera at {position:?}" + ); + } } /// Criterion 1 (continued): no enclosed room is sensorless. diff --git a/wiki/log/decisions/2026-07-26.md b/wiki/log/decisions/2026-07-26.md --- a/wiki/log/decisions/2026-07-26.md +++ b/wiki/log/decisions/2026-07-26.md @@ -465,9 +465,11 @@ class — a door that records a badge is a witness. - Sensors are **populated by rule, not hand-listed per room**, so later floors inherit the coverage logic. Same reason prefabs are data. -- The scarce resource is **access, not existence**. Knowledge, reach, segment, - and badge tier are the gates. "I can see it exists and cannot have it yet" is - the intended feeling; a region that reads as sensorless is a content bug. +- The scarce resource is **access, not existence**. Knowledge, digital reach, + and segment gate a TAP. Badge tier gates only the body-having people who may + install, maintain, or rewire physical hardware; it never gates packets. "I + can see it exists and cannot have it yet" is the intended feeling; a region + that reads as sensorless is a content bug. - **Some of the building stays dark, and darkness is never authored by fiat.** A dark region always traces to a nameable physical cause — an air-gapped segment, an unbridged switch, a door tier you cannot pass. Darkness is -- tangled.sh