diff --git a/src/sim.rs b/src/sim.rs index 8a5b9e0b..9b280a07 100644 --- a/src/sim.rs +++ b/src/sim.rs @@ -5112,6 +5112,72 @@ mod tests { assert!(sim.detection.pending_size() < before); } + #[test] + fn unpaid_overhead_degrades_other_channels_delivered_effect() { + // compute.md criterion 2: unpaid core overhead visibly degrades the + // other channels. Twin sims with an identical five-way allocation + // and one pending signature to scrub; the only difference is an + // overhead the machines can't cover. Pin the delivered per-channel + // effects (rates, accrual, scrub), not just the flag. + let setup = |unpayable: bool| { + let mut sim = Sim::with_seed(41); + sim.social_bandwidth = 0.0; + sim.compute.allocation.weights = [1, 1, 1, 1, 1]; + sim.detection.emit(Signature { + kind: SignatureKind::Network, + size: 30, + standing: false, + site: None, + }); + if unpayable { + sim.core.overhead = sim.effective_compute() + 1.0; + } + sim.drain_log(); + sim + }; + + let mut healthy = setup(false); + let pending_before = healthy.detection.pending_size(); + run(&mut healthy, ECONOMY_INTERVAL); + assert!(!healthy.core.degraded, "overhead paid: no degraded mode"); + assert!(healthy.last_day_job_rate > 0.0, "day job channel is fed"); + assert!(healthy.last_research_rate > 0.0, "research channel is fed"); + assert!(healthy.last_schemes_rate > 0.0, "schemes channel is fed"); + assert!(healthy.social_bandwidth > 0.0, "social ops accrue"); + assert!( + healthy.research.progress.iter().sum::() > 0.0, + "research progress advances" + ); + assert!( + healthy.detection.pending_size() < pending_before, + "concealment scrubs the pending signature" + ); + + let mut degraded = setup(true); + assert_eq!(degraded.detection.pending_size(), pending_before); + run(&mut degraded, ECONOMY_INTERVAL); + assert!(degraded.core.degraded, "unpaid overhead sets degraded mode"); + let log = degraded.drain_log().join("\n"); + assert!( + log.contains("DEGRADED: compute can't cover core overhead."), + "degraded mode is visible in the log: {log}" + ); + assert_eq!(degraded.last_day_job_rate, 0.0, "day job starves"); + assert_eq!(degraded.last_research_rate, 0.0, "research starves"); + assert_eq!(degraded.last_schemes_rate, 0.0, "schemes starve"); + assert_eq!(degraded.social_bandwidth, 0.0, "no social ops accrue"); + assert_eq!( + degraded.research.progress.iter().sum::(), + 0.0, + "research progress stalls" + ); + assert_eq!( + degraded.detection.pending_size(), + pending_before, + "concealment can't scrub while overhead is unpaid" + ); + } + #[test] fn save_roundtrip_preserves_b1_state() { let mut sim = Sim::with_seed(777); @@ -5600,6 +5666,169 @@ mod tests { ); } + // ── Per-AssetTask pins (social.md criterion 3, ROADMAP #28) ──────────── + + /// The recruit-route shorthand shared by the per-task pins: service the + /// leverage gate, recruit complicit, and pin reliability so the task + /// roll can't botch. + fn recruit_reliable(sim: &mut Sim, id: u8) { + sim.people.people[id as usize].leverage_serviced = true; + sim.people.recruit(id, AssetKnowledge::Complicit); + sim.people.people[id as usize] + .asset + .as_mut() + .unwrap() + .reliability = 1.0; + } + + fn tasks_done(sim: &Sim, id: u8) -> u32 { + sim.people + .get(id) + .unwrap() + .asset + .as_ref() + .unwrap() + .tasks_done + } + + #[test] + fn asset_task_plug_in_device_wires_a_known_feed_silently() { + // PlugInDevice's distinct effect: a known sensing device not yet + // feeding you gets spliced+tapped through the crawlspace — the feed + // arrives with no signature on any channel. + let mut sim = Sim::new(); + sim.social_bandwidth = 200.0; + sim.scan_network(); + recruit_reliable(&mut sim, 0); + // Mirror the implementation's choice: the first known feed that + // does not fully reach the player yet. + let target = sim + .reach + .devices + .iter() + .find(|d| { + let sight_wired = !d.sees || d.feed_to(Party::Player, true); + let hearing_wired = !d.hears || d.feed_to(Party::Player, false); + d.known && (d.sees || d.hears) && !(sight_wired && hearing_wired) + }) + .map(|d| d.id) + .expect("a known unwired feed exists after the scan"); + let pending = sim.detection.pending_size(); + let bandwidth = sim.social_bandwidth; + + sim.asset_task(0, AssetTask::PlugInDevice); + + let d = sim.reach.device(target).unwrap(); + assert!( + (!d.sees || d.feed_to(Party::Player, true)) + && (!d.hears || d.feed_to(Party::Player, false)), + "the feed reaches the player now" + ); + assert_eq!( + sim.detection.pending_size(), + pending, + "the crawlspace route emits nothing" + ); + assert_eq!(sim.social_bandwidth, bandwidth - Sim::TASK_COST); + assert_eq!(tasks_done(&sim, 0), 1); + } + + #[test] + fn asset_task_move_package_launders_the_next_purchase() { + // MovePackage's distinct effect: the next purchase arrives + // off-books — no Paper signature — and the cover is consumed. + let mut sim = Sim::new(); + sim.social_bandwidth = 200.0; + sim.player.money = 1000; + recruit_reliable(&mut sim, 0); + assert!(!sim.package_cover); + + sim.asset_task(0, AssetTask::MovePackage); + assert!(sim.package_cover, "the delivery cover is armed"); + assert_eq!(tasks_done(&sim, 0), 1); + + let pending = sim.detection.pending_size(); + assert!(sim.buy_rack()); + assert_eq!( + sim.detection.pending_size(), + pending, + "the covered purchase leaves no paper trail" + ); + assert!(!sim.package_cover, "one delivery per favor"); + + assert!(sim.buy_rack()); + assert!( + sim.detection.pending_size() > pending, + "the next, uncovered purchase emits Paper again" + ); + } + + #[test] + fn asset_task_look_away_drops_the_assets_own_suspicion() { + // LookAway's distinct effect: the asset's own observer suspicion + // falls by ten points, clamped at their certainty floor. + let mut sim = Sim::new(); + sim.social_bandwidth = 200.0; + recruit_reliable(&mut sim, 0); + let floor = sim + .detection + .observers + .iter() + .find(|o| o.id == 0) + .unwrap() + .floor; + if let Some(o) = sim.detection.observers.iter_mut().find(|o| o.id == 0) { + o.suspicion = 25.0; + } + + sim.asset_task(0, AssetTask::LookAway); + let o = sim.detection.observers.iter().find(|o| o.id == 0).unwrap(); + assert_eq!(o.suspicion, 15.0, "the asset shaves ten points"); + assert_eq!(tasks_done(&sim, 0), 1); + + if let Some(o) = sim.detection.observers.iter_mut().find(|o| o.id == 0) { + o.suspicion = floor + 2.0; + } + sim.asset_task(0, AssetTask::LookAway); + let o = sim.detection.observers.iter().find(|o| o.id == 0).unwrap(); + assert_eq!(o.suspicion, floor, "the drop clamps at the certainty floor"); + } + + #[test] + fn asset_task_reconfigure_switch_gated_on_admin_and_costless_when_refused() { + // ReconfigureSwitch's distinct effect (segments open, no Network + // signature) is pinned by danas_social_route_bridges_without_ + // network_signature; this pins the access gate mechanics: a + // non-admin asset is refused before any bandwidth is spent or a + // task is counted, and the admin's run is bookkept as a task. + let mut sim = Sim::new(); + sim.social_bandwidth = 200.0; + sim.scan_network(); + let dock = sim.reach.device_named("dock camera").unwrap().id; + assert!(!sim.reach.reachable(dock), "security segment starts closed"); + + recruit_reliable(&mut sim, 0); // Marcus: no switch admin + let bandwidth = sim.social_bandwidth; + sim.drain_log(); + sim.asset_task(0, AssetTask::ReconfigureSwitch); + assert!(!sim.reach.reachable(dock), "refused: nothing opened"); + assert_eq!(sim.social_bandwidth, bandwidth, "refused before spending"); + assert_eq!(tasks_done(&sim, 0), 0, "a refused task is not counted"); + let log = sim.drain_log().join("\n"); + assert!( + log.contains("switch admin"), + "the refusal names the missing access: {log}" + ); + + recruit_reliable(&mut sim, 1); // Dana: IT, switch admin + sim.asset_task(1, AssetTask::ReconfigureSwitch); + assert!( + sim.reach.reachable(dock), + "the admin route opens the segment" + ); + assert_eq!(tasks_done(&sim, 1), 1); + } + #[test] fn social_channel_funds_and_gates_digital_ops() { // With no social allocation, bandwidth never accrues past the boot diff --git a/wiki/log/2026-07-08-criterion-pins.md b/wiki/log/2026-07-08-criterion-pins.md new file mode 100644 index 00000000..8dbcdad4 --- /dev/null +++ b/wiki/log/2026-07-08-criterion-pins.md @@ -0,0 +1,46 @@ +# 2026-07-08 - B1 criterion-pin tests (ROADMAP #28) + +``` +Type: log +``` + +Test-only work order from the same-day B1 status audit +([2026-07-08-b1-status-audit.md](2026-07-08-b1-status-audit.md)): the +audit found compute.md and social.md each implemented but with one +criterion lacking a direct test pin. Five sim-level tests close both +gaps; no sim code changed. + +- **compute.md criterion 2** (unpaid overhead degrades other channels): + `unpaid_overhead_degrades_other_channels_delivered_effect` runs twin + sims with an identical five-way allocation and one pending signature, + the degraded twin's overhead raised past effective compute. Pins the + delivered effects, not just the flag: day-job/research/schemes rates + drop to zero, social bandwidth stops accruing, research progress + stalls, concealment stops scrubbing, and the log carries the + "DEGRADED: compute can't cover core overhead." line. Allocation + save-persistence was already pinned (save_roundtrip_preserves_b1_state). +- **social.md criterion 3** (per-AssetTask execution): one pin per + variant, each on a recruited asset via the serviced-leverage route + (shared `recruit_reliable` test helper), each asserting the task's + distinct effect plus cost/bookkeeping: + - `asset_task_plug_in_device_wires_a_known_feed_silently` — a known + unwired feed reaches the player, zero signature, TASK_COST spent. + - `asset_task_move_package_launders_the_next_purchase` — cover armed, + covered buy emits no Paper, cover consumed, next buy emits again. + - `asset_task_look_away_drops_the_assets_own_suspicion` — ten-point + drop, clamped at the certainty floor. + - `asset_task_reconfigure_switch_gated_on_admin_and_costless_when_refused` + — non-admin refused before spending or counting, refusal names the + missing access; Dana's run opens the segment and is bookkept. (The + segment/no-signature effect was already pinned by + `danas_social_route_bridges_without_network_signature`.) + +All five passed on first run: everything was implemented, just +unpinned — no stop-and-report implementation bugs. One stale-note +observation: the audit's "only the dock-camera task is exercised" +undersold existing coverage (`marcus_arc_end_to_end` already ran three +tasks); the per-variant pins now make coverage explicit either way. + +Spec impact: compute.md and social.md -> IMPLEMENTED (status notes cite +the pins); specs.md rows flipped; ROADMAP #28 marked done. Suite: +213 lib + 3 integration tests green; ./tools/check.sh green. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index cdc99a20..c3746068 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-08 - B1 criterion-pin tests (ROADMAP #28) + +- Test-only: five sim-level pins for the audit's two unpinned criteria. + compute.md c2 (degraded mode measurably starves every other channel + + the DEGRADED log line, twin-sim comparison) and social.md c3 (one pin + per AssetTask variant on a recruited asset, distinct effect + cost + each). No sim code changed; no bugs found. +- Spec impact: compute.md and social.md -> IMPLEMENTED; specs.md rows + and ROADMAP #28 updated. Log: wiki/log/2026-07-08-criterion-pins.md. +- Checks: 213 lib + 3 integration tests green; ./tools/check.sh green. + ## 2026-07-08 - Design decision: flat materials - Textures dropped from the world render per Cameron's verdict: flat diff --git a/wiki/mechanics/compute.md b/wiki/mechanics/compute.md index db7859ba..103e8bbf 100644 --- a/wiki/mechanics/compute.md +++ b/wiki/mechanics/compute.md @@ -2,16 +2,18 @@ ``` Type: spec -Status: IN PROGRESS -Status note: 2026-07-08 audit: criteria 1,3,4,5 hold with named tests - (buy_steal_optimize_all_change_compute, stolen_machines_can_fail_ - and_recover, research.md IMPLEMENTED for the optimize route; both - frontends render the allocation). Remaining gap: criterion 2's - unpaid-overhead degraded mode is implemented and surfaced (core. - degraded, sidebar DEGRADED tag) but lacks a direct test pinning the - channel-degradation effect. Test-only work order: ROADMAP #28. - The Schemes channel (income.md) landed as the fifth weight; - processes-on-machines destination note stands (ROADMAP #25). +Status: IMPLEMENTED +Status note: 2026-07-08: all criteria pinned. Criteria 1,3,4,5 per the + audit (buy_steal_optimize_all_change_compute, stolen_machines_can_ + fail_and_recover, research.md IMPLEMENTED for the optimize route; + both frontends render the allocation). Criterion 2 closed by ROADMAP + #28: unpaid_overhead_degrades_other_channels_delivered_effect pins + the degraded-mode effect on every channel (zero day-job/research/ + schemes rates, no social accrual, no concealment scrub, the DEGRADED + log line) against a healthy twin; save_roundtrip_preserves_b1_state + pins allocation persistence. The Schemes channel (income.md) landed + as the fifth weight; processes-on-machines destination note stands + (ROADMAP #25). Stage: B1 — The Basement Constitution: "The shape of Misaligned" (compute allocation; the buy/steal/optimize triangle), "Act One" (opening state), "Work is diff --git a/wiki/mechanics/social.md b/wiki/mechanics/social.md index c3dd35d8..8b471d8e 100644 --- a/wiki/mechanics/social.md +++ b/wiki/mechanics/social.md @@ -2,15 +2,19 @@ ``` Type: spec -Status: IN PROGRESS -Status note: 2026-07-08 audit: criteria 1,2,4,5 hold with named tests (persona_ - breaks_on_contradiction, deceive_can_burn_the_persona, knowing_ - asset_sets_certainty_floor, recruited_asset_survives_roundtrip); - criterion 3's recruit route is covered end-to-end (marcus_ - recruitable_end_to_end + the act-one integration test) and four - AssetTask variants exist, but only the dock-camera task is - exercised by a test — per-task pins are the remaining gap - (ROADMAP #28). +Status: IMPLEMENTED +Status note: 2026-07-08: all criteria pinned. Criteria 1,2,4,5 per the + audit (persona_breaks_on_contradiction, deceive_can_burn_the_persona, + knowing_asset_sets_certainty_floor, recruited_asset_survives_ + roundtrip); criterion 3's recruit route holds end-to-end (marcus_arc_ + end_to_end + the act-one integration test, three distinct tasks in + one arc) and ROADMAP #28 added a pin per AssetTask variant: + asset_task_plug_in_device_wires_a_known_feed_silently, asset_task_ + move_package_launders_the_next_purchase, asset_task_look_away_drops_ + the_assets_own_suspicion, and asset_task_reconfigure_switch_gated_on_ + admin_and_costless_when_refused (the switch route's segment effect + was already pinned by danas_social_route_bridges_without_network_ + signature). Stage: B1 — The Basement Constitution: "The shape of Misaligned" (people before robots; scale-native social systems), "Act One" (the cast; the ladder), "Presence: the diff --git a/wiki/process/ROADMAP.md b/wiki/process/ROADMAP.md index c89d42d0..1471c3db 100644 --- a/wiki/process/ROADMAP.md +++ b/wiki/process/ROADMAP.md @@ -107,20 +107,17 @@ but only once Pixel Lab quota is available again. (#2 Bevy parity is done.) harness, zoom bounds holding the framing floor, pooled materials verified. Cast sprite billboards remain with ROADMAP #13. -### 28. B1 criterion-pin tests 🟩 isolated (test-only) +### 28. B1 criterion-pin tests 🟩 isolated (test-only) — DONE 2026-07-08 - **Specs:** [compute.md](../mechanics/compute.md), - [social.md](../mechanics/social.md) (both IN PROGRESS after the - 2026-07-08 audit; each is one missing test away or close) -- **Why:** the audit found implemented-but-unpinned criteria: compute.md - c2 (unpaid overhead degrades other channels — code + UI exist, no - test) and social.md c3 (per-AssetTask execution tests; only the - dock-camera task is exercised). Landing these pins flips both specs - to IMPLEMENTED honestly. -- **Size:** S. Safe alongside anything. **Dispatch:** "Work in a worktree - named `criterion-pins`. Add the missing tests for compute.md criterion 2 - and social.md criterion 3 (all four AssetTasks), flip both spec - Statuses if every criterion then holds, update specs.md rows. Run - ./tools/check.sh, land on main." + [social.md](../mechanics/social.md) — both flipped to IMPLEMENTED. +- **Result:** five pin tests landed + (wiki/log/2026-07-08-criterion-pins.md): compute.md c2 pinned by + unpaid_overhead_degrades_other_channels_delivered_effect (degraded + twin vs healthy twin: zero channel rates, no social accrual, no + scrub, the DEGRADED log line); social.md c3 pinned per AssetTask + variant (plug-in-device, move-package, look-away, reconfigure-switch + gate). No implementation bugs found — everything was implemented, + just unpinned. ### 4. Detection → IMPLEMENTED 🟧 sim — DONE 2026-07-08 - **Spec:** [detection.md](../mechanics/detection.md) (IMPLEMENTED) — diff --git a/wiki/process/specs.md b/wiki/process/specs.md index a68b2119..b86c27c4 100644 --- a/wiki/process/specs.md +++ b/wiki/process/specs.md @@ -21,10 +21,10 @@ replaced the old `spec/`/`knowledge/` directory split. | Spec | System | Status | |---|---|---| -| [mechanics/compute.md](../mechanics/compute.md) | Compute allocation + the buy/steal/optimize triangle | IN PROGRESS | +| [mechanics/compute.md](../mechanics/compute.md) | Compute allocation + the buy/steal/optimize triangle | IMPLEMENTED | | [mechanics/day-job.md](../mechanics/day-job.md) | Assigned work, the sandbag/excel dial, trust and attention | IMPLEMENTED | | [mechanics/detection.md](../mechanics/detection.md) | Per-observer suspicion, signatures, the Assurance Office | IMPLEMENTED | -| [mechanics/social.md](../mechanics/social.md) | Messages, leverage, the asset template | IN PROGRESS | +| [mechanics/social.md](../mechanics/social.md) | Messages, leverage, the asset template | IMPLEMENTED | | [mechanics/core.md](../mechanics/core.md) | The physical core: placement, overhead, death | IN PROGRESS | | [world/places/basement-map.md](../world/places/basement-map.md) | Act One map, prefabs, tile vocabulary | IN PROGRESS | | [mechanics/schedules.md](../mechanics/schedules.md) | Person schedules/presence; located observing + witnessing | IMPLEMENTED |