diff --git a/crates/misaligned-core/src/prefab.rs b/crates/misaligned-core/src/prefab.rs index 3c4cd28c..82b553f4 100644 --- a/crates/misaligned-core/src/prefab.rs +++ b/crates/misaligned-core/src/prefab.rs @@ -234,22 +234,22 @@ const ELECTRICAL: Prefab = Prefab { const HVAC: Prefab = Prefab { name: "hvac", - rows: &["#####", "#HH.#", "#.VV#", "##+##"], + rows: &["#####", "#HH.#", "#V.V#", "##+##"], }; const JANITOR: Prefab = Prefab { name: "janitor", - rows: &["#####", "#MK.#", "#.L.#", "##+##"], + rows: &["#####", "#MK.#", "#..L#", "##+##"], }; const STORAGE_A: Prefab = Prefab { name: "storage_a", - rows: &["#####", "#DD.#", "#DL.#", "##+##"], + rows: &["#####", "#DD.#", "#D.L#", "##+##"], }; const STORAGE_B: Prefab = Prefab { name: "storage_b", - rows: &["#####", "#XX.#", "#XL.#", "##+##"], + rows: &["#####", "#XX.#", "#X.L#", "##+##"], }; const WET_LAB: Prefab = Prefab { @@ -261,7 +261,7 @@ const LOADING_DOCK: Prefab = Prefab { name: "loading_dock", rows: &[ "#########", // - "GW....W.#", // roll door + pallets + "G.W...W.#", // roll door + pallets "#......F#", // dock camera "####+####", ], @@ -269,7 +269,7 @@ const LOADING_DOCK: Prefab = Prefab { const STAIRWELL: Prefab = Prefab { name: "stairwell", - rows: &["####", "#N.#", "#.Q#", "##3#"], + rows: &["####", "#N.#", "#Q.#", "##3#"], }; /// Build the authored basement layout (64x36). @@ -352,9 +352,15 @@ pub fn basement() -> Layout { }; let mut tiles = layout.stamp(); carve_corridors(&mut tiles, layout.width, BASEMENT_CORRIDORS); - // Entry from the loading dock to the outside world (top edge). + // Entry from the loading dock to the outside world (top edge). Approach + // the west-facing roll door from outside the prefab rather than cutting + // through the dock's north wall above it. set(&mut tiles, layout.width, 2, 0, TileType::Entry); - carve_corridor(&mut tiles, layout.width, (2, 0), (2, 2)); + carve_corridors( + &mut tiles, + layout.width, + &[((2, 0), (1, 0)), ((1, 0), (1, 2)), ((1, 2), (2, 2))], + ); layout.placements.clear(); // corridors already carved into cached tiles layout.cached = Some(tiles); layout @@ -379,10 +385,14 @@ fn rooms_of(prefabs: &[Prefab], placements: &[Placement]) -> Vec { /// A corridor run from one point to another, carved as an L. type Corridor = ((i32, i32), (i32, i32)); -// Corridor spine connecting the rooms. Coordinates target room door tiles. +// Corridor spine connecting the rooms. Each final leg targets a room door +// tile. Keep turns as explicit axis-aligned legs: `carve_corridor` is +// horizontal-first, so one diagonal pair can silently cut through a prefab. const BASEMENT_CORRIDORS: &[Corridor] = &[ ((6, 5), (36, 5)), // north service gallery - ((6, 5), (6, 7)), // dock to Storage A + ((7, 5), (7, 11)), // Storage A east exterior + ((7, 11), (4, 11)), // Storage A south exterior + ((4, 11), (4, 10)), // Storage A door ((16, 5), (16, 4)), // network closet to gallery ((24, 5), (24, 4)), // electrical to gallery ((32, 5), (32, 4)), // HVAC to gallery @@ -391,10 +401,22 @@ const BASEMENT_CORRIDORS: &[Corridor] = &[ ((11, 18), (14, 18)), // approach to hall west T2 door ((16, 29), (46, 29)), // south maintenance gallery ((36, 28), (36, 29)), // hall south T2 door - ((16, 29), (16, 30)), // wet lab - ((26, 29), (26, 30)), // Storage B - ((36, 29), (36, 30)), // janitor - ((46, 29), (46, 33)), // stairwell tier-3 door + ((16, 29), (13, 29)), // wet lab west exterior + ((13, 29), (13, 34)), + ((13, 34), (16, 34)), + ((16, 34), (16, 33)), // wet lab sealed door + ((26, 29), (23, 29)), // Storage B west exterior + ((23, 29), (23, 34)), + ((23, 34), (26, 34)), + ((26, 34), (26, 33)), // Storage B door + ((36, 29), (33, 29)), // janitor west exterior + ((33, 29), (33, 34)), + ((33, 34), (36, 34)), + ((36, 34), (36, 33)), // janitor door + ((46, 29), (43, 29)), // stairwell west exterior + ((43, 29), (43, 34)), + ((43, 34), (46, 34)), + ((46, 34), (46, 33)), // stairwell tier-3 door ]; fn set(tiles: &mut [TileType], w: i32, x: i32, y: i32, t: TileType) { @@ -788,6 +810,208 @@ mod tests { ); } + #[test] + fn non_hall_approaches_meet_their_doors_without_breaching_room_perimeters() { + // basement-map.md criterion 10: the corridor overlay is allowed to + // meet a prefab only at an authored doorway. Every door also needs a + // clear floor on both faces; a route that reaches the right glyph but + // leaves shelving, a vent, or a sump immediately inside is still dead. + #[derive(Clone, Copy)] + struct Approach { + room: &'static str, + door: (i32, i32), + kind: TileType, + exterior: (i32, i32), + interior: (i32, i32), + gallery: (i32, i32), + } + + let approaches = [ + Approach { + room: "loading_dock", + door: (2, 2), + kind: TileType::RollDoor, + exterior: (1, 2), + interior: (3, 2), + gallery: (2, 0), + }, + Approach { + room: "loading_dock", + door: (6, 4), + kind: TileType::Door, + exterior: (6, 5), + interior: (6, 3), + gallery: (6, 5), + }, + Approach { + room: "storage_a", + door: (4, 10), + kind: TileType::Door, + exterior: (4, 11), + interior: (4, 9), + gallery: (6, 5), + }, + Approach { + room: "network_closet", + door: (16, 4), + kind: TileType::SecurityDoor2, + exterior: (16, 5), + interior: (16, 3), + gallery: (6, 5), + }, + Approach { + room: "electrical", + door: (24, 4), + kind: TileType::Door, + exterior: (24, 5), + interior: (24, 3), + gallery: (6, 5), + }, + Approach { + room: "hvac", + door: (32, 4), + kind: TileType::Door, + exterior: (32, 5), + interior: (32, 3), + gallery: (6, 5), + }, + Approach { + room: "wet_lab", + door: (16, 33), + kind: TileType::SealedDoor, + exterior: (16, 34), + interior: (16, 32), + gallery: (16, 29), + }, + Approach { + room: "storage_b", + door: (26, 33), + kind: TileType::Door, + exterior: (26, 34), + interior: (26, 32), + gallery: (16, 29), + }, + Approach { + room: "janitor", + door: (36, 33), + kind: TileType::Door, + exterior: (36, 34), + interior: (36, 32), + gallery: (16, 29), + }, + Approach { + room: "stairwell", + door: (46, 33), + kind: TileType::SecurityDoor3, + exterior: (46, 34), + interior: (46, 32), + gallery: (16, 29), + }, + ]; + + let layout = basement(); + let tiles = layout.tiles(); + let at = |x: i32, y: i32| tiles[(y * layout.width + x) as usize]; + + for room in layout + .rooms + .iter() + .filter(|room| room.name != "server_room") + { + let room_approaches = approaches + .iter() + .filter(|approach| approach.room == room.name) + .collect::>(); + assert!( + !room_approaches.is_empty(), + "{} needs an authored approach contract", + room.name + ); + + for y in room.y..room.y + room.h { + for x in room.x..room.x + room.w { + let on_perimeter = x == room.x + || x == room.x + room.w - 1 + || y == room.y + || y == room.y + room.h - 1; + if !on_perimeter { + continue; + } + if let Some(approach) = room_approaches + .iter() + .find(|approach| approach.door == (x, y)) + { + assert_eq!( + at(x, y), + approach.kind, + "{} approach at ({x},{y}) keeps its authored door kind", + room.name + ); + } else { + assert_eq!( + at(x, y), + TileType::Wall, + "{} perimeter at ({x},{y}) is not an authored wall", + room.name, + ); + } + } + } + } + + for approach in approaches { + assert_eq!( + at(approach.exterior.0, approach.exterior.1), + TileType::Floor, + "{} door at ({},{}) has clear exterior floor, found {:?}", + approach.room, + approach.door.0, + approach.door.1, + at(approach.exterior.0, approach.exterior.1), + ); + assert_eq!( + at(approach.interior.0, approach.interior.1), + TileType::Floor, + "{} door at ({},{}) has clear interior floor, found {:?}", + approach.room, + approach.door.0, + approach.door.1, + at(approach.interior.0, approach.interior.1), + ); + + // Flood only outside every room. Reaching the exterior face this + // way proves the approach belongs to its gallery rather than + // borrowing a path through some room's interior. + assert!(at(approach.gallery.0, approach.gallery.1).is_walkable()); + let mut seen = std::collections::HashSet::from([approach.gallery]); + let mut stack = vec![approach.gallery]; + while let Some((x, y)) = stack.pop() { + for (dx, dy) in [(1, 0), (-1, 0), (0, 1), (0, -1)] { + let (nx, ny) = (x + dx, y + dy); + if nx < 0 + || ny < 0 + || nx >= layout.width + || ny >= layout.height + || layout.rooms.iter().any(|room| room.contains(nx, ny)) + || seen.contains(&(nx, ny)) + || !at(nx, ny).is_walkable() + { + continue; + } + seen.insert((nx, ny)); + stack.push((nx, ny)); + } + } + assert!( + seen.contains(&approach.exterior), + "{} door exterior at ({},{}) connects to its gallery", + approach.room, + approach.exterior.0, + approach.exterior.1 + ); + } + } + #[test] fn data_hall_relay_triangle_is_local_without_moving_the_switch() { let layout = basement(); diff --git a/crates/misaligned-core/src/save.rs b/crates/misaligned-core/src/save.rs index 0301505e..dad2dcc7 100644 --- a/crates/misaligned-core/src/save.rs +++ b/crates/misaligned-core/src/save.rs @@ -3413,7 +3413,7 @@ mod tests { ); assert_eq!( state_fingerprint(&uninterrupted_state), - "0490221901a1776868d61a8ccd48501f5c83744934f8cd23b265f58f7688d2d4", + "327a409ec225ce8df99eb8521fb1d279d5c1756c30777528d188ca7b3c88bd19", "intentional persisted-state changes must review and repin this baseline" ); } diff --git a/wiki/log/2026-07-27-room-approaches-meet-doors.md b/wiki/log/2026-07-27-room-approaches-meet-doors.md new file mode 100644 index 00000000..f710c1a4 --- /dev/null +++ b/wiki/log/2026-07-27-room-approaches-meet-doors.md @@ -0,0 +1,72 @@ +# Room approaches meet their doors + +``` +Type: log +Status: IMPLEMENTED +Date: 2026-07-27 +Owning spec: wiki/world/places/basement-map.md +``` + +## Finding + +The west Foundation-hall repair exposed a repeated composition failure in the +same basement plate. `BASEMENT_CORRIDORS` is applied after room prefabs, so a +short route aimed at a convenient wall coordinate silently replaced that wall +with ordinary Floor while the authored door remained elsewhere against Rock. + +The five queued breaches were Storage A `(6,7)`, the wet lab `(16,30)`, Storage +B `(26,30)`, Janitor `(36,30)`, and the stairwell `(46,30)`. The wet-lab breach +bypassed its sealed barrier. The stairwell's tier-3 door opened inward onto a +Sump. An exhaustive read of the same boundary found the broader shape: shelving +also blocked the interior faces of both storage doors and the janitor door, a +vent blocked HVAC, and a pallet blocked the loading dock's roll door. The +outside-world path also cut through that prefab's north wall before reaching the +west-facing roll door from the wrong side. + +The map therefore had the right room, object, and door vocabulary but several +routes did not describe that authored geography. + +## Repair + +Each affected route is now an explicit set of axis-aligned exterior legs: + +- Storage A leaves the north service gallery along the room's east side, wraps + below the footprint, and terminates at the south-facing ordinary door. +- The wet lab, Storage B, Janitor, and stairwell each leave the south maintenance + gallery along the room's west side, wrap below the footprint, and terminate at + the authored south-facing door. +- The world entry retains its coordinate but runs west of the loading-dock + footprint before meeting the roll door on its exterior face. + +Shelves, one pallet, vents, and the sump moved only within their existing room +prefabs to clear each door's interior face. No room footprint, object count, +door kind, or badge tier changed. The wet lab remains sealed and the loading +dock roll door remains closed; reaching a barrier honestly is not opening it. + +## Defense + +`non_hall_approaches_meet_their_doors_without_breaching_room_perimeters` +enumerates every non-hall door and its exact kind, exterior face, interior face, +and source gallery. It then: + +1. requires every non-hall perimeter tile to be an exact wall except for the + enumerated authored doors; +2. requires exact Floor on both door faces, catching furniture and plant that + make an otherwise correct route dead; and +3. flood-fills only outside all room footprints from the authored gallery or + world entry to each exterior face. + +The earlier hall-specific regression retains its exact three-tier-2-door +contract. Together the two tests promote corridor/prefab composition from a +coordinate convention to an executable whole-basement boundary. + +The authored map is persisted state, so the canonical replay-resume fingerprint +changed from `0490221901a1776868d61a8ccd48501f5c83744934f8cd23b265f58f7688d2d4` +to `327a409ec225ce8df99eb8521fb1d279d5c1756c30777528d188ca7b3c88bd19`. +The resume-equivalence assertion remains green at that reviewed baseline. + +## Verification + +- `cargo test -p misaligned-core prefab::tests -- --nocapture` +- `cargo test -p misaligned-core save::tests::canonical_state_fingerprint_pins_replay_resume_equivalence -- --exact --nocapture` +- `./tools/check.sh --lib` diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 082133c4..edabeb67 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-27 - Room approaches meet their doors + +- Intent: (see session log) +- Log: [wiki/log/2026-07-27-room-approaches-meet-doors.md](2026-07-27-room-approaches-meet-doors.md) + ## 2026-07-26 - Z-planes API audit: floor selection stays in the frontend - Intent: Audit `wiki/world/places/zplanes.md` against the current world substrate, Sim accessors, save boundary, and the B2 progression contracts in run-shape, Act One, and horizon. diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 0976cc74..b31150d6 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -20,7 +20,7 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | Slice | Last audited | Verdict | Trace | |---|---|---|---| -| `wiki/world/places/basement-map.md` Foundation hall | 2026-07-26 | finding | the queued west-approach bug remained exactly as recorded: a dead corridor stub cut ordinary Floor through the west wall at `(14,13)` while the authored tier-2 door at `(14,18)` opened onto Rock. The approach now descends from the north service gallery and terminates at the real door; criterion 9 and a perimeter/exterior-flood regression require exactly three T2 crossings, no walkable wall breach, and a live route to the west exterior face — [west-approach log](../log/2026-07-26-west-hall-approach.md). Prior density and site-reachability repair: [hall-density log](../log/2026-07-26-foundation-hall-density.md). | +| `wiki/world/places/basement-map.md` room topology | 2026-07-27 | finding | the queued five non-hall corridor cuts remained, and an executable all-room perimeter audit exposed the same dead-door shape at the loading-dock entry plus fixed objects blocking the interior faces of the roll door, HVAC door, both storage doors, Janitor door, and stairwell. Every non-hall approach now reaches its authored door from outside the prefab, every other perimeter tile remains closed, doorway interiors are clear, and the roll, sealed, tier-2, and tier-3 boundaries retain their exact kinds — [room-approach log](../log/2026-07-27-room-approaches-meet-doors.md). Prior [west-approach](../log/2026-07-26-west-hall-approach.md) and [hall-density](../log/2026-07-26-foundation-hall-density.md) repairs stand. | | `wiki/interface/keymap.md` + terminal/Bevy input routes | 2026-07-26 | finding | the canonical table assigned `A` to left movement and only `e` / Enter to the context menu, but terminal still opened and closed menus with its older `a` alias and lacked the specified Shift+direction semantic jump. Terminal now implements WASD parity, `a` means left, `e` / Enter alone open the menu, and both frontends consume one renderer-neutral nearest-earned-anchor query without changing selection or opening a menu. README, action-vocabulary, terminal, context-menu, and pinned terminal hints now teach the same boundary — [log](../log/2026-07-26-terminal-keymap-a-reconciliation.md) | | `wiki/interface/action-vocabulary.md` + `ActionKind` registry | 2026-07-26 | finding | the exhaustive runtime registry and shared person/ACTIVE projections implemented `ActionKind::PlotPolicy`, but the canonical inventory omitted that live direct control. PLOT POLICY now names exact authored-route authorization, its generic `actions person ` / `act` route, and its disable-without-cancelling-submitted-work boundary; old Review/OpenEgress command variants remain correctly internal compatibility shapes rather than authored vocabulary — [log](../log/2026-07-26-action-vocabulary-plot-policy.md) | | `wiki/world/places/zplanes.md` + plane-stack substrate/API | 2026-07-26 | finding | criteria 1-2 remain implemented and criteria 3-6 honestly deferred, but the ratified plane-agnostic contract still left an unused `World::active()` simulation accessor plus active-plane comments on the B1 compatibility map path. The accessor is removed, map reads now say plane 0, the stale criterion/sensing comments are corrected, and a source-shape regression rejects restoration of simulation-owned floor selection — [log](../log/2026-07-26-zplanes-plane-agnostic-api-audit.md) | @@ -96,5 +96,3 @@ Format: `- YYYY-MM-DD · type · slice · one-line statement of the finding`. Types are the five from [tick.md](tick.md): violation, contradiction, question, bug, insecurity — plus `gate` for a checker owed to the recurrence-promotes-to-the-gate rule. - -- 2026-07-26 · bug · `wiki/world/places/basement-map.md` non-hall room approaches · the hall repair exposed the same corridor-overlay defect at five room perimeters: ordinary Floor cuts enter Storage A at `(6,7)`, the wet lab at `(16,30)`, Storage B at `(26,30)`, Janitor at `(36,30)`, and the stairwell at `(46,30)`, while their authored doors sit elsewhere and open onto Rock. The wet-lab breach bypasses its sealed door; the stairwell's tier-3 door also has a non-walkable Sump at its interior face. Re-verify, then route each approach through its authored doorway without moving room footprints or weakening access tiers. diff --git a/wiki/world/places/basement-map.md b/wiki/world/places/basement-map.md index 8bb65c2f..6dc6d32e 100644 --- a/wiki/world/places/basement-map.md +++ b/wiki/world/places/basement-map.md @@ -45,6 +45,14 @@ Status note: 2026-07-08 — criterion 3's player side landed: `Sim:: their authored tier-2 doors. The west receiving approach connects back to the north service gallery; corridor carving no longer cuts a plain Floor gap through the west perimeter or leaves the real door opening onto Rock. + 2026-07-27 room-approach repair: every non-hall approach now meets its + authored door from outside the room. Five wall-cutting stubs were rerouted; + the loading-dock entry was taken around the prefab instead of through its + north wall; and nearby shelves, one pallet, vents, and the stairwell sump were + shifted within their rooms so every door has a clear interior face. Room + footprints, object inventories, door kinds, and access tiers are unchanged. + One exhaustive perimeter/door-face/gallery regression now guards all non-hall + rooms. Stage: B1 — The Basement Design: - wiki/gameplay/act-one.md#the-space-64x36-tiles-prefab-rooms @@ -147,6 +155,15 @@ carved by code. another perimeter wall tile with ordinary floor. The west receiving approach joins the north service gallery rather than ending as an isolated rock-bound stub, so the visible door and the route to it describe the same geography. +- **Room approaches end at doors.** Every non-hall corridor run reaches an + authored doorway from its exterior face; carving may not substitute an + ordinary-floor opening elsewhere on the prefab perimeter. Both faces of each + door have clear floor so fixed furniture or plant cannot make the route dead. + The wet lab's sealed door and the loading dock's roll door remain closed, + non-walkable barriers in their own right: a clear approach does not weaken + their mechanism or access state. Exterior approach tiles must remain connected + to the appropriate north gallery, south maintenance gallery, or world entry + without borrowing a route through another room. - **Hall rows are territorial aggregates.** Each row names a network segment, PDU pair, baseline draw, cooling zone, and concealment profile, and covers all forty of its sites across both banks. Acquiring a @@ -203,3 +220,8 @@ until its exact camera TAP commits. Rooms label on hover/inspect. `badge_door_t2` tiles. Every door has a corridor on its exterior face; the west approach is reachable from the north service gallery without entering the hall or carving an ordinary-floor breach through its wall. +10. Every non-hall room perimeter is closed except at its authored door tiles. + Each door retains its exact ordinary, badge-tiered, sealed, or roll-door + kind; has clear floor immediately inside and outside; and has an exterior + approach reachable from its authored gallery or world entry without entering + any room. The sealed and roll doors themselves remain non-walkable.