From e957ce7b757fbdb07183aaf89bc520fbc38b10fa Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 9 Jul 2026 10:17:10 -0700 Subject: [PATCH] Hide foreign machines under blueprint fog. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Defense: machine-work.md — foreign chassis need intel to exist at all; blueprint is room topology (walls/doors/floors), not a rack farm. Owned hosts still render via telemetry. Bevy was painting every blueprint prop amber and lighting the nearest known device, which read as controlled territory at tick zero. Co-authored-by: Cursor --- src/bin/bevy.rs | 76 ++++++++++++-------- src/sim.rs | 73 ++++++++++++++++++- wiki/log/2026-07-09-blueprint-no-machines.md | 20 ++++++ wiki/log/DEVLOG.md | 9 +++ wiki/mechanics/cursor.md | 28 +++++--- wiki/mechanics/reach.md | 5 +- wiki/mechanics/sim-mechanics.md | 2 +- 7 files changed, 170 insertions(+), 43 deletions(-) create mode 100644 wiki/log/2026-07-09-blueprint-no-machines.md diff --git a/src/bin/bevy.rs b/src/bin/bevy.rs index 6d8f86e..fa0c1ba 100644 --- a/src/bin/bevy.rs +++ b/src/bin/bevy.rs @@ -241,9 +241,12 @@ fn remembered_tint(tile: TileType) -> Color { } fn blueprint_color(tile: TileType) -> Color { + // Blueprint is room topology only (machine-work.md / cursor.md): walls, + // doors, floors. Machine chassis are absent until intel or sight — never + // painted as amber "known racks" on the plan. use TileType::*; if machine_tile(tile) { - scaled(AMBER_DIM, 0.41) + scaled(PORCELAIN, 0.115) // treat chassis tiles as empty floor on the plan } else if danger_tile(tile) { scaled(CRIMSON, 0.29) } else { @@ -1251,6 +1254,18 @@ fn person_silhouette_image() -> Image { ) } +/// Owned compute at this tile answers through telemetry (cursor.md): its +/// chassis may render even when the surrounding room is only Blueprint. +/// Foreign / uncontrolled machine tiles stay absent under blueprint — +/// they are built in the world, but you have no intel that they are there. +fn owned_machine_at(sim: &misaligned::sim::Sim, x: i32, y: i32) -> bool { + use misaligned::machine::Provenance; + sim.compute + .machines + .iter() + .any(|m| m.x == x && m.y == y && matches!(m.provenance, Provenance::Owned)) +} + /// The sim-known tile at a coordinate under fog: remembered coordinates /// render their remembered snapshot, everything else the live map. fn known_tile(game: &Game, x: i32, y: i32) -> TileType { @@ -1644,7 +1659,8 @@ fn setup_3d( layer.clone(), ChildOf(root), )); - // 2) warm amber machine glow at the core rack (machine presence); + // 2) warm amber machine glow at the core rack (machine presence) — the + // only machine light at tick zero; foreign chassis stay dark/absent. commands.spawn(( PointLight { color: AMBER, @@ -1656,33 +1672,11 @@ fn setup_3d( Transform::from_translation( grid_to_world_3d(core.0, core.1, 0.0) + Vec3::new(0.0, 1.5, 0.6), ), - layer.clone(), + layer, ChildOf(root), )); - // 3) a second amber glow at the nearest known device anchor away from the - // core (device anchors come from the start-of-game blueprint, so this - // lights no unearned geometry). - let second = game - .sim - .reach - .known() - .map(|d| (d.x, d.y)) - .filter(|&(dx, dy)| (dx - core.0).abs() + (dy - core.1).abs() >= 4) - .min_by_key(|&(dx, dy)| (dx - core.0).abs() + (dy - core.1).abs()); - if let Some((sx, sy)) = second { - commands.spawn(( - PointLight { - color: AMBER, - intensity: 200_000.0, - range: 7.0, - shadows_enabled: false, - ..default() - }, - Transform::from_translation(grid_to_world_3d(sx, sy, 0.0) + Vec3::new(0.0, 1.3, 0.4)), - layer, - ChildOf(root), - )); - } + // No second amber lamp on foreign/known-subnet devices: blueprint is + // topology, not a glowing rack farm (machine-work.md: no intel, no render). commands.insert_resource(mesh_pool); } @@ -1768,6 +1762,11 @@ fn restyle_3d( (TilePart::Block | TilePart::Top, Fog::Heard) => false, (TilePart::Block | TilePart::Top, _) => blocky_tile(tile), (TilePart::Prop, Fog::Heard) => false, + // Blueprint: no foreign chassis / furniture props — room mass + // only. Owned machines still render via telemetry. + (TilePart::Prop, Fog::Blueprint) => { + machine_tile(tile) && owned_machine_at(&game.sim, t.x, t.y) + } (TilePart::Prop, _) => prop_tile(tile), }; if !show { @@ -1796,13 +1795,23 @@ fn restyle_3d( } // Live-feed emissive state (flat-materials.md criterion 3): a seen // device actively feeding the player scans in the cold signal color. + // Owned machines under blueprint still use Seen materials — telemetry + // legitimizes the chassis without granting room sight. + let mat_fog = if t.part == TilePart::Prop + && matches!(fog, Fog::Blueprint) + && owned_machine_at(&game.sim, t.x, t.y) + { + Fog::Seen + } else { + fog + }; let live = t.part == TilePart::Prop - && matches!(fog, Fog::Seen) + && matches!(mat_fog, Fog::Seen) && game.sim.reach.known_at(t.x, t.y).is_some_and(|d| { d.feed_to(Party::Player, true) || d.feed_to(Party::Player, false) }); - let handle = pooled_material(tile, fog, t.part, live, &mut mats, &mut cache); + let handle = pooled_material(tile, mat_fog, t.part, live, &mut mats, &mut cache); if mat.0 != handle { mat.0 = handle; } @@ -2475,7 +2484,14 @@ fn render_map(game: Res, mut q: Query<(&mut Sprite, &TilePos), With blueprint_color(game.sim.map.get_tile(pos.x, pos.y)), + Fog::Blueprint => { + if owned_machine_at(&game.sim, pos.x, pos.y) { + // Telemetry body: you feel your rack even without eyes. + scaled(AMBER_DIM, 0.55) + } else { + blueprint_color(game.sim.map.get_tile(pos.x, pos.y)) + } + } Fog::Unknown => unknown_color(pos.x, pos.y), }; } diff --git a/src/sim.rs b/src/sim.rs index 6b27ffb..ab08b0d 100644 --- a/src/sim.rs +++ b/src/sim.rs @@ -763,7 +763,22 @@ impl Sim { } Fog::Blueprint => { let tile = self.map.get_tile(x, y); - fact!("schematic", tile.name(), FactSource::Blueprint); + // Blueprint is topology (walls/doors/floors), not hardware. + // Machine chassis stay unnamed here — owned machines still + // answer through the telemetry block below (cursor.md). + let schematic = if matches!( + tile, + crate::tiles::TileType::Core + | crate::tiles::TileType::Rack + | crate::tiles::TileType::PowerCore + | crate::tiles::TileType::Ups + | crate::tiles::TileType::DeadEquipment + ) { + "open bay" + } else { + tile.name() + }; + fact!("schematic", schematic, FactSource::Blueprint); if tile.is_door() && tile.security_level() > 0 { fact!( "badge", @@ -780,6 +795,8 @@ impl Sim { FactSource::Telemetry, ); } + // Known reach nodes (switch, sensors) remain topology facts; + // they are not rendered as physical chassis under blueprint. if let Some(d) = self.reach.known_at(x, y) { fact!("device", d.name.clone(), FactSource::Blueprint); } @@ -5888,6 +5905,60 @@ mod tests { assert_eq!(sim.fog_at(storage_b.0, storage_b.1), Fog::Unknown); } + #[test] + fn blueprint_does_not_name_foreign_machine_chassis() { + // machine-work.md: foreign machines need intel to exist at all; + // blueprint is room topology, not a rack farm. Owned host still + // answers through telemetry. + let sim = Sim::new(); + let (hx, hy) = sim.core_position(); + let host = sim.inspect(hx, hy); + assert!( + host.facts.iter().any(|f| f.label == "machine"), + "owned host answers via telemetry" + ); + // A non-host rack tile in the blueprint room, if any, must not + // advertise itself as a Server Rack on the schematic. + let room = sim.map.room_named("server_room").unwrap(); + let mut checked = 0; + for y in room.y..room.y + room.h { + for x in room.x..room.x + room.w { + if (x, y) == (hx, hy) { + continue; + } + if !matches!( + sim.map.get_tile(x, y), + crate::tiles::TileType::Rack | crate::tiles::TileType::Core + ) { + continue; + } + if sim.fog_at(x, y) != Fog::Blueprint { + continue; + } + let card = sim.inspect(x, y); + let schematic = card + .facts + .iter() + .find(|f| f.label == "schematic") + .map(|f| f.value.as_str()); + assert_eq!( + schematic, + Some("open bay"), + "foreign chassis must not name themselves under blueprint at ({x},{y})" + ); + assert!( + !card.facts.iter().any(|f| f.label == "machine"), + "no telemetry machine fact on uncontrolled chassis" + ); + checked += 1; + } + } + assert!( + checked > 0, + "expected at least one foreign rack tile in the server room blueprint" + ); + } + #[test] fn hearing_gives_presence_without_identity() { // A heard event names no one until staged knowledge identifies them. diff --git a/wiki/log/2026-07-09-blueprint-no-machines.md b/wiki/log/2026-07-09-blueprint-no-machines.md new file mode 100644 index 0000000..09512d5 --- /dev/null +++ b/wiki/log/2026-07-09-blueprint-no-machines.md @@ -0,0 +1,20 @@ +# 2026-07-09 — Blueprint is topology, not a rack farm + +``` +Type: log +``` + +Cameron: foreign machines are already built but you do not control them — +blueprint must not paint them. Opening material view was showing a glowing +amber rack farm from subnet-seeded blueprint props. + +## Fix + +- Bevy material: under `Fog::Blueprint`, hide non-owned machine/furniture + props; owned compute still shows via telemetry (Seen materials). +- Flat blueprint colors: machine tiles read as empty floor, not amber. +- Drop the second amber point light on nearest known foreign device. +- `Sim::inspect` under blueprint names chassis tiles as `open bay`, not + Server Rack; owned host still gets telemetry machine facts. +- Specs: cursor.md / reach.md / sim-mechanics.md aligned with + machine-work.md ("no intel, no render"). diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 8c6a60a..b049f75 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -5,6 +5,15 @@ Type: log ``` Reverse chronological implementation notes. Keep this factual: what changed, why, checks, and spec impact. +## 2026-07-09 - Blueprint hides foreign machines + +- Intent: blueprint is room topology; foreign chassis need intel/sight + (machine-work.md), not a glowing plan of every rack. +- Changed: Bevy blueprint prop culling + telemetry-owned exception; + inspect schematic `open bay`; cursor/reach/sim-mechanics wording. +- Checks: focused fog/inspect tests; `./tools/check.sh`. +- Log: wiki/log/2026-07-09-blueprint-no-machines.md. + ## 2026-07-09 - Spec stale-language cleanup - Intent: scrub current-state wiki/DESIGN language that still treated the diff --git a/wiki/mechanics/cursor.md b/wiki/mechanics/cursor.md index 15bee01..ceea2bc 100644 --- a/wiki/mechanics/cursor.md +++ b/wiki/mechanics/cursor.md @@ -12,8 +12,10 @@ Status note: implemented 2026-07-07 on the cursor-senses branch. The walking player-facing identity surface (people panel, detection sidebar, map glyph, context-menu verbs, signature notes, agent frame) behind staged social knowledge — role silhouettes until Schedule, authored names after - (criterion 5 extended). Verified by `cargo test` and `cargo check - --no-default-features --features bevy_ui --bin misaligned-bevy`. + (criterion 5 extended). 2026-07-09: blueprint is room topology only — + foreign machine chassis are absent under blueprint; owned hosts still + render via telemetry (machine-work.md). Verified by `cargo test` and + `cargo check --no-default-features --features bevy_ui --bin misaligned-bevy`. Stage: B1 — The Basement Constitution: "Presence: the cursor and the senses", "No disembodied hands" (the action-side twin), "Justification and legibility" @@ -98,10 +100,13 @@ precedence: **Seen** > **Heard** > **Remembered** > **Blueprint** > Seen state, timestamped ("last seen day 3, 14:00" on inspect); never live. A process keeps logs — DF-style map memory with an in-fiction reason. -- **Blueprint** — schematic: walls, doors, fixed objects known from - static sources; **no live data**. Seeded from the reach graph's known - topology (the partial subnet map — reach.md owns knowledge growth); - grows from records and schedule knowledge. +- **Blueprint** — schematic **room topology** only: walls, doors, floors + known from static sources; **no live data, no machine chassis**. Foreign + hardware is absent until intel or sight (machine-work.md: no intel, no + render). Owned machines you run still answer through **telemetry** on + their own tile. Seeded from the reach graph's known topology (the + partial subnet map — reach.md owns knowledge growth); grows from + records and schedule knowledge. - **Unknown** — dark. Terminal renders the states as distinct dim/glyph conventions; Bevy as @@ -143,7 +148,8 @@ 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 map is dark except blueprint - traces of the known subnet. Tapping the env monitor's audio feed + room traces of the known subnet (no foreign racks) and your own + telemetry host. Tapping the env monitor's audio feed (Ears — reach.md) brings the first heard events; splicing its camera floods the coverage into Seen — first sound, then sight, each a visible epistemic upgrade to a diagram you already obsess over. @@ -185,9 +191,11 @@ without the sense coverage that earns it.** 7. Save/load round-trips sensor capability flags and all knowledge state this spec adds; cursor position is frontend-only and absent from the save. -8. Blueprint: subnet-seeded rooms render schematic from tick one; - non-seeded rooms are dark; blueprint tiles show no live state and - convert to Seen/Heard when coverage arrives. +8. Blueprint: subnet-seeded rooms render schematic topology from tick + one (walls/doors/floors); non-seeded rooms are dark; blueprint never + paints foreign machine chassis; owned machines remain visible via + telemetry; blueprint tiles show no live state and convert to + Seen/Heard when coverage arrives. 9. **Remembered:** a tile seen once and lost renders its dimmed snapshot (never live data); inspect shows the last-seen timestamp; diff --git a/wiki/mechanics/reach.md b/wiki/mechanics/reach.md index f823622..6d660bb 100644 --- a/wiki/mechanics/reach.md +++ b/wiki/mechanics/reach.md @@ -107,7 +107,10 @@ What you know of the graph is staged, like all knowledge: signature), records (Storage B), and social intel (Dana knows the topology cold). - **Render**: known topology renders as the **blueprint** fog state - (cursor.md — decided 2026-07-06): schematic layout, no live data. + (cursor.md — decided 2026-07-06): schematic **room** layout, no live + data, no foreign machine chassis (those need intel/sight — + machine-work.md). Reach-graph device names may still appear on + inspect as topology facts; they are not physical props on the plan. The subnet map is both what you know and what you can touch; reach and blueprint are one object viewed as verb and as noun. - Unknown devices do not appear in inspect, reach failures, or the map. diff --git a/wiki/mechanics/sim-mechanics.md b/wiki/mechanics/sim-mechanics.md index 6d8e503..f6aa25c 100644 --- a/wiki/mechanics/sim-mechanics.md +++ b/wiki/mechanics/sim-mechanics.md @@ -80,7 +80,7 @@ clause (see wiki/log/2026-07-05-demolition.md). - `Sim::fog_at(x, y)` returns the epistemic tile state by precedence: Seen (subscribed seeing feed covers it) > Heard (hearing feed) > Blueprint - (known graph topology, schematic) > Unknown. Blueprint is seeded from the + (known room topology, schematic — no foreign chassis) > Unknown. Blueprint is seeded from the reach graph's known devices and their rooms. - The hearing channel emits `HeardEvent`s for legible audio feedback (entry into covered rooms, and authored `Person.utterances` — Marcus's midnight -- 2.51.2