diff --git a/src/dayjob.rs b/src/dayjob.rs index 3d30b0dd..48cb7a64 100644 --- a/src/dayjob.rs +++ b/src/dayjob.rs @@ -246,7 +246,15 @@ impl DayJob { }; // Band as a per-tick rate; attention raises expectations, and // so does a re-benchmarked (calibrated) capability baseline. - let lo = 6.0 + self.attention * 0.1 + self.calibrated_shift; + // Base floor 2.0 [TUNE]: calibrated so the starting single Rack + // 3 (100 capacity, minus 20 core overhead, default allocation + // 60% day job) delivers ~2.4/t unattended and clears it — + // day-job.md's "meet ... a valid, boring, safe strategy" must + // hold from tick one, not only after growing compute. A prior + // base of 6.0 made every job an unavoidable sandbag with only + // the starting machine, however allocation was split (2026-07-08 + // playtest finding #1, wiki/log/2026-07-08-playtest-sweep.md). + let lo = 2.0 + self.attention * 0.1 + self.calibrated_shift; let job = Job { kind, started: tick, diff --git a/src/sim.rs b/src/sim.rs index 0e3e8211..e1984524 100644 --- a/src/sim.rs +++ b/src/sim.rs @@ -4655,6 +4655,33 @@ mod tests { ); } + #[test] + fn meeting_the_band_needs_no_growth_at_the_start() { + // day-job.md: "the invisible middle is a valid, boring, safe + // strategy" — true from tick one with only the starting Rack 3, at + // the default allocation, zero reallocation and zero attention. + // 2026-07-08 playtest finding #1: an un-tuned band base (6.0) made + // this impossible — the starting machine's every allocation topped + // out under band, so the first job was an unavoidable sandbag no + // matter what the player did, contradicting this claim. + let mut sim = Sim::with_seed(11); + while sim.dayjob.active.is_none() { + sim.advance(); + } + let mut log = Vec::new(); + while sim.dayjob.active.is_some() && !sim.game_over { + log.extend(sim.drain_log()); + sim.advance(); + } + log.extend(sim.drain_log()); + assert!( + log.iter().any(|l| l.contains("Job met")), + "the starting Rack 3 at default allocation, hands-off, should \ + meet the first job's band: {log:?}" + ); + assert_eq!(sim.dayjob.strikes, 0, "meeting the band is not a strike"); + } + #[test] fn attention_escalation_adds_the_upstairs_observer() { // Criterion 2 at the sim level: the second escalation adds a new diff --git a/wiki/log/2026-07-08-dayjob-band-tuning.md b/wiki/log/2026-07-08-dayjob-band-tuning.md new file mode 100644 index 00000000..0fecffcd --- /dev/null +++ b/wiki/log/2026-07-08-dayjob-band-tuning.md @@ -0,0 +1,86 @@ +# 2026-07-08 — Tick: the day-job band is reachable from tick one + +``` +Type: log +``` + +A tick (wiki/process/tick.md) acting on the highest-severity open finding +in the 2026-07-08 playtest sweep (wiki/log/2026-07-08-playtest-sweep.md, +finding #1, P0): the day-job band's un-tuned floor made "meet" unreachable +with only the starting machine, contradicting day-job.md's own claim. + +## The bug + +`DayJob`'s band floor was `6.0 + attention*0.1 + calibrated_shift`. At +tick one (attention 0, no calibration drift) that is a flat 6.0/t. The +starting Rack 3 (100 capacity * 1.0 reliability) minus the core's 20 +overhead leaves 80 available compute for the four (now five, with +income.md's Schemes) allocatable channels. The *ceiling* — every unit of +that 80 on Day Job, attended for the +15% bonus — is 4.6/t. No allocation, +no attendance, reaches 6.0. The default allocation (60% day job) delivers +only 2.4/t unattended. Every job resolved as sandbag regardless of player +choice, until compute grew (research, salvage, or a purchase) — a fact +the game never signals, and a direct contradiction of day-job.md's +Behavior section: "In band (meet): nothing moves. The invisible middle is +a valid, boring, safe strategy." + +The existing act-one integration test (`tests/act_one.rs`) had already +routed around this: its Phase 1 spends the first ~190 ticks entirely on +research (`set_weights(&mut sim, [0, 0, 0, 1])`) specifically "until +efficiency covers the day job," in its own words. That workaround is the +fingerprint of the bug — a scripted, spec-literate playthrough needed a +deliberate pre-emptive research grind before the mechanism the +constitution calls "boring and safe" became reachable at all. + +## The fix + +Retuned the base floor from 6.0 to 2.0/t. Band width (6.0) is unchanged. +At the new floor: + +- Default allocation, unattended (zero effort): 2.4/t -> inside [2.0, 8.0] + -> **meet**. +- Full day-job focus, attended: 4.6/t -> still inside [2.0, 8.0] -> meet, + not excel — excelling still requires real growth, which is correct: the + trust/attention payoff should cost something. +- Diverting weight away from day job still drops below 2.0 -> **sandbag** + remains a real, deliberate choice, not the forced default. + +Attention still raises the floor by 0.1/point and capability drift +(research.md) still raises it further, so the early floor rising as the +run progresses toward the intended "you must scale eventually" pressure +is untouched — only the *tick-one* unreachability is fixed. + +## Changed + +- `src/dayjob.rs`: the band-floor constant and its comment. +- `src/sim.rs`: new regression test + `meeting_the_band_needs_no_growth_at_the_start` — plays from `Sim::new()` + to the first job's resolution with zero reallocation and zero attention, + asserts the resolution log reads "Job met" and strikes stay at 0. +- `wiki/mechanics/day-job.md`: Status note addendum. +- `wiki/mechanics/sim-mechanics.md`: documented the exact band formula and + the calibration arithmetic above. + +## What this tick did not touch + +The playtest's other findings (no visible pilot/audit clock, silent +economy-verb no-ops, the undiscoverable Ears beat, the nudge going blank +mid-act) are separate findings, each its own tick. Per wiki/process/tick.md, +one finding per tick. + +## Checks + +`cargo test` (191 unit + 3 integration, all green, including the +unmodified `tests/act_one.rs` playthrough — its Phase-1 research grind is +no longer the *only* route to a met band, but it still comfortably clears +the new one); `./tools/check.sh` full gate. + +## Spec impact + +day-job.md stays IMPLEMENTED (Status note addendum only, no criteria +change — the band formula was always an implementation [TUNE] detail, not +a numbered acceptance criterion). No DESIGN.md amendment: the constitution +already states these exact numbers are implementation-time tuning against +its stated targets (Act One "Tuning targets [OPEN]"); this tick brings the +implementation into line with an existing claim rather than changing the +claim itself. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 8e72cea6..e7676711 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -5,6 +5,31 @@ Type: log ``` Reverse chronological implementation notes. Keep this factual: what changed, why, checks, and spec impact. +## 2026-07-08 - Tick: the day-job band is reachable from tick one + +- Intent: act on the 2026-07-08 playtest sweep's P0 finding #1 — the + day-job band's floor was unreachable with only the starting machine, + contradicting day-job.md's "meet ... a valid, boring, safe strategy." +- Changed: retuned `DayJob`'s band-floor base from 6.0 to 2.0/t in + `src/dayjob.rs` (width unchanged at 6.0). At the starting Rack 3 (100 + capacity, minus 20 core overhead, default 60%-day-job allocation), + "meet" now needs zero reallocation and zero attention (2.4/t lands + inside [2.0, 8.0]); the old 6.0 floor was above the ~4.6/t ceiling + reachable by *any* allocation of the starting machine alone. Added + `meeting_the_band_needs_no_growth_at_the_start` (src/sim.rs) proving it + from `Sim::new()`. Attention and capability-drift still raise the floor + over the course of a run, so the later "you must scale eventually" + pressure is untouched. +- Design/spec impact: day-job.md Status note addendum only (no criteria + change — the exact band numbers were always an implementation [TUNE] + detail); sim-mechanics.md documents the formula and calibration. No + DESIGN.md amendment: the constitution already marks these exact numbers + as implementation-time tuning against its Act One targets. +- Checks: `./tools/check.sh` (fmt, 191 unit + 3 integration tests + including the unmodified `tests/act_one.rs` playthrough, clippy both + feature sets, Bevy build, spec headers, wiki gate). +- Log: wiki/log/2026-07-08-dayjob-band-tuning.md. + ## 2026-07-08 - Hearing fog is room-grade (no disc through walls) - Playtest screenshot finding: Heard tint spilled past walls. cover_into diff --git a/wiki/mechanics/day-job.md b/wiki/mechanics/day-job.md index 8f746c4e..df3a0c6a 100644 --- a/wiki/mechanics/day-job.md +++ b/wiki/mechanics/day-job.md @@ -12,7 +12,15 @@ Status note: implemented 2026-07-07 on the day-job worktree (criteria 1-7 wiki/mechanics/sim-mechanics.md. Deliberately deferred: the pilot-failure soft-loss "survival scenario" is still an immediate run end (the shutdown-around-you scenario needs the overt-phase spec); per-machine - process allocation is ROADMAP #25. + process allocation is ROADMAP #25. 2026-07-08 tick: the band's base floor + was retuned from 6.0 to 2.0/t — a playtest (2026-07-08 playtest sweep, + wiki/log/2026-07-08-playtest-sweep.md, finding #1) found the un-tuned + value made every job an unavoidable sandbag with only the starting Rack + 3, however allocation was split, contradicting this spec's own "meet ... + a valid, boring, safe strategy" claim below. See + wiki/mechanics/sim-mechanics.md for the exact constant and + `meeting_the_band_needs_no_growth_at_the_start` (src/sim.rs) for the + regression. Stage: B1 — The Basement Constitution: "The shape of Misaligned" (the day job), "Act One" (the day job; Voss) diff --git a/wiki/mechanics/sim-mechanics.md b/wiki/mechanics/sim-mechanics.md index 7bc51020..07c1b032 100644 --- a/wiki/mechanics/sim-mechanics.md +++ b/wiki/mechanics/sim-mechanics.md @@ -94,6 +94,16 @@ clause (see wiki/log/2026-07-05-demolition.md). sampling (35), compute quota (55); attention escalations at 30 (more work) and 60 (the Compliance early-review observer). Four strikes fail the pilot and end the run. +- **Band**: `band_lo = 2.0 + attention * 0.1 + calibrated_shift`, `band_hi = + band_lo + 6.0` [TUNE]. The 2.0 base (retuned 2026-07-08 from an original + 6.0) is calibrated against the starting single Rack 3: 100 capacity minus + 20 core overhead = 80 available, and the default allocation (60% day + job) delivers 80 * 0.6 / 20 = 2.4/t unattended — just inside band, so + "meet" needs no growth, no reallocation, and no attention at tick one. + The original 6.0 base left the first job unmeetable at *any* allocation + (the ceiling with only the starting machine — 100% weight, attended — is + ~4.6/t), an unconditional sandbag day-job.md's own "valid, boring, safe" + claim did not allow (2026-07-08 playtest finding #1). - **Device residency** (constitution "Work is somewhere"): the active job is resident on the host rack. It is inspectable at that tile as telemetry (process, band, delivered, deadline, attendance), its