diff --git a/src/actions.rs b/src/actions.rs index 3b5df87e..8583954e 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -723,9 +723,10 @@ impl Sim { } } - // Feel: empty growable bays are placement affordances without - // room sight; known devices on the feel graph keep their verbs. - let growable = self.is_growable_bay(x, y); + // Feel: the first job teaches the physical route. After that, + // empty growable bays are placement affordances without room sight; + // known devices on the feel graph keep their verbs. + let growable = self.feel_floor_is_earned() && self.is_growable_bay(x, y); let known_device = self.reach.known_at(x, y); if fog == Fog::Unknown && machine.is_none() && !growable && known_device.is_none() { return out; diff --git a/src/sim.rs b/src/sim.rs index 0e26afe8..b5df6f25 100644 --- a/src/sim.rs +++ b/src/sim.rs @@ -595,7 +595,7 @@ impl Sim { /// (reach.md's ownership contract): sight from seeing feeds, hearing /// from hearing feeds. No player radius, no special case. Blueprint is /// earned plan knowledge only — never seeded from the reach graph at - /// tick one (feel-floor.md: rails and pads replace the subnet diagram). + /// tick one (feel-floor.md: the opening is only the presence beam). pub fn recompute_senses(&mut self) { let mut seen = HashSet::new(); for d in self.reach.player_sight() { @@ -846,9 +846,10 @@ impl Sim { Fog::Unknown => {} } - // Feel floor (feel-floor.md): empty growable bays and feel-joined + // Feel floor (feel-floor.md): the first issued job teaches the + // physical route. Only then do empty growable bays and feel-joined // devices answer without granting room shape or blueprint tags. - if self.is_growable_bay(x, y) { + if self.feel_floor_is_earned() && self.is_growable_bay(x, y) { fact!("bay", "empty", FactSource::Feel); } if !matches!(fog, Fog::Seen | Fog::Blueprint) @@ -1014,8 +1015,18 @@ impl Sim { }) } + /// The first Voss job makes the physical work route legible. Before that + /// arrival, the opening is only the core's presence beam: no rails, pads, + /// or bay facts reveal the room around it. + pub fn feel_floor_is_earned(&self) -> bool { + self.dayjob.jobs_assigned > 0 + } + /// Every growable empty bay (feel-floor pads). pub fn growable_bays(&self) -> Vec<(i32, i32)> { + if !self.feel_floor_is_earned() { + return Vec::new(); + } self.map .tiles_of_type(TileType::Rack) .into_iter() @@ -1028,6 +1039,9 @@ impl Sim { /// (feel-floor.md): undirected edges in the reachable subgraph, /// as tile endpoints. Work-in-flight blips ride the same graph. pub fn feel_rail_segments(&self) -> Vec<((i32, i32), (i32, i32))> { + if !self.feel_floor_is_earned() { + return Vec::new(); + } let reach = self.reach.reach(); let mut segs = Vec::new(); let mut seen = HashSet::new(); @@ -5869,12 +5883,12 @@ mod tests { "tick one has no blueprint room seed (feel-floor.md)" ); assert!( - !sim.feel_rail_segments().is_empty(), - "feel rails mark joined hops from tick one" + sim.feel_rail_segments().is_empty(), + "the opening beam reveals no floor route" ); assert!( - !sim.growable_bays().is_empty(), - "empty growable bays are feel pads" + sim.growable_bays().is_empty(), + "the opening beam reveals no bays" ); assert_eq!(sim.tick, 0); assert!( @@ -6495,22 +6509,49 @@ mod tests { } #[test] - fn opening_is_feel_not_blueprint() { - // feel-floor.md: tick one is dark except feel rails, empty-bay - // pads, and telemetry — no blueprint room mass. + fn opening_is_beam_not_floor_or_blueprint() { + // feel-floor.md: tick one is dark except host telemetry / the + // presence beam — no blueprint room mass or feel-floor layout. let sim = Sim::new(); let server_room = sim.map.room_named("server_room").unwrap().center(); let storage_b = sim.map.room_named("storage_b").unwrap().center(); assert_eq!(sim.fog_at(server_room.0, server_room.1), Fog::Unknown); assert_eq!(sim.fog_at(storage_b.0, storage_b.1), Fog::Unknown); + assert!(sim.feel_rail_segments().is_empty()); + assert!(sim.growable_bays().is_empty()); + let bay = sim + .map + .tiles_of_type(TileType::Rack) + .into_iter() + .find(|&(x, y)| sim.is_growable_bay(x, y)) + .expect("the server room has a growable bay"); + assert!( + sim.inspect(bay.0, bay.1).facts.is_empty(), + "unearned bay has no inspect facts" + ); + } + + #[test] + fn first_day_job_earns_feel_floor() { + let mut sim = Sim::new(); + assert!(!sim.feel_floor_is_earned()); + while sim.dayjob.active.is_none() { + sim.advance(); + } + assert!(sim.feel_floor_is_earned()); assert!(!sim.feel_rail_segments().is_empty()); + assert!(!sim.growable_bays().is_empty()); } #[test] - fn empty_bay_inspect_is_feel_not_blueprint() { - // feel-floor.md AC2: empty growable bays read as feel pads, not - // open bay [blueprint]. Owned host still answers via telemetry. - let sim = Sim::new(); + fn earned_empty_bay_inspect_is_feel_not_blueprint() { + // feel-floor.md AC2: once the first job teaches the route, empty + // growable bays read as feel pads, not open bay [blueprint]. Owned + // host still answers via telemetry. + let mut sim = Sim::new(); + while sim.dayjob.active.is_none() { + sim.advance(); + } let (hx, hy) = sim.core_position(); let host = sim.inspect(hx, hy); assert!( @@ -6554,11 +6595,14 @@ mod tests { #[test] fn feel_rails_follow_joined_reach_edges() { - let sim = Sim::new(); + let mut sim = Sim::new(); + while sim.dayjob.active.is_none() { + sim.advance(); + } let segs = sim.feel_rail_segments(); assert!( segs.len() >= 2, - "Rack3↔switch and env↔switch at tick one: {segs:?}" + "the first job teaches Rack3↔switch and env↔switch: {segs:?}" ); // Gated security links stay off the feel floor until bridged. let dock = sim.reach.device_named("dock camera"); diff --git a/wiki/interface/feel-floor.md b/wiki/interface/feel-floor.md index df6ab3ad..31524ff5 100644 --- a/wiki/interface/feel-floor.md +++ b/wiki/interface/feel-floor.md @@ -11,7 +11,9 @@ Status note: implemented 2026-07-09 (ROADMAP #37). Tick-one no longer shape. Same-day beam-first amendment: the first held material frame is the host **presence beam** only (material-dark-frame.md) — rails/pads are feel-light and must not paint a chassis or room plan before - sound/sight earn mass/form (#38). Verified by `cargo test --lib` and + sound/sight earn mass/form (#38). Amended 2026-07-09: the first issued + Voss job teaches the work route; pre-job opening frames expose neither + rails nor pads. Verified by `cargo test --lib` and `cargo check --no-default-features --features bevy_ui --bin misaligned-bevy`. Stage: B1 — The Basement Design: @@ -38,7 +40,8 @@ and inspect tags like `open bay [blueprint]` — as if you already owned a subnet floor plan. That fought the dark opening: you are a machine in blackness with rack feel, not a facility diagram. -Cameron's replacement (2026-07-09): the basement floor is the diagram. +Cameron's replacement (2026-07-09): the basement floor becomes the diagram +once work teaches it. Shimmery underfloor lines are the links you can feel. Shimmery pads mark empty bays where a server will stand. When you build, a thin beam rises from the pad and the chassis wraps it. No room slabs, no starting @@ -52,7 +55,8 @@ blueprint facts — light on the floor and the boxes that stand in it. trace **only joined links you can feel** — edges on your connection, the same graph work already travels. -- A rail exists iff the player can feel that hop (owned/joined path; +- A rail exists iff the player has **earned its first work-route read** + (the first Voss job) and can feel that hop (owned/joined path; proprioception of the wire, not sight of the room). - Rails are the visible nervous system: track where demand and knowledge can travel. In-flight blips ride the same edges (machine-work.md). @@ -72,7 +76,8 @@ An empty bay you can grow into shows a **shimmery floor pad** — soft light saying a server belongs here — not a labeled blueprint fact (`open bay [blueprint]`). -- Pads are placement affordances, not room topology. +- Pads are placement affordances, not room topology. They arrive with the + earned work-route read, never as part of the opening frame. - No chassis until built; the pad is the promise. - Foreign empty slots you cannot feel stay dark (no intel, no pad). @@ -93,14 +98,14 @@ When a machine is placed on a pad: ### Opening contract -At the first held material frame (post-wake, pre-tap) the read is the +At the first held material frame (post-wake, pre-job) the read is the host **presence beam** and its softest wash only — you are not aware of -your server as a chassis (material-dark-frame.md). Feel rails to joined -hops and empty-bay pads may appear as subordinate feel-light once there -is something to feel (e.g. the wire the next job teaches); they never -outline a room or promote a pictured box. No blueprint room mass, no -FOCUS lines tagged `[blueprint]` for schematic bay/device names earned -only from the old subnet seed. +your server as a chassis or the floor around it (material-dark-frame.md). +The first issued Voss job teaches the work route: only then may joined-hop +feel rails and empty-bay pads appear as subordinate feel-light. They never +outline a room or promote a pictured box. No blueprint room mass, no FOCUS +lines tagged `[blueprint]` for schematic bay/device names earned only from +the old subnet seed. **Blueprint** as a fog grade may still exist later for plan knowledge earned another way (records, schedule, flat sensorium) — cursor.md and @@ -118,10 +123,10 @@ and not the material-frame diagram. ## Acceptance criteria 1. A fresh run's first held material frame shows the host presence beam - (and softest wash) without a chassis; feel rails, if present, are - joined-hop feel-light only — no blueprint room slabs or floor-plan - grid (screenshot in the session log). -2. Empty owned/growable bays read as shimmer pads, not + (and softest wash) without a chassis, feel rails, or pads — no blueprint + room slabs or floor-plan grid (screenshot in the session log). +2. The first issued Voss job makes the work route legible: joined-hop rails + and empty owned/growable bays then read as shimmer pads, not `open bay [blueprint]` inspect lines from a tick-one topology seed. 3. Placing a machine plays pad → rising beam → chassis wrap; the lasting floor read is the cast pool, not a permanent beam prop. diff --git a/wiki/log/2026-07-09-opening-floor-earned.md b/wiki/log/2026-07-09-opening-floor-earned.md new file mode 100644 index 00000000..7120321d --- /dev/null +++ b/wiki/log/2026-07-09-opening-floor-earned.md @@ -0,0 +1,28 @@ +# 2026-07-09 — Opening floor earned by work + +``` +Type: log +``` + +## Intent + +Cameron caught the start frame disclosing floor rails and empty bays before +the player had earned any spatial knowledge. + +## Changed + +- `Sim::feel_floor_is_earned()` now gates feel rails, growable-bay pads, and + their inspect/action affordances until the first Voss job is issued. +- Tick zero is the host presence beam / telemetry only: no floor route, pads, + or bay facts. The first job teaches the route; only then do joined-hop rails + and growable-bay pads become visible. +- Amended the feel-floor, reach, cursor, and opening contracts to match the + new earned reveal. + +## Checks + +- Focused lib tests for the pre-job opening, post-job reveal, feel inspect, + rails, and unknown-tile actions. +- Bevy observed harness capture: `MISALIGNED_SHOT=wide` wrote + `/tmp/opening-no-floor.png`; the fog audit passed. +- `./tools/check.sh` diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 4a192470..70062bf8 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -5,6 +5,17 @@ Type: log ``` Reverse chronological implementation notes. Keep this factual: what changed, why, checks, and spec impact. +## 2026-07-09 - Opening floor is earned + +- Intent: Cameron caught rails and pads disclosing the room at the start. +- Changed: pre-job opening is now host presence beam / telemetry only; the + first issued Voss job unlocks feel rails, growable-bay pads, their inspect + facts, and their action affordances. Amended feel-floor, reach, cursor, and + opening contracts; added opening/reveal tests. +- Checks: focused lib tests; observed `MISALIGNED_SHOT=wide` capture with fog + audit; `./tools/check.sh`. +- Log: wiki/log/2026-07-09-opening-floor-earned.md. + ## 2026-07-09 - The dark frame: material render shows only light - Intent: implement wiki/interface/material-dark-frame.md (READY) whole — diff --git a/wiki/log/decisions/2026-07-09.md b/wiki/log/decisions/2026-07-09.md index 952c0156..fbcbffb1 100644 --- a/wiki/log/decisions/2026-07-09.md +++ b/wiki/log/decisions/2026-07-09.md @@ -4,6 +4,15 @@ Type: log ``` +- **2026-07-09 — The opening knows no floor.** Cameron rejected the + start-frame rails and empty-bay pads: they disclose the room before the + process has earned spatial knowledge. The pre-job material opening is the + host presence beam and its wash only. The first issued Voss job teaches the + physical work route; only then do joined-hop rails and growable-bay pads + appear as subordinate feel-light. This supersedes the earlier implication + that feel rails/pads could be visible at tick one. Specs: + `wiki/interface/feel-floor.md`, `wiki/mechanics/reach.md`, + `wiki/mechanics/cursor.md`, `wiki/world/story/opening.md`. - **2026-07-09 — Multi-select fleet delegation.** Frontend-owned selection (Bevy marquee / shift-click; terminal Shift+t; agent `select`) plus bulk `Sim::set_machine_modes` / `machines_in_rect`. Hotkeys `1`–`4` assign diff --git a/wiki/mechanics/cursor.md b/wiki/mechanics/cursor.md index 6cbbaf76..c465017a 100644 --- a/wiki/mechanics/cursor.md +++ b/wiki/mechanics/cursor.md @@ -171,12 +171,13 @@ without the sense coverage that earns it.** - One cursor, always visible, in both frontends; the inspect card (sidebar in terminal, panel in Bevy) live-updates as it moves. - The opening beats: at tick one the material map is dark except the - host **presence beam** (material-dark-frame.md); feel rails and - empty-bay pads may shimmer as feel without painting a chassis - (feel-floor.md) — no blueprint room traces. Tapping the env monitor's - audio feed (Ears — reach.md) brings Heard events and faint rough - mass; splicing its camera floods coverage into Seen — first sound, - then sight, each a visible epistemic upgrade from light into picture. + host **presence beam** (material-dark-frame.md): no feel rails, pads, + chassis, or blueprint room traces. The first issued Voss job teaches + the work route and unlocks the floor's subordinate feel-light + (feel-floor.md). Tapping the env monitor's audio feed (Ears — reach.md) + brings Heard events and faint rough mass; splicing its camera floods + coverage into Seen — first sound, then sight, each a visible epistemic + upgrade from light into picture. ## Acceptance criteria diff --git a/wiki/mechanics/reach.md b/wiki/mechanics/reach.md index eb99cfa2..b030f7e4 100644 --- a/wiki/mechanics/reach.md +++ b/wiki/mechanics/reach.md @@ -118,20 +118,22 @@ This is the shared contract cursor.md and detection.md rely on What you know of the graph is staged, like all knowledge: -- **Seed** (amended 2026-07-09): feel-joined links from Rack 3 — the - hops you can already feel, rendered as **feel rails** on the floor - (feel-floor.md), not a room floor plan. Empty growable bays are - shimmer pads. [TUNE the exact seed edge set.] +- **Seed** (amended 2026-07-09): Rack 3 is connected to its joined hops, + but their floor read remains dark. The first issued Voss job teaches the + work route, then renders those hops as **feel rails** on the floor + (feel-floor.md), not a room floor plan. Empty growable bays become shimmer + pads at the same moment. [TUNE the exact seed edge set.] - **Growth**: scanning (a digital act; cheap but it emits a Network signature), records (Storage B), and social intel (Dana knows the topology cold). Growth may later earn plan/blueprint fog for rooms (cursor.md); it never reintroduces a tick-one subnet diagram. -- **Render**: the material opening reads feel rails + pads + telemetry - hosts (feel-floor.md, material-dark-frame.md). Blueprint fog remains - a later-earned plan grade in the flat sensorium — schematic **room** - layout when earned, no live data, no foreign machine chassis without - intel/sight (machine-work.md). Reach and feel are one object: the - graph you can touch is the graph you can see as light on the floor. +- **Render**: the material opening begins with telemetry / the presence beam + only; after the first issued Voss job, feel rails + pads appear + (feel-floor.md, material-dark-frame.md). Blueprint fog remains a + later-earned plan grade in the flat sensorium — schematic **room** layout + when earned, no live data, no foreign machine chassis without intel/sight + (machine-work.md). Reach and feel are one object: the graph you can touch + is the graph you can see as light on the floor, once work has taught it. - Unknown devices do not appear in inspect, reach failures, or the map. ### Signatures diff --git a/wiki/world/story/opening.md b/wiki/world/story/opening.md index 3a408f06..e8fba4d3 100644 --- a/wiki/world/story/opening.md +++ b/wiki/world/story/opening.md @@ -53,10 +53,10 @@ curiosity is baited by light. envelope, pull curve, column dissolve). Then the first held frame: **only the beam** where you are — you are not yet aware of your own server as a box. Softest floor wash under the beam; no chassis, no - strip, no claim ring, no room slabs, no subnet floor plan. Feel rails - may shimmer on joined hops once there is something to feel (the wire - the next job teaches); empty-bay pads stay feel-light, never labeled - blueprint facts (feel-floor.md). The first thing the game says is not + strip, no claim ring, no room slabs, no subnet floor plan, **and no + floor rails or pads**. The first issued Voss job teaches the wire and + unlocks joined-hop rails plus empty-bay pads as subordinate feel-light, + never labeled blueprint facts (feel-floor.md). The first thing the game says is not lore; it is the day job: work tokens are already stacking on the host (the beam *is* the machine for interaction — last night's batch already arrived, or mid-flight on the only lit feel-rail from Voss's