diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 41d3c556..3d6d76b6 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -31,7 +31,10 @@ use misaligned::actions::{ ActionCommand, ActionKind, Anchor, BuildRouteFamily, HumanMenuPage, HumanMenuRow, MenuRow, }; use misaligned::detection::{Band, SignatureKind}; -use misaligned::hall::{HALL_ROWS, RackSite}; +use misaligned::hall::{ + HALL_BANK_EAST, HALL_BANK_WEST, HALL_COLD_AISLES, HALL_CROSS_AISLE, HALL_ROWS, RackSite, + hall_site_columns, is_hall_site_column, +}; use misaligned::intents::BuildGhostGeometry; use misaligned::operations_projection::{ ObjectState, OperationsTarget, OperationsView, PressureLevel, diff --git a/crates/misaligned-bevy/src/material_view.rs b/crates/misaligned-bevy/src/material_view.rs index 345738a6..3a104184 100644 --- a/crates/misaligned-bevy/src/material_view.rs +++ b/crates/misaligned-bevy/src/material_view.rs @@ -400,7 +400,9 @@ pub(super) fn grid_to_world_3d(x: i32, y: i32, h: f32) -> Vec3 { /// Authored service lights in the three cold aisles between paired rack /// rows. Their repeated four-tile rhythm makes the six-row hall legible as a /// maintained institution without outlining every tile or spending amber on -/// decoration. Coordinates belong to the fixed B1 basement plate. +/// decoration. The rhythm now runs the full length of the banks it serves, so a +/// cold aisle reads as one lit corridor between two walls of metal rather than a +/// short lit stretch in the middle of a dark room. pub(super) const FOUNDATION_FLOOR_LIGHTS: &[(i32, i32)] = &[ (20, 10), (24, 10), @@ -408,20 +410,39 @@ pub(super) const FOUNDATION_FLOOR_LIGHTS: &[(i32, i32)] = &[ (32, 10), (36, 10), (40, 10), + (44, 10), + (48, 10), + (52, 10), + (56, 10), (20, 16), (24, 16), (28, 16), (32, 16), (36, 16), (40, 16), + (44, 16), + (48, 16), + (52, 16), + (56, 16), (20, 22), (24, 22), (28, 22), (32, 22), (36, 22), (40, 22), + (44, 22), + (48, 22), + (52, 22), + (56, 22), ]; +/// The full length a cold-aisle trench runs: from the west bank's first site +/// column to the east bank's last, crossing the cross-aisle between them. The +/// trench serves the racks on both sides of the aisle, so it spans them. +fn trench_span() -> std::ops::RangeInclusive { + *HALL_BANK_WEST.start()..=*HALL_BANK_EAST.end() +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(super) struct DataHallInfrastructureSpec { pub(super) x: i32, @@ -429,28 +450,33 @@ pub(super) struct DataHallInfrastructureSpec { pub(super) kind: InstitutionPropKind, } -/// Presentation geometry authored onto the existing sixty-site hall plate. -/// Plinths bind each rack into its row; high trays explain the service spine; -/// dark floor trenches organize the three cold aisles. A segment never spans -/// another tile, so Seen provenance is exact at the perception boundary. +/// Presentation geometry authored onto the 240-site hall plate. Plinths bind +/// each rack into its row; high trays explain the service spine; a cable bridge +/// carries each row's tray across the cross-aisle that splits its two banks; +/// dark floor trenches organize the three cold aisles. Geometry comes from +/// `hall`, so the service layer cannot drift off the authored banks. A segment +/// never spans another tile, so Seen provenance is exact at the perception +/// boundary. pub(super) fn data_hall_infrastructure_specs(sim: &Sim) -> Vec { - let mut specs = Vec::with_capacity(239); + let mut specs = Vec::with_capacity(620); for row in HALL_ROWS { - for x in (20..=38).step_by(2) { + for x in hall_site_columns() { specs.push(DataHallInfrastructureSpec { x, y: row.y, kind: InstitutionPropKind::CableTray, }); } - for x in (20..=38).step_by(2) { + for x in hall_site_columns() { specs.push(DataHallInfrastructureSpec { x, y: row.y, kind: InstitutionPropKind::RackPlinth, }); } - for x in (21..=37).step_by(2) { + // Banks are contiguous, so the only span a bridge has to cross is the + // cross-aisle between them. + for x in HALL_CROSS_AISLE { specs.push(DataHallInfrastructureSpec { x, y: row.y, @@ -458,8 +484,8 @@ pub(super) fn data_hall_infrastructure_specs(sim: &Sim) -> Vec Vec Vec = specs.iter().copied().collect(); - assert_eq!(specs.len(), 239); + // 6 rows x (40 trays + 40 plinths + 2 cross-aisle bridges) + 3 aisles x + // 42 trenches + one service drop + one maintenance bay. + assert_eq!(specs.len(), 620); assert_eq!(unique.len(), specs.len()); for row in HALL_ROWS { @@ -2984,30 +3015,38 @@ mod data_hall_infrastructure_tests { .iter() .filter(|s| s.y == row.y && s.kind == InstitutionPropKind::RackPlinth) .count(), - 10 + HALL_SITES_PER_ROW ); assert_eq!( specs .iter() .filter(|s| s.y == row.y && s.kind == InstitutionPropKind::CableBridge) .count(), - 9 + HALL_CROSS_AISLE.len(), + "a contiguous bank needs a bridge only across the cross-aisle" ); assert_eq!( specs .iter() .filter(|s| s.y == row.y && s.kind == InstitutionPropKind::CableTray) .count(), - 10 + HALL_SITES_PER_ROW + ); + // Every plinth lands on an authored site column, never on a walkway. + assert!( + specs + .iter() + .filter(|s| s.y == row.y && s.kind == InstitutionPropKind::RackPlinth) + .all(|s| is_hall_site_column(s.x)) ); } - for y in [10, 16, 22] { + for y in HALL_COLD_AISLES { assert_eq!( specs .iter() .filter(|s| s.y == y && s.kind == InstitutionPropKind::FloorTrench) .count(), - 21 + 42 ); } let drops: Vec<_> = specs @@ -3324,8 +3363,10 @@ mod flat_materials { assert!( FOUNDATION_FLOOR_LIGHTS .iter() - .all(|(x, y)| { (20..=40).contains(x) && [10, 16, 22].contains(y) && x % 4 == 0 }) + .all(|(x, y)| { (20..=56).contains(x) && [10, 16, 22].contains(y) && x % 4 == 0 }) ); + // The rhythm runs the length of the banks, not just their middle. + assert_eq!(FOUNDATION_FLOOR_LIGHTS.len(), 30); let sim = Sim::new(); for &(x, y) in FOUNDATION_FLOOR_LIGHTS { assert_eq!( diff --git a/crates/misaligned-core/src/hall.rs b/crates/misaligned-core/src/hall.rs index 1771d4d1..2a9a4572 100644 --- a/crates/misaligned-core/src/hall.rs +++ b/crates/misaligned-core/src/hall.rs @@ -1,12 +1,14 @@ //! Foundation data-hall aggregate state. //! -//! The opening farm is authored as sixty rack sites, but it is not sixty -//! menus. Rows are the first aggregate Resource-sources: physical sight can -//! distinguish chassis state while network knowledge exposes workload and -//! segment identity. Phase 3 records control of shared infrastructure without -//! stealing the foreign compute inside it (that remains the later Phase 4). +//! The opening farm is authored as two hundred and forty rack sites, but it is +//! not two hundred and forty menus. Rows are the first aggregate +//! Resource-sources: physical sight can distinguish chassis state while network +//! knowledge exposes workload and segment identity. Phase 3 records control of +//! shared infrastructure without stealing the foreign compute inside it (that +//! remains the later Phase 4). use std::collections::{BTreeMap, BTreeSet}; +use std::ops::RangeInclusive; use serde::{Deserialize, Serialize}; @@ -138,11 +140,44 @@ pub const HALL_ROWS: [HallRowSpec; 6] = [ }, ]; +// ─── Hall floor geometry (basement-map.md criterion 6) ────────────────────── +// +// A row is two contiguous banks of twenty chassis split by a cross-aisle, so +// it reads as a wall of metal rather than a line of lonely towers. This is the +// single authority for which columns are rack sites: the prefab plate, the +// aggregate readout, and the frontends' authored service layer all derive from +// it rather than repeating a literal range. + +/// West bank: twenty adjacent site columns. +pub const HALL_BANK_WEST: RangeInclusive = 17..=36; +/// East bank: twenty adjacent site columns. +pub const HALL_BANK_EAST: RangeInclusive = 39..=58; +/// The cross-aisle columns between the banks. Not sites — this is the walkway +/// a technician crosses to reach the far bank, and the span the overhead cable +/// bridge carries the row's service tray across. +pub const HALL_CROSS_AISLE: [i32; 2] = [37, 38]; +/// Cold-aisle rows between each paired pair of rack rows. +pub const HALL_COLD_AISLES: [i32; 3] = [10, 16, 22]; +/// Rack sites in one row. +pub const HALL_SITES_PER_ROW: usize = 40; +/// Authored rack sites in the whole hall. +pub const HALL_SITE_COUNT: usize = HALL_SITES_PER_ROW * HALL_ROWS.len(); + +/// Every site column of a hall row, west bank then east bank, ascending. +pub fn hall_site_columns() -> impl Iterator + Clone { + HALL_BANK_WEST.chain(HALL_BANK_EAST) +} + +/// Whether `x` is a rack-site column. Walkways, the cross-aisle, and the +/// row-end cable runs are deliberately not sites. +pub fn is_hall_site_column(x: i32) -> bool { + HALL_BANK_WEST.contains(&x) || HALL_BANK_EAST.contains(&x) +} + pub fn row_at(x: i32, y: i32) -> Option { - // Rack sites run from x=20 through x=38 at even coordinates. Cable-run - // endpoints are not members of the row Resource-source. - (20..=38) - .contains(&x) + // Cable-run endpoints, walkways, and the cross-aisle are not members of the + // row Resource-source. + is_hall_site_column(x) .then(|| HALL_ROWS.iter().find(|row| row.y == y).map(|row| row.id)) .flatten() } diff --git a/crates/misaligned-core/src/prefab.rs b/crates/misaligned-core/src/prefab.rs index d69dc147..a02ce049 100644 --- a/crates/misaligned-core/src/prefab.rs +++ b/crates/misaligned-core/src/prefab.rs @@ -174,38 +174,50 @@ impl Layout { // The Act One plate, as prefabs. Rooms are stamped, then corridors // join them. Rack 3 (the 'C' core bay) sits in the server room. -/// The Foundation data hall. Sixty authored rack positions form six -/// hot/cold-aisle rows: 51 live Foundation racks, five dead chassis, Rack 3, -/// and three empty pilot allocations. Rack 3 is buried mid-row rather than -/// staged at the room's head. The environmental monitor and a local patch -/// relay sit several pitches away in the same hall, forming a legible local -/// network triangle before the route continues to the closet switch. `f` and -/// `c` are physical chassis; `R` is empty floor allocation and must never -/// render a rack. +/// The Foundation data hall. Two hundred and forty authored rack positions form +/// six hot/cold-aisle rows: 221 live Foundation racks, fifteen dead chassis, +/// Rack 3, and three empty pilot allocations. Each row is two contiguous banks +/// of twenty chassis split by a cross-aisle, so a row reads as a wall of +/// institutional metal rather than a line of lonely towers, and the hall fills +/// its own floor instead of clustering in one corner of it +/// (`hall::HALL_BANK_WEST` / `HALL_BANK_EAST` are the geometry authority). +/// +/// Rack 3 is buried mid-bank rather than staged at the room's head, flanked by +/// two of the three empty pilot allocations so the attention-close frame still +/// resolves it as one machine. The environmental monitor and a local patch +/// relay sit several pitches away on the walkway rows, forming a legible local +/// network triangle before the route continues to the closet switch. +/// +/// `f` and `c` are physical chassis; `R` is empty floor allocation and must +/// never render a rack. The `Y` cable run caps the west end of each rack row +/// only: `TileType::CableRun` is not walkable, so a full-height run would sever +/// the hall into unreachable strips — which is what the pre-density plate did at +/// x=41, leaving the whole east half of the room stranded and empty. Every +/// walkway row and both cross-aisle columns stay clear. const SERVER_ROOM: Prefab = Prefab { name: "server_room", rows: &[ "######################2#######################", - "#..........................Y.................#", - "#...Y.f.f.c.f.f.f.f.f.f.f.YY.................#", - "#..........................Y.................#", - "#...Y.f.f.f.f.f.f.f.c.f.f.YY.................#", - "#..............E...........Y.................#", - "#..........................Y.................#", - "#...........A..............Y.................#", - "#...Y.f.f.f.R.C.R.R.f.f.f.YY.................#", - "#..........................Y.................#", - "#...Y.f.c.f.f.f.f.f.f.f.f.YY.................#", - "2..........................Y.................#", - "#..........................Y.................#", - "#..........................Y.................#", - "#...Y.f.f.f.f.f.f.f.f.c.f.YY.................#", - "#..........................Y.................#", - "#...Y.f.f.f.f.f.c.f.f.f.f.YY.................#", - "#..........................Y.................#", - "#..........................Y.................#", - "#..........................Y.................#", - "#..........................Y.................#", + "#............................................#", + "#.Yffffcfffffffffffcfff..ffffffffcfffffffffff#", + "#............................................#", + "#.Yffffffffcfffffffffff..ffffffffffffffcfffff#", + "#..............E.............................#", + "#............................................#", + "#...........A................................#", + "#.YffffffcfffRCRfffffff..fffffRffffffcfffffff#", + "#............................................#", + "#.Yffcfffffffffffffffcf..ffffffcfffffffffffff#", + "2............................................#", + "#............................................#", + "#............................................#", + "#.Yffffffffffffcfffffff..ffffffffffffffffcfff#", + "#............................................#", + "#.Ycfffffffffffffffffff..ffcfffffffffffffffcf#", + "#............................................#", + "#............................................#", + "#............................................#", + "#............................................#", "######################2#######################", ], }; @@ -556,7 +568,7 @@ mod tests { } #[test] - fn data_hall_has_sixty_explicit_rack_sites() { + fn data_hall_has_two_hundred_forty_explicit_rack_sites() { let tiles = basement().tiles(); let sites = tiles .iter() @@ -567,12 +579,125 @@ mod tests { ) }) .count(); - assert_eq!(sites, 60); + assert_eq!(sites, crate::hall::HALL_SITE_COUNT); + assert_eq!(sites, 240); assert_eq!(tiles.iter().filter(|&&t| t == TileType::Rack).count(), 3); assert_eq!( tiles.iter().filter(|&&t| t == TileType::DeadRack).count(), - 5 + 15 ); + assert_eq!( + tiles + .iter() + .filter(|&&t| t == TileType::ForeignRack) + .count(), + 221 + ); + } + + #[test] + fn authored_plate_matches_the_hall_site_geometry() { + // hall.rs owns which columns are sites; the plate must agree exactly, or + // the aggregate row readout counts tiles the author never placed. + let layout = basement(); + let tiles = layout.tiles(); + let at = |x: i32, y: i32| tiles[(y * layout.width + x) as usize]; + let is_site = |t: TileType| { + matches!( + t, + TileType::Core | TileType::Rack | TileType::ForeignRack | TileType::DeadRack + ) + }; + let hall = layout + .rooms + .iter() + .find(|room| room.name == "server_room") + .unwrap(); + + for row in crate::hall::HALL_ROWS { + let sites = crate::hall::hall_site_columns() + .filter(|&x| is_site(at(x, row.y))) + .count(); + assert_eq!( + sites, + crate::hall::HALL_SITES_PER_ROW, + "{} must be a full pair of banks", + row.id.name() + ); + } + // Nothing outside the authored banks is a site, and no site sits on a + // walkway, the cross-aisle, or a cold aisle. + for y in hall.y..hall.y + hall.h { + for x in hall.x..hall.x + hall.w { + if !is_site(at(x, y)) { + continue; + } + assert!( + crate::hall::is_hall_site_column(x), + "site at ({x}, {y}) is off the authored banks" + ); + assert!( + crate::hall::HALL_ROWS.iter().any(|row| row.y == y), + "site at ({x}, {y}) is not on an authored row" + ); + } + } + for x in crate::hall::HALL_CROSS_AISLE { + for y in crate::hall::HALL_ROWS.map(|row| row.y) { + assert_eq!( + at(x, y), + TileType::Floor, + "the cross-aisle stays walkable at ({x}, {y})" + ); + } + } + } + + #[test] + fn every_hall_site_is_reachable_from_the_hall_doors() { + // Density must not wall the hall off from itself. CableRun is not + // walkable, so a full-height service run would strand a whole bank — + // and a stranded site can never take a physical install. + let layout = basement(); + let tiles = layout.tiles(); + let at = |x: i32, y: i32| tiles[(y * layout.width + x) as usize]; + let hall = layout + .rooms + .iter() + .find(|room| room.name == "server_room") + .unwrap(); + + let start = (hall.x + hall.w / 2, hall.y + 1); + assert!( + at(start.0, start.1).is_walkable(), + "hall has an inner apron" + ); + let mut seen = std::collections::HashSet::from([start]); + let mut stack = vec![start]; + 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 !hall.contains(nx, ny) || seen.contains(&(nx, ny)) { + continue; + } + if at(nx, ny).is_walkable() { + seen.insert((nx, ny)); + stack.push((nx, ny)); + } + } + } + for row in crate::hall::HALL_ROWS { + for x in crate::hall::hall_site_columns() { + let touching = [(1, 0), (-1, 0), (0, 1), (0, -1)] + .iter() + .any(|(dx, dy)| seen.contains(&(x + dx, row.y + dy))); + assert!( + touching, + "site ({x}, {}) has no reachable working face", + row.y + ); + } + } } #[test] diff --git a/crates/misaligned-core/src/save.rs b/crates/misaligned-core/src/save.rs index 6fb0d713..935d0e32 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), - "79d1c5311c88abc0f78944e39b1a7c666593eea3953dcf011af214d369dd8182", + "60fd36187ea51e91baa8f1e2a668b7a109f26fc45773310c4067509e351c2404", "intentional persisted-state changes must review and repin this baseline" ); } diff --git a/crates/misaligned-core/src/sim/reach_build.rs b/crates/misaligned-core/src/sim/reach_build.rs index 87abfc82..008996ce 100644 --- a/crates/misaligned-core/src/sim/reach_build.rs +++ b/crates/misaligned-core/src/sim/reach_build.rs @@ -8,7 +8,8 @@ use crate::account::{AccountId, FlowChannel}; use crate::actions::Anchor; use crate::detection::{Signature, SignatureKind}; use crate::hall::{ - HallRowId, HallRowReadout, RackSite, SegmentRequirement, row_at as hall_row_at, row_spec, + HallRowId, HallRowReadout, RackSite, SegmentRequirement, hall_site_columns, + row_at as hall_row_at, row_spec, }; use crate::intents::{ BuildActuator, BuildGhostGeometry, BuildIntent, BuildIntentProjection, BuildRecipeKind, @@ -78,7 +79,7 @@ impl Sim { progress: self.hall_control.progress(row), concealment_ready: false, }; - for x in (20..=38).step_by(2) { + for x in hall_site_columns() { match self.rack_site_at(x, spec.y) { Some(RackSite::OwnedMachine { .. }) => readout.owned += 1, Some(RackSite::Commissionable) => readout.commissionable += 1, diff --git a/crates/misaligned-core/src/sim/tests/reach_build.rs b/crates/misaligned-core/src/sim/tests/reach_build.rs index 26385dee..cf532d62 100644 --- a/crates/misaligned-core/src/sim/tests/reach_build.rs +++ b/crates/misaligned-core/src/sim/tests/reach_build.rs @@ -2710,13 +2710,13 @@ fn data_hall_rows_expose_foreign_capacity_without_granting_it() { let sim = Sim::new(); let sites: Vec = crate::hall::HALL_ROWS .iter() - .flat_map(|row| { - (20..=38) - .step_by(2) - .filter_map(|x| sim.rack_site_at(x, row.y)) - }) + .flat_map(|row| crate::hall::hall_site_columns().filter_map(|x| sim.rack_site_at(x, row.y))) .collect(); - assert_eq!(sites.len(), 60); + assert_eq!(sites.len(), crate::hall::HALL_SITE_COUNT); + assert_eq!(sites.len(), 240); + // The player's side of the hall stays absolute, not scaled: density is + // Foundation pressure, and owning one site of 240 reads as smaller than + // owning one of sixty. assert_eq!( sites .iter() @@ -2736,14 +2736,14 @@ fn data_hall_rows_expose_foreign_capacity_without_granting_it() { .iter() .filter(|site| matches!(site, RackSite::Foreign { .. })) .count(), - 51 + 221 ); assert_eq!( sites .iter() .filter(|site| matches!(site, RackSite::Dead)) .count(), - 5 + 15 ); assert_eq!( sim.compute.machines.len(), @@ -2755,7 +2755,7 @@ fn data_hall_rows_expose_foreign_capacity_without_granting_it() { .iter() .map(|row| sim.hall_row_readout(*row).foreign_capacity) .sum::(), - 5_100, + 22_100, "aggregate readout makes nearby unavailable capacity legible" ); } diff --git a/wiki/gameplay/act-one.md b/wiki/gameplay/act-one.md index 288169ce..2937258d 100644 --- a/wiki/gameplay/act-one.md +++ b/wiki/gameplay/act-one.md @@ -68,11 +68,13 @@ get them to do things** — and end with a way out of the basement. = standard door ## badge-locked (tier 3) [..] fixed object ``` -- **Foundation data hall** — Rack 3 (your core) buried mid-row among - fifty-one live Foundation racks, five dead chassis, and three empty pilot - allocations (growth); six independently controlled row segments, UPS, - environmental monitor (first camera candidate). Badge tier 2. The nearby - 5,100 raw capacity is pressure and temptation, not starting compute. +- **Foundation data hall** — Rack 3 (your core) buried mid-bank among + 221 live Foundation racks, fifteen dead chassis, and three empty pilot + allocations (growth); six independently controlled row segments of forty sites + each, UPS, environmental monitor (first camera candidate). Badge tier 2. Rows + are contiguous walls of institutional metal, so the room reads as a facility + you are hiding inside rather than a vignette. The nearby + 22,100 raw capacity is pressure and temptation, not starting compute. - **Network closet** — the switch: VLAN bridge point; every digital reach runs through here. Badge tier 2. - **Electrical room** — breaker panels: the basement power budget; extra diff --git a/wiki/interface/clinical-frame.md b/wiki/interface/clinical-frame.md index 88bda760..7c266319 100644 --- a/wiki/interface/clinical-frame.md +++ b/wiki/interface/clinical-frame.md @@ -21,8 +21,9 @@ Status note: IMPLEMENTED. Current state: - **Semantic material zoom.** Close lowers the oblique toward machine/floor, far raises toward facility survey; monotonic distance, no free orbit, bounded 0.7-5.2. - - **The Foundation data hall.** Sixty rack plinths, sixty rear service trays, - and sixty-three cold-aisle trenches bind the complete hall under tile-local + - **The Foundation data hall.** A rack plinth and rear service tray per site + (240 each), a cable bridge on each of a row's two cross-aisle columns, and + 126 cold-aisle trenches bind the complete hall under tile-local Seen gates; one sim Thought move is one material slug; camera-seen people carrying work gain a service cart/lamp in REAL and a work mark in DIGITAL. Exposure's particulate renderer is replaced by carrier-local custody @@ -453,7 +454,7 @@ and action surfaces. ownership, selection, work, and threat facts; the terminal keeps semantic parity even though this exact composition is Bevy-specific. 9. The canonical data-hall capture reads as six maintained rack rows rather - than sixty unrelated monuments: low service plinths, cold-aisle thresholds, + than 240 unrelated monuments: low service plinths, cold-aisle thresholds, floor trenches, sparse overhead systems, and fixtures align into functional banks. Every added segment has a `Seen` tile provenance and disappears when that tile is not live sight. diff --git a/wiki/interface/computer-visual-language.md b/wiki/interface/computer-visual-language.md index 6d1f89b4..acd45313 100644 --- a/wiki/interface/computer-visual-language.md +++ b/wiki/interface/computer-visual-language.md @@ -20,7 +20,7 @@ Status note: IMPLEMENTED (ROADMAP #32). Current state: a presence beam (material-dark-frame.md). The fog audit asserts a visible chassis has sight and that exactly one core exists. - **The B1 chassis** is a tall, slender, asymmetric bone-white appliance with - thick black service structure. All sixty Foundation rack sites derive their + thick black service structure. All 240 Foundation rack sites derive their state (live/unpowered/dead/owned/core/empty) from one `RackSite` query across material, flat, terminal, and agent views. Future heterogeneous computers keep these signals without inheriting this exact shell. @@ -256,7 +256,7 @@ the fallback. 9. Light / medium / hard intensity changes the owned machine's cast-pool strength in the material render and appears as L/M/H text in the terminal; neither frontend invents intensity state. -10. A sixty-site Foundation-hall capture distinguishes live foreign, +10. A 240-site Foundation-hall capture distinguishes live foreign, unpowered foreign, dead, owned, core, and empty allocations without warm pixels on foreign/dead hardware. All frontends derive those states from the shared rack-site query; the material renderer pools repeated shell diff --git a/wiki/interface/feel-floor.md b/wiki/interface/feel-floor.md index e70c560b..add971c6 100644 --- a/wiki/interface/feel-floor.md +++ b/wiki/interface/feel-floor.md @@ -159,9 +159,10 @@ light saying a server belongs here — not a labeled blueprint fact - No chassis until built; the pad is the promise. - Foreign empty slots you cannot feel stay dark (no intel, no pad). - In the Foundation hall this is a deliberately scarce authored set: three - pilot allocations, all near Rack 3. Adjacency to fifty-one live foreign - racks does not make them growable; five dead chassis require SALVAGE and - remain physical rack bodies rather than becoming floor pads. + pilot allocations, two of them flanking Rack 3. Adjacency to 221 live foreign + racks does not make them growable; fifteen dead chassis require SALVAGE and + remain physical rack bodies rather than becoming floor pads. The scarcity is + the point, and it sharpens as the hall gets denser. ### The build beam @@ -222,8 +223,10 @@ and not the material-frame diagram. draw (one graph, two zoom reads). 6. Terminal and agent surfaces expose the same feel-graph without inventing room geometry the material frame refused. -7. The sixty-site data hall exposes exactly three feel pads after the route +7. The 240-site data hall exposes exactly three feel pads after the route is earned. A foreign or dead rack never renders or inspects as an empty bay. + Density does not add pads: the hall's felt surface is the player's own + holding, not the institution's. 8. At close material framing a long felt edge is a single broken filament strongest near attention, tapered at its endpoints, and fully absent at distance. Following it with the cursor reveals successive local stretches; diff --git a/wiki/log/2026-07-26-foundation-hall-density.md b/wiki/log/2026-07-26-foundation-hall-density.md new file mode 100644 index 00000000..3fff5bfd --- /dev/null +++ b/wiki/log/2026-07-26-foundation-hall-density.md @@ -0,0 +1,92 @@ +# The Foundation hall fills its own floor + +``` +Type: log +``` + +## Intent + +Cameron asked for a much higher density of servers in the basement. The +sparseness was not an authoring shortfall of ambition, it was geometry: the hall +is 44x20 tiles of floor, yet all sixty rack sites sat in a block between x=20 and +x=38, and every second tile inside a row was empty. Half the authored room was +bare, and a row read as ten lonely towers instead of a run of cabinets. + +Two independent causes, fixed together: **fill the width** (rows now span the +room) and **drop the spacer** (chassis stand on adjacent tiles). Sixty sites +becomes 240 with no change to the room footprint, the six rows, or the +row-acquisition ladder. + +## What changed + +- `basement-map.md` — new 2026-07-26 status note; the hall's fixed-object row is + 240 sites in two contiguous twenty-chassis banks per row; new bullets for + density-as-pressure and the unwalkable-service-run rule; criterion 6 rewritten + to 240 sites and a single geometry authority; criterion 7 additionally requires + the empty allocations flanking Rack 3; **new criterion 8** requires every site + to keep a reachable working face. +- `hall.rs` is now the single authority for hall floor geometry + (`HALL_BANK_WEST`, `HALL_BANK_EAST`, `HALL_CROSS_AISLE`, `HALL_COLD_AISLES`, + `HALL_SITES_PER_ROW`, `HALL_SITE_COUNT`, `hall_site_columns`, + `is_hall_site_column`). The plate, the aggregate row readout, and the Bevy + service layer derive from it; the literal `20..=38` range is gone from all + four call sites that repeated it. +- `prefab.rs` — the `SERVER_ROOM` plate is re-authored: 221 foreign, fifteen + dead, Rack 3, three pilot allocations. Rack 3 and the two allocations flanking + it, the environmental monitor, and the patch relay keep their exact previous + coordinates, so the criterion-7 triangle is unchanged. +- `material_view.rs` — the authored service layer scales with the plate: a + plinth and tray per site, a cable bridge on each cross-aisle column (a + contiguous bank has no inner gap left to bridge), trenches spanning the banks, + and the aisle light rhythm running the full bank length. 239 props become 620. +- Dependent interface and vision pages that counted sixty are amended: + `clinical-frame.md`, `computer-visual-language.md`, `feel-floor.md`, + `scale.md`, `act-one.md`. +- `tools/test_corpus_engine.sh` — one `sed -i` without a backup suffix, which + BSD sed rejects. The docs gate could not complete on macOS before this, on a + clean tree as well as a dirty one. Fixed to the portable idiom already used + elsewhere in the same file. + +## The pathing bug this uncovered + +`CableRun` is not walkable, and the pre-change plate ran one full-height at +x=41. Every hall door sits west of it, so it sealed the room's whole east strip +off from the rest of the hall — which is exactly the half that was empty, so +nothing ever stood there to notice. Filling the room without spotting this would +have stranded about a hundred of the new sites behind it. Cable runs now cap row +ends only, every walkway row is a clear crossing, and criterion 8 plus a +flood-fill test hold the line. + +A second, unrelated artifact turned up in the same audit and is **not** fixed +here: the west corridor run carves to (14,13) while the authored west +`badge_door_t2` sits at (14,18), leaving a Floor gap in the tier-2 wall, an +unreachable door, and a dead corridor stub. A flood fill from the outside Entry +confirms the hall is still not enterable without passing a door, so this is a +plate inconsistency rather than a badge bypass. It is queued in the tick ledger. + +## Player-side counts are deliberately absolute + +One core, three pilot allocations, unchanged. Density is Foundation pressure, not +player capacity, so owning one site of 240 reads as smaller than owning one of +sixty. Aggregate foreign capacity rises from 5,100 to 22,100 — legible potential, +never player compute. + +## Verification + +- `cargo test --workspace` — all green (core 535 including two new geometry + tests, bevy 140, terminal 71). +- New `prefab.rs` tests: the plate agrees with `hall.rs` geometry per row and + rejects any site off the authored banks; a flood fill from inside the hall + proves all 240 sites keep a reachable working face. +- `save.rs` characterization fingerprint reviewed and repinned — the hall plate + is persisted state, so a content change legitimately moves it. Schema is + unchanged, so `SAVE_VERSION` stays 56; development saves written before this + change still carry the old sixty-site plate and should be discarded. +- `./tools/check.sh --docs` — **ALL CHECKS PASSED**. +- `./tools/check.sh --land` — see the landing commit. +- Observed runs under `tools/observed-run.sh` (real save directory verified + untouched both times): + - `hall-material` — fog audit OK, **620/620** authored service roots visible + and fog-gated, 60/60 floor fixtures, material pool 26 handles, all flat. + - `hall` (flat/DIGITAL) — six dense rows span the frame with the cross-aisle + legible and Rack 3 still resolving as one machine between its allocations. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 8b3f9a40..f9b2414d 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -46,6 +46,11 @@ add or amend a session log, then re-run the generator. - Intent: (see session log) - Log: [wiki/log/2026-07-26-interface-cover-evidence-credibility.md](2026-07-26-interface-cover-evidence-credibility.md) +## 2026-07-26 - The Foundation hall fills its own floor + +- Intent: Cameron asked for a much higher density of servers in the basement. The sparseness was not an authoring shortfall of ambition, it was geometry: the hall is 44x20 tiles of floor, yet all sixty rack sites sat in a block between x=20 and x=38, and every second tile inside a row w... +- Log: [wiki/log/2026-07-26-foundation-hall-density.md](2026-07-26-foundation-hall-density.md) + ## 2026-07-26 - The first Thought is a visible causal sequence - Intent: (see session log) diff --git a/wiki/log/decisions/2026-07-26.md b/wiki/log/decisions/2026-07-26.md index 1abe7652..ad8a469d 100644 --- a/wiki/log/decisions/2026-07-26.md +++ b/wiki/log/decisions/2026-07-26.md @@ -187,3 +187,46 @@ Owners: [operations-workspace.md](../../interface/operations-workspace.md) and - An unexplained TAP button whose consequence is visible only after commitment. Owner: [opening.md](../../world/story/opening.md). + +## The Foundation hall fills its own floor + +### DECIDED + +- The Foundation data hall holds **240 rack sites**, not sixty. Each of the six + rows is two contiguous banks of twenty chassis split by a walkable cross-aisle. + A row is a wall of institutional metal and the aisle does the separating, the + way a real hall is built. +- Density is **Foundation pressure, not player capacity**. The player's side + stays absolute at one core and three pilot allocations, so a denser hall makes + the player's holding read smaller. Growing the site count is always a content + change and never a capacity grant. +- The room footprint, the six rows, the row-acquisition ladder, the save schema, + and the coordinates of Rack 3, the environmental monitor, and the patch relay + are all unchanged. Rack 3 keeps an empty pilot allocation on each side so the + attention-close frame still resolves it as one machine. +- One module (`hall.rs`) owns hall floor geometry. The plate, the aggregate row + readout, and both frontends' authored service layer derive from it rather than + repeating a coordinate range. +- An unwalkable service run may never sever the hall. Every site keeps an + orthogonally adjacent walkable face reachable from the hall doors, because a + site nobody can stand at can never take a physical install. This is new + criterion 8, earned by finding that the old full-height cable run at x=41 had + sealed the hall's whole east half into an unreachable strip. + +### Rejected + +- Scaling the player's starting side with the hall (five dead chassis to fifteen + is authored maintenance history; three allocations staying three is the point). + Proportional growth would have cancelled the exact feeling the density buys. +- Enlarging the room to get density. The hall already spans the map east of + x=14; the sparseness was unused interior floor, so growing the footprint would + have added more emptiness rather than more institution. +- Adding a fourth row pair (eight rows, 320 sites). It fits the south floor, but + six rows are the authored aggregate and `HallRowId` has six variants that + persist in the save; a seventh and eighth row are a save-schema change, not a + content change. +- Keeping the every-other-tile spacer and only filling the width (126 sites). + It doubles the count without buying the wall-of-metal read that makes the room + feel like a facility. + +Owner: [basement-map.md](../../world/places/basement-map.md). diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index b82bd654..a74df4f1 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/world/places/basement-map.md` Foundation hall | 2026-07-26 | finding | Cameron asked for a denser hall; the plate held sixty sites clustered between x=20 and x=38 with every second row tile empty, leaving half the authored 44x20 room bare. The hall is now 240 sites in six rows of two contiguous twenty-chassis banks, with the player's side deliberately absolute (one core, three allocations) so density reads as Foundation pressure. Audit also found an unwalkable full-height cable run at x=41 that had sealed the hall's entire east half into a strip unreachable from the rest of the hall; cable runs now cap row ends and new criterion 8 plus a flood-fill test require every site to keep a reachable working face. `hall.rs` is now the single geometry authority for the plate, the row readout, and both frontends' service layer — [log](../log/2026-07-26-foundation-hall-density.md) | | `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) | @@ -97,3 +98,4 @@ question, bug, insecurity — plus `gate` for a checker owed to the recurrence-promotes-to-the-gate rule. - 2026-07-26 · violation · `wiki/mechanics/building.md` + focused foreign-rack UI · a focused seen Foundation Rack identifies itself as foreign/powered but offers no plain consequence saying it cannot yet be converted or where usable capacity comes from; Cameron read the live frame as a takeover target and had to ask how to act, despite Phase 4 being explicitly unimplemented +- 2026-07-26 · bug · `wiki/world/places/basement-map.md` west hall approach · the plate's west corridor run and its authored west door disagree. `BASEMENT_CORRIDORS` carves `(11,13)->(14,13)` and `carve_cell` overwrites Wall, so the data hall's tier-2 west wall has a plain Floor gap at (14,13), while the authored `badge_door_t2` sits at (14,18) with Rock outside it and the carved (11..14,13) stub connects to nothing. Verified by flood fill: the hall is still not enterable from the outside Entry without passing a door, so this is not a badge bypass — it is a wall gap, an unreachable door, and a dead corridor stub that should agree with each other. Pre-existing and untouched by the density work. diff --git a/wiki/vision/scale.md b/wiki/vision/scale.md index 45059e3e..976e71d5 100644 --- a/wiki/vision/scale.md +++ b/wiki/vision/scale.md @@ -70,8 +70,10 @@ define a deliberate stable world designation such as a machine's M-number; an implementation number does not become player language merely because it is stable. -The Foundation hall is the B1 proof of this boundary (adopted 2026-07-11): -sixty physical rack sites aggregate through six row readouts, while only +The Foundation hall is the B1 proof of this boundary (adopted 2026-07-11, +densified 2026-07-26): 240 physical rack sites aggregate through six row +readouts — the aggregate is what makes that count legible instead of +overwhelming, and it is why density costs no new player surface. Only owned `Machine` instances aggregate into the fleet. The row readout is derived from those same sites and machine states; it is not a second cache of truth. diff --git a/wiki/world/places/basement-map.md b/wiki/world/places/basement-map.md index d4a8eda1..b11262a6 100644 --- a/wiki/world/places/basement-map.md +++ b/wiki/world/places/basement-map.md @@ -30,6 +30,17 @@ Status note: 2026-07-08 — criterion 3's player side landed: `Sim:: route passes through that local fixture before continuing to the subnet switch, which remains in the network closet with its existing reach and badge semantics. + 2026-07-26 hall-density amendment: the hall now fills its own floor. Each of + the six rows is two contiguous banks of twenty chassis split by a walkable + cross-aisle, raising the plate from sixty sites to 240 (221 foreign, fifteen + dead, Rack 3, three pilot allocations). The room footprint, the six rows, the + row-acquisition ladder, and the player's starting side are all unchanged — + density is Foundation pressure, not player capacity. The pre-density plate + clustered every site between x=20 and x=38 and walled the east half off behind + a full-height cable run, so half the authored room was unreachable and empty; + cable runs now cap the row ends only and criterion 8 asserts every site keeps a + reachable working face. `hall.rs` owns the site geometry that the plate, the + aggregate readout, and the frontends' service layer all derive from. Stage: B1 — The Basement Design: - wiki/gameplay/act-one.md#the-space-64x36-tiles-prefab-rooms @@ -76,7 +87,7 @@ carved by code. | Room | Fixed objects | |---|---| -| Foundation data hall | six rows / sixty sites: `foreign_rack` (x51), `dead_rack` (x5), Rack 3 core, `rack` (x3 empty pilot allocations), local `patch_panel` relay, `env_camera` (dormant), `badge_door_t2` | +| Foundation data hall | six rows / 240 sites, each row two contiguous banks of twenty split by a walkable cross-aisle: `foreign_rack` (x221), `dead_rack` (x15), Rack 3 core, `rack` (x3 empty pilot allocations), local `patch_panel` relay, `env_camera` (dormant), `cable_run` row-end caps, `badge_door_t2` | | Network closet | subnet `switch`, `patch_panel`, `power_core`, `badge_door_t2` | | Electrical room | `ups` (carries the UPS meter device), `breaker_panel`, `conduit` | | HVAC plant | `hvac_unit` (x2; one carries the HVAC meter device), `vent` (x2) | @@ -110,14 +121,26 @@ carved by code. separated by several floor pitches — enough to read as places at attention-close framing, close enough to establish a followable local relationship without changing access progression. -- **The data hall is pressure, not loot.** Rack 3 sits mid-row inside a +- **The data hall is pressure, not loot.** Rack 3 sits mid-bank inside a forest of Foundation-owned machines. `foreign_rack`, `dead_rack`, empty allocation, and owned machine are distinct site states exposed through one sim query. Foreign racks contribute no player compute merely because they are adjacent. A dead chassis can be revived in place as owned compute; its authored corpse tile remains provenance underneath the new machine. +- **Density is the pressure.** A row is a wall of institutional metal, not a + line of lonely towers: chassis stand shoulder to shoulder and the aisle does + the separating, the way a real hall is built. The player's side of the hall is + **absolute, not proportional** — one core, three pilot allocations — so a + denser hall makes the player's holding read smaller, never larger. Owning one + site of 240 is the felt statement the room exists to make. Growing the site + count is therefore a content change, never a capacity grant. +- **Service runs may not sever the hall.** `cable_run` is unwalkable, so it caps + row ends and never runs the room's full height. Every site keeps an + orthogonally adjacent walkable face reachable from the hall doors, because a + site nobody can stand at can never take a physical install. - **Hall rows are territorial aggregates.** Each row names a network segment, - PDU pair, baseline draw, cooling zone, and concealment profile. Acquiring a + PDU pair, baseline draw, cooling zone, and concealment profile, and covers all + forty of its sites across both banks. Acquiring a row requires one coordinated act from each existing human capability: Dana prepares network control, Priya prepares power/cooling, Marcus prepares physical installation. The row must then hold at least two owned machines @@ -149,13 +172,21 @@ until its exact camera TAP commits. Rooms label on hover/inspect. 4. All fixed-object tile types above exist as distinct, inspectable tiles in both frontends (flat colors acceptable until the art pass). 5. Save/load round-trips the layout, sensor ownership, and fog state. -6. The Foundation hall contains exactly sixty explicit rack sites in six - rows: 51 live foreign, five dead, one owned core, and three empty pilot +6. The Foundation hall contains exactly 240 explicit rack sites in six rows, + each row two contiguous banks of twenty split by a walkable cross-aisle: + 221 live foreign, fifteen dead, one owned core, and three empty pilot allocations. Foreign capacity is legible but absent from the player fleet; dead-rack revival and row-control progress save/load without silently - converting neighboring capacity. + converting neighboring capacity. One module owns the site geometry; the + plate, the aggregate row readout, and both frontends' authored service layer + derive from it rather than repeating a coordinate range. 7. Rack 3, the environmental monitor, and the local patch relay occupy distinct coordinates inside the Foundation data hall. Each pair stays within seven orthogonal floor pitches, while the monitor is at least three pitches from Rack 3 so the material close frame does not stack their forms. + Rack 3 keeps an empty pilot allocation on each side, so the attention-close + frame still resolves it as one machine inside an otherwise solid bank. The opening subnet switch remains inside the network closet. +8. No authored service run severs the hall. Every one of the 240 sites has an + orthogonally adjacent walkable tile reachable from the hall's doors, so any + site can host a physical install.