L1: month-turn engine (#612)#636
Merged
Merged
Conversation
…ar badge ADR-0009 #612: introduce the MONTH as the plan cadence over the day-grained resolution tick (turn stays a workday; badge = calendar date, days-survived scoring unchanged). New MonthPlan (RefCounted): founder currency Attention (~20/month, Balance-driven), explicit reserve that evaporates crisply at month end (no banking), and duration-bearing queued strategic actions (nothing strategic resolves instantly). Composed onto GameState + serialized (L7). - Clock: month_index/is_month_boundary/month_ordinal/month_label helpers; annual_to_per_month; fix the L0 latent leap-day infinite loop (#620 note 2 — a date landing exactly on Feb 29 hung the rollover) across the 2017-2040 window. - HUD badge: retire 'Week 12 | ... | Day 3/5' (hung attention on the day tick, violating the guard rule) for the plan-month date badge. - Balance: attention.per_month, events.window_demand_budget(+endgame), unanswered_window_rep_penalty, delivery-tier/class defaults. Attention is introduced ALONGSIDE the legacy AP pool (L2 deletes AP and migrates cost dicts) — seam left clean. 20 new GUT tests (MonthPlan + Clock month/leap). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…schema bump Workshop#3 addendum #1-2, ADR-0009 §3, ADR-0012. The #630 flood fix made structural: every event genre is classified ambient / feed / window; only WINDOWS demand a decision, so the flood ceiling becomes a demand budget, not an information budget. - EventTiers: delivery-tier + event-class classifier (un-snoozable/deferrable/ standing/no-action), source_id provenance, legal-response derivation, and a partition() that splits a fired-event stream into the three tiers. - WindowResolver: the costed menu — HANDLE-from-reserve / HANDLE-by-cannibalizing (delays/kills planned WIP) / DEFER (mints a Ledger entry, deferrable class only) / IGNORE (list price). Unanswered windows auto-resolve as IGNORE + a mild, data-driven rep penalty; unignorable windows refuse DEFER and auto-ignore. Attention (via MonthPlan) is the window currency; legacy AP costs stripped. Routes doom only through option effects + ledger factories — ADR-0015 seam clean. - VerificationTracker: record_window_response appends {t,k:'w',ev,resp,pay} — the schema bump carrying payment_source. Replay artifact stamps the ADR-0016 league id beside seed+version. Format tag held at v1 (additive; unknown keys ignored). 15 new GUT tests. No regressions (70/70 touched suites). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-load ADR-0009 UI/acceptance: MonthController drives day-tick playback within a plan month, delegating the heavy sim to TurnManager and PAUSING on any window that demands a decision (auto-pause-on-window). Ambient/feed events never interrupt. - Window DEMAND budget enforced here (workshop#3 addendum #1): only N windows/month demand a decision (2-3 early, 5-6 endgame, Balance-driven); excess window-tier events downgrade to the feed. The structural #630 fix. - Month boundaries open a fresh plan phase: new Attention grant, crisp reserve evaporation, budget reset, duration-elapsed strategic WIP released (L2 seam). - Unanswered windows auto-resolve as IGNORE + mild rep penalty; unignorable refuse. - Open windows mirror into serialized pending_events so pause->save->load captures the pause point; rehydrate_from_state re-enters it after load. - GameManager: thin plan-layer API (get_month_plan / set_attention_reserve / queue_strategic_action / resolve_window) + replay league stamp. 9 new GUT tests incl. a headless playable-month-loop smoke and pause->save->load. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…he month out Playtest follow-up: the month engine existed but was unreachable — main_ui's End Turn still called end_turn() (single day-step), so the playtest exercised zero L1 behavior. The button now drives the month loop end to end: - GameManager.end_month(): commit queued actions as the month plan, default the reserve to all unspent Attention (implicit v1 — the plan screen makes it a dial), execute the open plan turn, then hand control to MonthController playback. - Day ticks advance visibly (day_tick_seconds, test-tunable); windows auto-pause and present through the EXISTING event_dialog (its extraction docstring promised exactly this reuse) with AP pre-stripped so displayed costs match what resolution charges (Attention). resolve_event routes paused-window choices through MonthController.resolve_current_window_option: the event's ignore option maps to IGNORE; any other option is a HANDLE paid reserve-first then by cannibalizing — payment_source still lands in the replay artifact. - Month boundary: the boundary tick is HELD OPEN as the new plan phase (month_open_pending) — never auto-executed, or its consequence steps would double-run when the plan commits. A plain month-review dialog (synthetic event, intercepted in resolve_event) closes into planning. - Feed items surface as log lines; ambient stays silent. Guard rule holds: only windows pause the day ticks. - Old single day-step survives ONLY behind the DEV MODE overlay (relabelled 'Day step (dev — old path)'); end_turn() itself is unchanged (tests pin it). - Save loaded mid-pause: controller rehydrates; answering the window resumes playback (resolve_event branch not gated on playback_active). Tests: new test_month_button_path.gd — headless smoke driving a FULL month via the exact button API (plan commit -> ticks -> auto-pause -> review -> next plan phase), with a doom clamp so pre-rebalance drift can't kill the run before the boundary. +2 controller tests (boundary hold-open, option->verb mapping). Full unit suite: 402/402 passing, 39 GUT scripts (no #590 hidden parse failures). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… marked The baked build_stamp.txt (fd60eb6 · 2026-07-11) was two days stale on every branch and cost a playtest session to build confusion. Dev checkouts now read the REAL identity at runtime: BuildInfo.get_live_git_stamp() shells git -C <repo-root> rev-parse (gated on a .git dir/file existing, so exported builds never try; cached after one probe; worktrees resolve correctly since git handles the .git file). Badge forms, each self-identifying: - dev checkout: 'DEV BUILD v0.11.0 · l1-month-turn-engine@e4cd408 · live' - exported/no git: '... · fd60eb6 · 2026-07-11 (stamp)' - never stamped: '... · unstamped' Acceptance: two different checkouts can never show the same badge silently — live HEAD differs per checkout, and a stamped badge visibly declares it is a stamp. A stale stamp additionally prints a console note (print, not push_warning: in a dev checkout the stamp is stale after every commit by construction; GUT/CI must not count that as an engine error). +2 badge tests (live identity present in a git checkout; every badge form declares its provenance). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…it open Balance-sweep finding: MonthController.resolve_current_window() (the four-verb path) popped the window off the queue BEFORE WindowResolver.resolve validated payment, and never re-pushed on failure — handle_reserve with an empty reserve silently dropped the window (no effect, no charge, no window, playback stuck paused on an empty queue). Latent in the shipped v1 dialog (which routes through resolve_current_window_option, whose guard is correct) but fatal to the future plan-screen UI. Mirror the option path's guard: peek the head, resolve, pop only on success. skip_current_window hardened with the same shape (its unignorable pre-check made it safe today; the pop-before-validate trap is now gone everywhere). Regression test: handle_reserve with empty reserve -> fails, window still queued AND still in the serialized pause point, then resolves via handle_cannibalize. Full unit suite: 403/403 passing, 39 GUT scripts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PipFoweraker
added a commit
that referenced
this pull request
Jul 14, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PipFoweraker
added a commit
that referenced
this pull request
Jul 14, 2026
* feat(L1): month plan layer — Attention economy, crisp reserve, calendar badge ADR-0009 #612: introduce the MONTH as the plan cadence over the day-grained resolution tick (turn stays a workday; badge = calendar date, days-survived scoring unchanged). New MonthPlan (RefCounted): founder currency Attention (~20/month, Balance-driven), explicit reserve that evaporates crisply at month end (no banking), and duration-bearing queued strategic actions (nothing strategic resolves instantly). Composed onto GameState + serialized (L7). - Clock: month_index/is_month_boundary/month_ordinal/month_label helpers; annual_to_per_month; fix the L0 latent leap-day infinite loop (#620 note 2 — a date landing exactly on Feb 29 hung the rollover) across the 2017-2040 window. - HUD badge: retire 'Week 12 | ... | Day 3/5' (hung attention on the day tick, violating the guard rule) for the plan-month date badge. - Balance: attention.per_month, events.window_demand_budget(+endgame), unanswered_window_rep_penalty, delivery-tier/class defaults. Attention is introduced ALONGSIDE the legacy AP pool (L2 deletes AP and migrates cost dicts) — seam left clean. 20 new GUT tests (MonthPlan + Clock month/leap). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(L1): event delivery tiers + response-window resolution + replay schema bump Workshop#3 addendum #1-2, ADR-0009 §3, ADR-0012. The #630 flood fix made structural: every event genre is classified ambient / feed / window; only WINDOWS demand a decision, so the flood ceiling becomes a demand budget, not an information budget. - EventTiers: delivery-tier + event-class classifier (un-snoozable/deferrable/ standing/no-action), source_id provenance, legal-response derivation, and a partition() that splits a fired-event stream into the three tiers. - WindowResolver: the costed menu — HANDLE-from-reserve / HANDLE-by-cannibalizing (delays/kills planned WIP) / DEFER (mints a Ledger entry, deferrable class only) / IGNORE (list price). Unanswered windows auto-resolve as IGNORE + a mild, data-driven rep penalty; unignorable windows refuse DEFER and auto-ignore. Attention (via MonthPlan) is the window currency; legacy AP costs stripped. Routes doom only through option effects + ledger factories — ADR-0015 seam clean. - VerificationTracker: record_window_response appends {t,k:'w',ev,resp,pay} — the schema bump carrying payment_source. Replay artifact stamps the ADR-0016 league id beside seed+version. Format tag held at v1 (additive; unknown keys ignored). 15 new GUT tests. No regressions (70/70 touched suites). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(L1): month-loop playback controller + plan-layer API + pause-save-load ADR-0009 UI/acceptance: MonthController drives day-tick playback within a plan month, delegating the heavy sim to TurnManager and PAUSING on any window that demands a decision (auto-pause-on-window). Ambient/feed events never interrupt. - Window DEMAND budget enforced here (workshop#3 addendum #1): only N windows/month demand a decision (2-3 early, 5-6 endgame, Balance-driven); excess window-tier events downgrade to the feed. The structural #630 fix. - Month boundaries open a fresh plan phase: new Attention grant, crisp reserve evaporation, budget reset, duration-elapsed strategic WIP released (L2 seam). - Unanswered windows auto-resolve as IGNORE + mild rep penalty; unignorable refuse. - Open windows mirror into serialized pending_events so pause->save->load captures the pause point; rehydrate_from_state re-enters it after load. - GameManager: thin plan-layer API (get_month_plan / set_attention_reserve / queue_strategic_action / resolve_window) + replay league stamp. 9 new GUT tests incl. a headless playable-month-loop smoke and pause->save->load. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(L1): playable month path — End Turn commits the plan and plays the month out Playtest follow-up: the month engine existed but was unreachable — main_ui's End Turn still called end_turn() (single day-step), so the playtest exercised zero L1 behavior. The button now drives the month loop end to end: - GameManager.end_month(): commit queued actions as the month plan, default the reserve to all unspent Attention (implicit v1 — the plan screen makes it a dial), execute the open plan turn, then hand control to MonthController playback. - Day ticks advance visibly (day_tick_seconds, test-tunable); windows auto-pause and present through the EXISTING event_dialog (its extraction docstring promised exactly this reuse) with AP pre-stripped so displayed costs match what resolution charges (Attention). resolve_event routes paused-window choices through MonthController.resolve_current_window_option: the event's ignore option maps to IGNORE; any other option is a HANDLE paid reserve-first then by cannibalizing — payment_source still lands in the replay artifact. - Month boundary: the boundary tick is HELD OPEN as the new plan phase (month_open_pending) — never auto-executed, or its consequence steps would double-run when the plan commits. A plain month-review dialog (synthetic event, intercepted in resolve_event) closes into planning. - Feed items surface as log lines; ambient stays silent. Guard rule holds: only windows pause the day ticks. - Old single day-step survives ONLY behind the DEV MODE overlay (relabelled 'Day step (dev — old path)'); end_turn() itself is unchanged (tests pin it). - Save loaded mid-pause: controller rehydrates; answering the window resumes playback (resolve_event branch not gated on playback_active). Tests: new test_month_button_path.gd — headless smoke driving a FULL month via the exact button API (plan commit -> ticks -> auto-pause -> review -> next plan phase), with a doom clamp so pre-rebalance drift can't kill the run before the boundary. +2 controller tests (boundary hold-open, option->verb mapping). Full unit suite: 402/402 passing, 39 GUT scripts (no #590 hidden parse failures). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dev): honest build badge — live git HEAD in dev, stamped fallback marked The baked build_stamp.txt (fd60eb6 · 2026-07-11) was two days stale on every branch and cost a playtest session to build confusion. Dev checkouts now read the REAL identity at runtime: BuildInfo.get_live_git_stamp() shells git -C <repo-root> rev-parse (gated on a .git dir/file existing, so exported builds never try; cached after one probe; worktrees resolve correctly since git handles the .git file). Badge forms, each self-identifying: - dev checkout: 'DEV BUILD v0.11.0 · l1-month-turn-engine@e4cd408 · live' - exported/no git: '... · fd60eb6 · 2026-07-11 (stamp)' - never stamped: '... · unstamped' Acceptance: two different checkouts can never show the same badge silently — live HEAD differs per checkout, and a stamped badge visibly declares it is a stamp. A stale stamp additionally prints a console note (print, not push_warning: in a dev checkout the stamp is stale after every commit by construction; GUT/CI must not count that as an engine error). +2 badge tests (live identity present in a git checkout; every badge form declares its provenance). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * balance: L1 month-cycle sweep — data + memo (no constants changed) Structured bot sweep over the new L1 month cycle (PR #636). 72 deterministic runs across 6 plan/response policies through the real MonthController playback (not clamped). Adds the harness + the balance memo + raw per-run CSV. Changes no balance constants — measures and recommends. Headline: no policy (incl. do-nothing) survives even the first plan-month — all runs die of doom at workday-tick 2-15 while a month is ~23 ticks. Dominant doom source is rival-lab pressure billed per day-tick (sized for the pre-L1 strategic turn, now billing ~23x/month) — the re-denomination pass ADR-0009 flagged is still open. DEFER can add +99..+143 doom in one tick via a desperation_payroll ledger bill (8/8 ledger-rooted deaths, corrected classifier). Reserve is inert (Attention ~6x oversupplied vs window demand). Also documents a latent #636 wiring bug (not fixed): resolve_current_window pops the window before validating payment, silently dropping it on a failed reserve payment; resolve_current_window_option guards correctly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * balance: L1 recalibration — dials 1-4 applied + iteration log Re-denominates the per-day-tick doom/ledger pressures that were sized for the pre-L1 strategic turn (ADR-0009) so the month cadence is survivable. After 5 measured 72-run sweeps: do_nothing 14 mo (target 12-18), greedy dies fastest-but-not-instantly, the ledger bites legibly over months (not a one-tick guillotine), and every policy still dies (max 28 mo — ADR-0002). Two load-bearing findings surfaced and are documented for Pip's veto: - The ledger's doom teeth were INERT: _step_resolve_doom overwrites state.doom = doom_system.current_doom every tick, clobbering the ledger's direct doom writes. Routed ledger doom through doom_system so DEFER's cost actually lands (capped per bill + rolled over months = loan semantics). - Safety's beats-passive margin (~1.1x, target 1.5-2.5x) is capped by the bot's monthly-overhiring bankruptcy — a money/salary-economy effect outside dials 1-4. Dials: (1) rival + player-researcher/base/unproductive doom re-denomination; (2) start doom 50->20; (3) DEFER/ledger priced as a loan — per-bill doom+rep caps with month-spread rollover, ledger fuses/interest/exposure re-denominated; (4) momentum re-tune. Dial 5 (Attention) untouched (out of scope). Harness: MAX_MONTHS 60->420 (ADR-0002 detection), m1-m6 slope/CSV columns, MORTALITY_CHECK. Stale magic-number assertions in doom_system/ledger_actions/ death_attribution reworked to read Balance (tuning-robust). Full memo + per-iteration log + re-narrated lived experiences: docs/balance/L1_CALIBRATION_2026-07-14.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(balance): refresh L1 calibration memo + CSV to post-merge numbers Regenerated the 72-run sweep on the merge with main (event-outcome fixes + window-pop guard). do_nothing 14 mo (unchanged), safety_lean 20 / reserve_heavy 19 (main's event economy lifted safety from the pre-merge 1.1x to ~1.4x — a near-miss on 1.5-2.5x, still bankruptcy/variance-bound: a +25% safety-coefficient probe did not move the median). Mortality max 45 mo, PASS. Narratives re-narrated from the merged runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * balance: T9 floor — no standard run completes ruin before month 6 Pip's T9 ruling (#638 review): the early game must be nearly unlosable w.r.t. compounding debt — ruin can be SET UP early but not COMPLETED early. Levers were dial-3 completion territory + remaining per-tick cadence artifacts, never ambient doom (do_nothing stays 14 mo, byte-identical). - risk.trigger_scale_per_tick (NEW, 0.045): risk-pool trigger probability pool/100 was per DAY-TICK — a pool of 40 fired ~9 events/month. Re-denominated so pool N reads ~N%/MONTH. Threshold crossings stay guaranteed one-shots. (This was greedy_overcommit's hidden +2-doom drip; 5 -> 7 mo.) - ledger fuses stretched (loan 44->77, desperation 33->55, strings 66->88 ticks) + interest 0.012->0.009; funding_strings principal x0.15->x0.10; doom_per_unpaid_1000 1.5->0.9. - per-bill caps 15->10 doom AND rep (the monthly Ponzi-collapse loan PAIR now lands +20, not +30); expose.rep_per_1000 4.0->2.5 (the last sub-6 run was a rep-collapse via secret exposures). - ledger.rollover_fuse_ticks (NEW, 8): residual doom beyond the cap bleeds every ~9 ticks instead of every 2 — a matured mega-debt takes months to finish you, forgives nothing (ADR-0002 intact). Momentum (dial 4, ruled a low-commitment SWITCH): doom.momentum_enabled + doom.momentum_weight Balance keys; accumulators keep ticking while disabled so mid-run toggles behave. Final: every standard policy survives >=6 full months (loan_desperation 6-7, greedy 6-13); do_nothing 14; mortality max 45 mo, 0 immortal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: save/load float determinism + pre-L1 simulator caps; memo rewrite per #638 rulings Save/load determinism (found by the newly-real CI gate, #640): - Godot's JSON float parse is not correctly-rounded — full-precision floats can come back from a save 1 ulp off. Probe-verified: 0.009/0.015/0.045 corrupt, 0.008/0.0025/0.25 survive. Ledger interest rates moved to round-trip-safe decimals (0.009 -> 0.008) and principals quantized to whole units at mint + each compound step (integer-valued doubles round-trip exactly; self-healing). Gotcha documented in defaults.json + memo open question. Pre-L1 cap relics (T9-adjacent cadence artifacts): - ReplaySimulator/BaselineSimulator MAX_TURNS 200 -> 10000: 200 DAY-TICKS is ~9 fiction-months, so post-recalibration the verifier would truncate — and reject — every honest long run, and the baseline score truncated every game. - test_replay_verification: scripted runs must actually reach game_over (ADR-0006 score is defined at game over); max_turns 40 -> 500. - test_game_state: start-doom + clamp assertions read Balance (dial 2). Memo rewritten per the #638 review rulings: T1-T9 each carry reasoned context; T6 plain-language explainer (what DEFER minted, why +99-143 was an accounting collision, what cap+rollover means in play, why 10-15); momentum explainer box + switch semantics; ledger doom-routing approval recorded with the ADR-0005/ADR-0015 one-authority rationale; Legacy-variables shopping list (15 items) for the migration lane; T9 iteration log; final tables + narratives re-narrated from final-constants runs. All three CI gates pass locally: quick 333/0, integration 14/0, simulation 93/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
4 tasks
PipFoweraker
added a commit
that referenced
this pull request
Jul 15, 2026
Verified current main (post #636/#638/#639/#640/#641/#643 merge stack) launches clean headless, plays a full month through the End Turn -> MonthController path, and passes the fast unit gate CI runs (345 tests, 0 failures, 36/36 files). Nine-stream doom, finance offers, and flight recorder all pass their unit suites with no wiring gaps found. No code changes needed -- green light for tomorrow. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
PipFoweraker
added a commit
that referenced
this pull request
Jul 15, 2026
Developer-facing architecture map (audience: future junior dev). Maps each major system to its code and deciding ADR(s), with honest built/partial/designed status. Preserves the prior funder pitch at ARCHITECTURE_FUNDERS.md. Written against the L1 wave (month engine #636, nine-stream doom #643, finance #641, calibration #638, honest CI #640). Surfaces coherence-check findings in §7. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
L1 — Month-turn engine (#612)
Implements the ADR-0009 plan-months / two-speeds turn structure plus the workshop #3 spec addendum. The month is the plan cadence; the day-turn is demoted to a resolution tick beneath it (badge = calendar date; days-survived scoring stays fine-grained). Built as a layer over the existing day-tick engine rather than a re-grain — the sim substrate, RNG stream, and recorded replays are untouched, so this stays green and non-degrading.
What landed (per SCOPE item)
Clockgains month helpers (month_index/is_month_boundary/month_ordinal/month_label). The HUD badge retiresWeek 12 | … | Day 3/5(which hung attention on the day tick, violating the guard rule) for the plan-month date badge. Also fixes the latent leap-day infinite loop (Build lane L0 · Pre-L1 stabilization: one GameManager, one clock, split turn monoliths (behavior-preserving) #620 note 2) that would have hung on Feb-29 dates across the 2017–2040 window. Guard rule honoured: no routine decision hangs on the day tick.MonthPlan: founder currency Attention (~20/month,Balance.attention.per_month, not hardcoded); explicit reserve that evaporates crisply at month end (no banking); queued strategic actions with durations (nothing strategic resolves instantly). Attention is introduced alongside the legacy AP pool (L2 owns deleting AP + migrating cost dicts — seam left clean, no big-bang). Staff actions remain per-person/separate.EventTiersclassifies every genre ambient / feed / window withsource_idprovenance on feed items; only windows demand a decision.WindowResolverimplements the costed menu HANDLE-from-reserve / HANDLE-by-cannibalizing / DEFER (mints a Ledger entry, deferrable class only) / IGNORE (list price); unignorable flag supported; unanswered windows auto-resolve as IGNORE + a mild, data-driven rep penalty. Window demand budget 2–3/month → 5–6 endgame (Balance-driven) enforced in the controller.MonthControlleradvances ticks throughTurnManager, routes events by tier, pauses on windows, resets the plan phase at month boundaries, and releases duration-elapsed WIP.MonthPlanserialized intoGameState.to_dict/from_dict; a pause→save→load smoke test proves an open window + reserve + WIP round-trip and playback resumes.record_window_responsebumps the schema withpayment_source; the league (month) id is stamped beside (seed, game_version). Format tag held atv1(additive; unknown log keys are ignored by the v1 simulator, so pre-L1 artifacts stay verifiable).Out of scope (left clean): ADR-0015 doom-function migration is Lane 2 — this resolver/schema never reads/writes doom directly (routes through option effects + ledger factories only). The full new plan scene UI (explicit reserve dial, four-verb window menu incl. DEFER) remains attrition work — but the month loop is now reachable from the playable scene (see below).
Playable month path (playtest follow-up)
Pip's first playtest hit the gap: the engine existed but End Turn still called
end_turn()(single day-step) — zero L1 behavior reachable. Fixed (e4cd408):GameManager.end_month(): commit queued actions as the month plan → implicit reserve = all unspent Attention (v1 default; the plan screen makes it a dial) → execute the open plan turn → day-tick playback (visible date advance,day_tick_secondspacing).event_dialog(the L10 extraction built for exactly this), AP pre-stripped so displayed costs match what resolution charges. Choices route throughMonthController.resolve_current_window_option: the event's ignore option → IGNORE; any other option → HANDLE paid reserve-first, then by cannibalizing —payment_sourcestill lands in the replay artifact. (The explicit four-verb menu incl. DEFER is the plan screen's job.)month_open_pending) — never auto-executed, or its consequence steps would double-run when the plan commits. A plain month-review dialog closes into planning. Loop: plan → play → review → plan.end_turn()semantics unchanged (tests pin it). Guard rule holds: only windows pause the day ticks.source_id; ambient stays silent.Honest build badge (playtest follow-up)
The baked
build_stamp.txt(fd60eb6 · 2026-07-11) was stale on every branch and cost the playtest session (a4e7661). Dev checkouts now read live git HEAD at runtime (git -C <repo-root> rev-parse, gated on.gitexisting so exported builds never shell out; cached; worktree-safe): badge readsDEV BUILD v0.11.0 · l1-month-turn-engine@a4e7661 · live. Exported/no-git builds fall back to the baked stamp explicitly marked(stamp)— no badge form can pass as something it isn't, so two checkouts can never show the same badge silently. A stale stamp prints a console note.Window-drop fix (balance-sweep finding)
4269c0b:MonthController.resolve_current_window()(the four-verb path) popped the window off the queue beforeWindowResolver.resolvevalidated payment and never re-pushed on failure —handle_reservewith an empty reserve silently dropped the window (no effect, no charge, no window). Latent in the shipped v1 dialog (which routes through the correctly-guarded option path) but fatal to the future plan-screen UI. Fixed by mirroring the option path's guard — peek, resolve, pop only on success;skip_current_windowhardened with the same shape. Regression test: failedhandle_reserveleaves the window queued (and in the serialized pause point), then resolves viahandle_cannibalize.Local GUT results
CI's test gate is hollow (#629) — these are real local runs (Godot 4.5.1, after a headless
--importpass):tests/unit/run: 403/403 passing, 0 failures, 39 GUT scripts (the 40th unit file,test_verification_determinism.gd,extends Node— a pre-existing non-GUT script — so no Test suite: 14 order-dependent failures in full local run (singleton leakage), invisible to CI #590 hidden parse-failures).test_clock_month(leap-day + month helpers),test_month_plan(Attention/reserve/durations/serialization),test_event_tiers,test_window_resolver,test_month_controller(demand budget, auto-pause, boundary hold-open, option→verb mapping, headless playable-loop smoke),test_month_save_load(pause→save→load),test_month_button_path(a headless smoke driving a FULL month through the exact End-Turn-button API: plan commit → ticks → auto-pause → review → next plan phase).test_game_manager35/35 (end_turn semantics pinned + unchanged),test_replay_verification,test_save_load_roundtrip,test_game_state,test_events,test_engine_determinism,test_salary_cadence,test_liability_ledger,test_dev_build_indicator(+2 badge-honesty tests).Schema decisions to eyeball
delivery_tier,event_class,source_id,unignorable,expiry_turns, and awindow{ attention_cost, handle_option, ignore_option, defer{factory,amount} }block. Un-annotated legacy popups default to window (behaviour-preserving).action_pointscosts — Attention is the window decision currency.YYYY-MMof the run start month, pending the ADR-0016 pipeline.months_per_turn()stays 1.0 (turn = workday) — the month is a layer, not a re-grain; this keeps risk/doom calibration and replays intact. Noted in-code for L2.🤖 Generated with Claude Code