diff --git a/.claude/gates.json b/.claude/gates.json index bccbdf9..b999c9d 100644 --- a/.claude/gates.json +++ b/.claude/gates.json @@ -1,6 +1,6 @@ { - "manifestHash": "fe74684ae7ca549e563e429d0ce2ae9099f71b3e7dc61db78a48dd7dc7257456", - "generated": "2026-08-20", + "manifestHash": "9709a22838ae6c721e1bc8cb2777f587a7b02758ca2eabcb0c785e5effb0f108", + "generated": "2026-08-24", "gates": [ { "name": "Typecheck", @@ -20,19 +20,19 @@ }, { "name": "Inspect tarball", - "command": "tar -tzf src/engine/*.tgz; assert no src/, no tsconfig*.json, no .test.* artifacts, package/dist/ present" + "command": "tar inspect" }, { "name": "Consumer smoke", - "command": "rm -rf consumer-smoke/{node_modules,package-lock.json,dist}; cd consumer-smoke; npm run install:engine; npm run build; npm run smoke" + "command": "consumer-smoke install/build/smoke" }, { "name": "Parse-check PowerShell scripts", - "command": "pwsh: Parser::ParseFile over Get-ChildItem -Recurse -Filter *.ps1" + "command": "pwsh parse *.ps1" }, { "name": "Run Pester tests", - "command": "pwsh: Invoke-Pester -Path tools -Output Detailed -PassThru" + "command": "Invoke-Pester -Path tools" }, { "name": "Validate Markdown links, terminology, and generated files", @@ -40,7 +40,7 @@ }, { "name": "Build documentation", - "command": "./docs.ps1 -BuildOnly (local stand-in for /template/scripts/docs-build.ps1; needs Docker and an installed docs.ps1)" + "command": "./docs.ps1 -BuildOnly" }, { "name": "Build and verify landing page", @@ -48,19 +48,19 @@ }, { "name": "Merge landing page into documentation build", - "command": "npm --prefix site run merge (needs a prior docs build in artifacts/docs)" + "command": "npm --prefix site run merge" }, { "name": "Test the host", - "command": "dotnet test src/host/SubZeroDev.GameEngine.Host.Tests/SubZeroDev.GameEngine.Host.Tests.csproj (needs NUGET_GITHUB_TOKEN for the sibling-repo package feed)" + "command": "dotnet test src/host" }, { "name": "Positive route and probe smoke", - "command": "docker build -t subzerodev-gameengine-host:smoke .; docker run -d -p 18080:8080; curl /, /roadmap/, /docs/, /health/live, /health/ready -> 200 and an unknown route -> 404" + "command": "docker build+run+curl" }, { "name": "Negative fixture -- corrupted artifact must fail to start", - "command": "docker build -f tools/host-smoke/Dockerfile.negative-fixture --build-arg BASE_IMAGE=subzerodev-gameengine-host:smoke; docker run must exit non-zero" + "command": "docker build negative fixture" } ] } \ No newline at end of file diff --git a/.claude/verify-report.json b/.claude/verify-report.json index b98dd4d..81952bb 100644 --- a/.claude/verify-report.json +++ b/.claude/verify-report.json @@ -2,18 +2,18 @@ "gates": [ { "name": "Typecheck", "status": "Passed", "detail": "npm --prefix src/engine run typecheck — tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json — exit 0" }, { "name": "Lint", "status": "Passed", "detail": "npm --prefix src/engine run lint — eslint src scripts — exit 0" }, - { "name": "Test", "status": "Passed", "detail": "npm --prefix src/engine test (vitest run) — Test Files 79 passed (79), Tests 1146 passed (1146)" }, + { "name": "Test", "status": "Passed", "detail": "npm --prefix src/engine test (vitest run) — Test Files 79 passed (79), Tests 1174 passed (1174), Duration 3.40s" }, { "name": "Pack package", "status": "Passed", "detail": "cd src/engine; npm pack --silent — produced the-running-dev-game-engine-0.10.0.tgz, exit 0" }, - { "name": "Inspect tarball", "status": "Passed", "detail": "tar -tzf the-running-dev-game-engine-0.10.0.tgz: no matches for (^|/)src/, no tsconfig*.json entries, no .test.* build artifacts, package/dist/ present — INSPECT OK" }, - { "name": "Consumer smoke", "status": "Passed", "detail": "consumer-smoke: cleared node_modules/package-lock.json/dist, then npm run install:engine && npm run build && npm run smoke — all three exited 0, tsc build and smoke.js ran clean" }, - { "name": "Parse-check PowerShell scripts", "status": "Passed", "detail": "Parsed every *.ps1 (repo-wide) with [System.Management.Automation.Language.Parser]::ParseFile — 0 parse errors: 'All .ps1 files parsed cleanly.'" }, - { "name": "Run Pester tests", "status": "Passed", "detail": "Invoke-Pester -Path tools -Output Detailed -PassThru — Tests Passed: 235, Failed: 0, Skipped: 18, Inconclusive: 0, NotRun: 0" }, - { "name": "Validate Markdown links, terminology, and generated files", "status": "Passed", "detail": "./build/Test-Documentation.ps1 — 'Human-documentation drift check passed across 18 generated engine page(s), compatibility pointers, and the guide. Documentation checks passed across 123 Markdown file(s).' Previously failed in this session because design/20-contract.md's §12 event-status edit hadn't been carried through the generation workflow; ConvertTo-HumanDocumentation.ps1 and /make-human-docs were run and the guide stamped, regenerating docs/docs/engine/12-world-graph-kind.md and docs/docs/guide.md, and this re-run now passes." }, - { "name": "Build documentation", "status": "DidNotRun", "reason": "Runs inside the ghcr.io/the-running-dev/docs-template container image via /template/scripts/docs-build.ps1. docs.ps1 (the local wrapper) is absent from this checkout — CLAUDE.md states it is installer-generated (Invoke-SetupDocs), not committed — and pulling/running the container image directly was not attempted this session; the corresponding CI check ('Verify Documentation Build') on the pull request is where the answer will come from." }, - { "name": "Build and verify landing page", "status": "Passed", "detail": "npm --prefix src/engine run build (tsc -p tsconfig.build.json) exit 0, then npm --prefix site run check: format:check (prettier — all matched files use Prettier code style), lint (oxlint, clean), typecheck (tsc -b, clean), test (vitest — Test Files 2 passed (2), Tests 15 passed (15)), test:browser (vitest browser-mode via Playwright's managed Chromium — Test Files 1 passed (1), Tests 11 passed (11)), test:build (vite build succeeded, 'Both built HTML entry points contain their required static metadata, and 3 bundle(s) are free of Node-only runtime references'), test:merge ('Package-backed merge proven') — all exited 0" }, - { "name": "Merge landing page into documentation build", "status": "DidNotRun", "reason": "npm --prefix site run merge needs a completed docs build in artifacts/docs, which the 'Build documentation' gate above did not produce (docs-template container image not available locally)." }, - { "name": "Test the host", "status": "DidNotRun", "reason": "dotnet test src/host/SubZeroDev.GameEngine.Host.Tests requires NUGET_GITHUB_TOKEN to restore SubZeroDev.Platform.Hosting from the sibling repository's GitHub Packages feed; not set in this local environment. The corresponding CI check ('Build, run, and smoke the image') on the pull request is where the answer will come from." }, - { "name": "Positive route and probe smoke", "status": "DidNotRun", "reason": "Requires a built host image, which itself depends on restoring packages from the sibling-repo NuGet feed (NUGET_GITHUB_TOKEN, unavailable locally)." }, - { "name": "Negative fixture -- corrupted artifact must fail to start", "status": "DidNotRun", "reason": "Same dependency as 'Positive route and probe smoke': requires a built host image, blocked on the missing NUGET_GITHUB_TOKEN." } + { "name": "Inspect tarball", "status": "Passed", "detail": "tar -tzf the-running-dev-game-engine-0.10.0.tgz: grep for ^package/src/|tsconfig.*\\.json|\\.test\\. found NONE; package/dist/ present (e.g. package/dist/kinds/story-graph/achievements.js) — INSPECT OK" }, + { "name": "Consumer smoke", "status": "Passed", "detail": "consumer-smoke: cleared node_modules/package-lock.json/dist, copied fresh tarball, then npm run install:engine && npm run build && npm run smoke — all three exited 0 (install:engine added 5 packages, tsc build clean, node dist/smoke.js exit 0)" }, + { "name": "Parse-check PowerShell scripts", "status": "Passed", "detail": "Parsed every *.ps1 repo-wide with [System.Management.Automation.Language.Parser]::ParseFile — 0 parse errors, printed PASSED" }, + { "name": "Run Pester tests", "status": "Failed", "detail": "Invoke-Pester -Path tools -Output Detailed -PassThru — Tests Passed: 243, Failed: 10, Skipped: 0, NotRun: 0; plus 1 BeforeAll/AfterAll failure ('CI workflow: the Run Pester tests step is authenticated (#79)'), which also fails identically on main (confirmed via a disposable worktree at 9da79de — main: 235 passed, 1 failed, same GH_TOKEN-env test). The other 9 do NOT reproduce on main and persist even with design/state/work/ removed from the tree, ruling out the mirror files as the cause; the remaining candidate is the content edited in design/30-slices.md/design/90-decisions.md, not yet isolated further. The 10 test failures: (1) 'S4.6: unit/command/track and unit/document/agents-md exist and their closures are complete' — Expected $true, got $false. (2) 'the Run Pester tests step carries a GH_TOKEN env, the same as Check the design state against the tree' — pre-existing, also fails on main. (3) 'this repository's own table and its own enumeration agree' — Expected $null or empty, got ContractListUnreadable/GlobTableNotFound: design/20-contract.md; GlobDisagreement is uncomputed, not clean. (4) 'S12.5: the check exits 0 against this repository, and names the largest closure and its size' — Expected 0, got 2; findings list dozens of [UnrecordedArtifact] entries (every .claude/commands/*.md, most tools/*.ps1, most design/*.md have no active unit record naming them as Anchor), dozens of [LogEntryUnrecorded] entries (90-decisions.md log headings with no decision record naming them as Anchor, including several from 2026-08-23 that this branch's own commit touches), [RegionMalformed] entries in design/00-brief.md, design/10-design.md, design/20-contract.md, design/30-slices.md, design/90-decisions.md (closing marker for 'human-doc' region does not match innermost open region — three of these five files this PR does not touch), plus [ContractListUnreadable], [ProjectorFailed] exited 1, and [TrackerUnavailable] for all 48 issues mirrored under design/state/work/ (could not read issue #142, #145, ... #371) despite gh auth status confirming an authenticated session and gh issue view 142 succeeding standalone when run directly — root cause not diagnosed, this command does not fix. (5) 'S7.9: the projector runs against this repository and ProjectionStale does not fire' — Expected $null or empty, got ProjectorFailed/exited 1; ProjectionStale is uncomputed, not clean. (6) 'S16.1/S16.2: this repository has one Contract record per design/20-contract.md Public-surface entry' — Expected 9, got 0. (7) 'S16.5: design/state-index.md's consumers region lists real consumers' — Cannot find path design/state-index.md, does not exist. (8) 'S17.2: every invariant row in the real Invariants section sits inside the single invariants region' — Expected actual value greater than -1, got -1. (9) 'S18.6: EnforcementUnevidenced rejects this repository's own superseded decision' — Cannot find path design/state/decisions/2026-08-03-ticking-checkbox-is-the-users.md, does not exist. (10) 'S7.2: -DryRun against the real repository writes nothing' — Expected 0, got 7 refusals (same [UnrecordedArtifact]/[LogEntryUnrecorded]/[RegionMalformed] set as #4). This looks like pre-existing design-state drift the branch's content edits expose rather than something the two-line status/decision changes themselves broke, but that is not confirmed." }, + { "name": "Validate Markdown links, terminology, and generated files", "status": "Passed", "detail": "./build/Test-Documentation.ps1 — after running build/ConvertTo-HumanDocumentation.ps1 to regenerate engine/TODO.md and engine/OPEN-QUESTIONS.md (skipped in the first commit, since fixed in a follow-up commit): 'Human-documentation drift check passed across 18 generated engine page(s), compatibility pointers, and the guide. Documentation checks passed across 173 Markdown file(s).'" }, + { "name": "Build documentation", "status": "DidNotRun", "reason": "Runs inside the ghcr.io/the-running-dev/docs-template container image via /template/scripts/docs-build.ps1. docs.ps1 (the local wrapper) is absent from this checkout — CLAUDE.md states it is installer-generated (Invoke-SetupDocs), not committed. Docker itself is running locally, but the wrapper script needed to invoke the containerized build is missing, so this was not attempted; the corresponding CI check ('Verify Documentation Build') on the pull request is where the answer will come from." }, + { "name": "Build and verify landing page", "status": "Passed", "detail": "npm --prefix src/engine run build (tsc -p tsconfig.build.json) exit 0, then npm --prefix site run check: unit tests (Test Files 2 passed, Tests 15 passed), test:browser (Test Files 1 passed, Tests 11 passed), test:build (vite build succeeded, 'Both built HTML entry points contain their required static metadata, and 3 bundle(s) are free of Node-only runtime references'), test:merge ('Package-backed merge proven: /, /roadmap/ and the protected docs/ subtree all verified, and the top-level docs/ guard rejects a bad landing build') — all exited 0" }, + { "name": "Merge landing page into documentation build", "status": "DidNotRun", "reason": "npm --prefix site run merge needs a completed docs build in artifacts/docs, which the 'Build documentation' gate above did not produce (docs-template container image not available locally without the docs.ps1 wrapper)." }, + { "name": "Test the host", "status": "Passed", "detail": "dotnet test src/host/SubZeroDev.GameEngine.Host.Tests — all projects up-to-date for restore (NuGet packages already cached locally from a prior run, so no NUGET_GITHUB_TOKEN was needed this pass) — Passed: 12, Failed: 0, Skipped: 0, Total: 12, Duration 818ms" }, + { "name": "Positive route and probe smoke", "status": "DidNotRun", "reason": "Requires building the host Docker image, which restores SubZeroDev.Platform.Hosting from a private GitHub Packages NuGet feed inside the build via a docker/build-push-action secret (nuget_github_token). REGISTRY_TOKEN is not present in this local environment, so the image build step itself cannot be reproduced locally even though Docker is running; the corresponding CI check ('Build, run, and smoke the image') on the pull request is where the answer will come from." }, + { "name": "Negative fixture -- corrupted artifact must fail to start", "status": "DidNotRun", "reason": "Same dependency as 'Positive route and probe smoke': requires the built host image, blocked on the missing REGISTRY_TOKEN for the private NuGet feed restore." } ] } diff --git a/design/30-slices.md b/design/30-slices.md index 86b1af8..db9810f 100644 --- a/design/30-slices.md +++ b/design/30-slices.md @@ -3386,7 +3386,7 @@ system is half named after. for the drift rates, which are content and belong with Sun Trap; guest opinion changes driven by quality, which the utility model already reads. -### [ ] W84 — Incidents That Happen On Their Own {#w84} +### [x] W84 — Incidents That Happen On Their Own {#w84} **Delivers:** Things go wrong in the resort without the player causing them — a breakdown, a spill, a security problem — and the player has to deal with them. Today the only incident that can @@ -3407,7 +3407,7 @@ it succeeds, and a rejected candidate consuming nothing. a new fixture pair under `src/engine/fixtures/replay/`. - **Depends on:** [W83](#w83), so a rolled incident has meters to act on and a resolution path that is not litter-only. -- **Status:** Not started. +- **Status:** Done — [PR #360](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/pull/360). - **Done when:** - W84.1 An eligible definition rolls against its declared chance from the tick's incidents handle, and a successful roll allocates an occurrence with its start effects applied @@ -3440,7 +3440,7 @@ it succeeds, and a rejected candidate consuming nothing. narrative around an incident, which is a client concern; alerts raised for an active incident, which is [W85](#w85); balance values for chances and cooldowns, which are content. -### [ ] W85 — The Resort Tells You What Needs Attention {#w85} +### [x] W85 — The Resort Tells You What Needs Attention {#w85} **Delivers:** A player is told when something needs them — a building has broken, an incident is running, the scenario has resolved — instead of having to notice it in the numbers; and the @@ -3467,7 +3467,7 @@ core behaviour this kind has never exercised. can reach zero. [W84](#w84) is not required, since guest litter already raises incidents, but is sequenced ahead so the incident family is proved against an incident the player did not cause. -- **Status:** Not started. +- **Status:** Done — [PR #361](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/pull/361). - **Done when:** - W85.1 Still-locked achievements are evaluated by definition id against post-resolution state, an unlock writes the achievement's existence row with the core `achievement_unlocked` @@ -3518,7 +3518,7 @@ core behaviour this kind has never exercised. > the emit sites are named, and adding one is declaring it and emitting it. These two units are > that, and nothing else. -### [ ] W86 — The Story-Graph Kind Says Why {#w86} +### [x] W86 — The Story-Graph Kind Says Why {#w86} **Delivers:** A campaign author can find out why a choice was refused or greyed out, and a developer can find out why a replay diverged, without adding logging to the engine by hand. Today @@ -3537,7 +3537,7 @@ audiences the channel exists to serve. `src/engine/src/kinds/story-graph/kind.ts`. `src/engine/src/core/observability/` is **read, not modified**. - **Depends on:** nothing. -- **Status:** Not started. +- **Status:** Done — [PR #362](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/pull/362). - **Done when:** - W86.1 All six remaining rows are emitted at the step the table names, with exactly the `data` fields it lists and no others, and all ten are declared in `Kind.eventNames`. A test @@ -3566,7 +3566,7 @@ audiences the channel exists to serve. event names; changing any severity the table fixes; the sink implementations, which already ship. -### [ ] W87 — The World-Graph Kind Says What Its Ticks Did {#w87} +### [x] W87 — The World-Graph Kind Says What Its Ticks Did {#w87} **Delivers:** Someone diagnosing a resort that behaves oddly can watch what happened inside a tick — which guests could not reach a building, which tasks were assigned and cancelled, what each @@ -3585,7 +3585,7 @@ difference is invisible in the projection and obvious in the stream. `src/engine/src/kinds/world-graph/kind.ts`. - **Depends on:** [W81](#w81)–[W85](#w85), which each deliver the rows their own system owns. This unit takes what is left rather than duplicating them. -- **Status:** Not started. +- **Status:** Done — [PR #363](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/pull/363). - **Done when:** - W87.1 Every row in [12 §12](12-world-graph-kind.md#12-events)'s table is emitted at the system it names and declared in `Kind.eventNames`; the status column has no remaining *specified, diff --git a/design/90-decisions.md b/design/90-decisions.md index 8925776..0259ab4 100644 --- a/design/90-decisions.md +++ b/design/90-decisions.md @@ -267,20 +267,24 @@ and was deliberately regularised is the reasoning a later reader of [issue #285](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/issues/285) will want. That issue's premise no longer holds and it is `/track`'s to close. -**`incidents[].onStart` is the one effect list with no building-meter rule, and W47 has to pick -one.** After W83, every other list is accounted for: `products[].effects` and a building's -`operation.effects` defer as `service`, `scheduledChanges` and `policies[].whileActive` as -`policy`, a staff-resolved `onResolve` as `staff`, and a `wear` delta on `objectives.onCompleted`, -`failures.onTriggered`, or a duration-bearing `onResolve` is rejected (§9.2). `onStart` is neither, -and nothing misbehaves today only because no system applies it — it is declared, shape-validated, -and dead. W47 makes it live, and the answer depends on a choice W47 owns rather than W83: if -`onStart` runs only for system 16's rolls, it runs after system 14 and a `wear` delta there could -never reach §4.16's broken transition, so the §9.2 rejection should extend to it; but if W47 also -applies `onStart` at the `start_incident` call sites in systems 1 and 4, those run *before* 14 and -could defer legitimately, and extending the rejection would forbid content W47 wants. Both readings -are defensible, so W83's review pass deliberately left the code alone rather than pick one. Note -the contract's own MVP worked example (§13's litter incident) puts a `cleanliness` delta in -`onStart`, not a `wear` one, so the wear-only rule would not contradict it either way. +**`incidents[].onStart`'s building-meter rule — closed by W84, not W47.** After W83, every +other effect list was accounted for: `products[].effects` and a building's `operation.effects` +defer as `service`, `scheduledChanges` and `policies[].whileActive` as `policy`, a +staff-resolved `onResolve` as `staff`, and a `wear` delta on `objectives.onCompleted`, +`failures.onTriggered`, or a duration-bearing `onResolve` is rejected (§9.2). `onStart` was +neither, and nothing misbehaved only because no system applied it yet — it was declared, +shape-validated, and dead. This entry named W47 as the unit that would make it live and own +the choice; the incidents family that actually did so is **W84**, and it resolved the +question this entry left open. `tick/pipeline.ts` calls `applyWorldEffects` on +`incidentDefinition.onStart` from exactly one site — system 16/17's roll, after system 14 has +already closed its broken-transition check for the tick — and `validate.ts`'s +`forbidUndeferrableWearDelta(entry.onStart, …)` extends the §9.2 rejection to it, with the +comment recording why: "onStart only ever runs from system 16's roll, always after system 14 +closed its broken-transition check for the tick, so a wear delta there can never be seen +(W84)." No `start_incident` call site in systems 1 or 4 applies `onStart`, so the +legitimate-deferral reading was not the one built. The contract's own MVP worked example +(§13's litter incident) puts a `cleanliness` delta in `onStart`, not a `wear` one, so the +wear-only rule does not contradict it. **Nothing checks *emitted → registered* for `StateChange.reason`, and it has now failed twice.** `20-contract.md` §13 says so in its own words — a reason threaded through `EffectContext` is not diff --git a/design/TODO.md b/design/TODO.md index 4b246b8..ad66232 100644 --- a/design/TODO.md +++ b/design/TODO.md @@ -222,15 +222,15 @@ ### [x] W83 — Buildings Get Dirty, Wear Out, and Break {#w83} -### [ ] W84 — Incidents That Happen On Their Own {#w84} +### [x] W84 — Incidents That Happen On Their Own {#w84} -### [ ] W85 — The Resort Tells You What Needs Attention {#w85} +### [x] W85 — The Resort Tells You What Needs Attention {#w85} ### Rigour: Two Event Streams Specified and Never Emitted -### [ ] W86 — The Story-Graph Kind Says Why {#w86} +### [x] W86 — The Story-Graph Kind Says Why {#w86} -### [ ] W87 — The World-Graph Kind Says What Its Ticks Did {#w87} +### [x] W87 — The World-Graph Kind Says What Its Ticks Did {#w87} ## Known Open Items Carried In diff --git a/design/state/work/142.md b/design/state/work/142.md new file mode 100644 index 0000000..2aba4d3 --- /dev/null +++ b/design/state/work/142.md @@ -0,0 +1,7 @@ +# work/142 +Issue: 142 +Title: World-graph: demolished guests always route to exits[0], not nearest exit +State: OPEN +Rank: 142 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/145.md b/design/state/work/145.md new file mode 100644 index 0000000..a3d2cea --- /dev/null +++ b/design/state/work/145.md @@ -0,0 +1,7 @@ +# work/145 +Issue: 145 +Title: World-graph: scenery placement has no rotation/bounds validation (unlike buildings) +State: OPEN +Rank: 145 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/146.md b/design/state/work/146.md new file mode 100644 index 0000000..5e3a563 --- /dev/null +++ b/design/state/work/146.md @@ -0,0 +1,7 @@ +# work/146 +Issue: 146 +Title: World-graph: staff assignment emits both StateChanges unconditionally, even when unchanged +State: OPEN +Rank: 146 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/168.md b/design/state/work/168.md new file mode 100644 index 0000000..be360fc --- /dev/null +++ b/design/state/work/168.md @@ -0,0 +1,7 @@ +# work/168 +Issue: 168 +Title: Design a defensive-cloning strategy for the Kind.project seam +State: OPEN +Rank: 168 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/180.md b/design/state/work/180.md new file mode 100644 index 0000000..fb5deba --- /dev/null +++ b/design/state/work/180.md @@ -0,0 +1,7 @@ +# work/180 +Issue: 180 +Title: site/ declares five ESLint packages it never runs +State: OPEN +Rank: 180 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/195.md b/design/state/work/195.md new file mode 100644 index 0000000..da9a6ab --- /dev/null +++ b/design/state/work/195.md @@ -0,0 +1,7 @@ +# work/195 +Issue: 195 +Title: Enforce StatusEffect.stacking (refresh replaces, resets expiry) when an effect is applied +State: OPEN +Rank: 195 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/203.md b/design/state/work/203.md new file mode 100644 index 0000000..2543077 --- /dev/null +++ b/design/state/work/203.md @@ -0,0 +1,7 @@ +# work/203 +Issue: 203 +Title: CI: cache Alpine chromium install in Verify Documentation Build +State: OPEN +Rank: milestone/3 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/211.md b/design/state/work/211.md new file mode 100644 index 0000000..cda3583 --- /dev/null +++ b/design/state/work/211.md @@ -0,0 +1,7 @@ +# work/211 +Issue: 211 +Title: Assert per-kind regression evidence exists, so deleting a test class fails the build +State: OPEN +Rank: milestone/2 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/212.md b/design/state/work/212.md new file mode 100644 index 0000000..564fb60 --- /dev/null +++ b/design/state/work/212.md @@ -0,0 +1,7 @@ +# work/212 +Issue: 212 +Title: Vision doc says "two kinds ship in v1"; the repository ships three +State: OPEN +Rank: milestone/1 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/213.md b/design/state/work/213.md new file mode 100644 index 0000000..7ed8232 --- /dev/null +++ b/design/state/work/213.md @@ -0,0 +1,7 @@ +# work/213 +Issue: 213 +Title: Dead references to bulgaria-bureaucracy.determinism.test.ts, deleted in #189 +State: OPEN +Rank: milestone/1 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/214.md b/design/state/work/214.md new file mode 100644 index 0000000..0586c1a --- /dev/null +++ b/design/state/work/214.md @@ -0,0 +1,7 @@ +# work/214 +Issue: 214 +Title: Slice ledger checkboxes drifted from delivery reality: W50/W51/W66 unticked, W41ΓÇôW49 not headings +State: OPEN +Rank: milestone/1 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/215.md b/design/state/work/215.md new file mode 100644 index 0000000..cabca80 --- /dev/null +++ b/design/state/work/215.md @@ -0,0 +1,7 @@ +# work/215 +Issue: 215 +Title: Batch invariance (world-graph's load-bearing property) is tested at exactly one data point +State: OPEN +Rank: milestone/2 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/216.md b/design/state/work/216.md new file mode 100644 index 0000000..f03bb09 --- /dev/null +++ b/design/state/work/216.md @@ -0,0 +1,7 @@ +# work/216 +Issue: 216 +Title: world-graph: 21 source modules (~2,100 LOC) have no adjacent test file +State: OPEN +Rank: milestone/2 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/217.md b/design/state/work/217.md new file mode 100644 index 0000000..bef3e9d --- /dev/null +++ b/design/state/work/217.md @@ -0,0 +1,7 @@ +# work/217 +Issue: 217 +Title: docs-deploy.yml never received the Playwright/Chromium fix docs-ci.yml's verify job got; last three deploys failed +State: OPEN +Rank: milestone/3 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/218.md b/design/state/work/218.md new file mode 100644 index 0000000..ee00b7b --- /dev/null +++ b/design/state/work/218.md @@ -0,0 +1,7 @@ +# work/218 +Issue: 218 +Title: Envelope-duplication has recurred five times with no mechanical guard; add one +State: OPEN +Rank: milestone/4 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/219.md b/design/state/work/219.md new file mode 100644 index 0000000..a4258b5 --- /dev/null +++ b/design/state/work/219.md @@ -0,0 +1,7 @@ +# work/219 +Issue: 219 +Title: design/ has no customer, competitive analysis, or monetization path ΓÇö the commercial thesis is entirely unrecorded +State: OPEN +Rank: milestone/5 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/225.md b/design/state/work/225.md new file mode 100644 index 0000000..4819c81 --- /dev/null +++ b/design/state/work/225.md @@ -0,0 +1,7 @@ +# work/225 +Issue: 225 +Title: Migrate pre-resume orphaned local saves to the new save-index scheme +State: OPEN +Rank: 225 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/226.md b/design/state/work/226.md new file mode 100644 index 0000000..f7116da --- /dev/null +++ b/design/state/work/226.md @@ -0,0 +1,7 @@ +# work/226 +Issue: 226 +Title: SaveRecordStore.delete() has a TOCTOU race with a concurrent put() for the same saveId +State: OPEN +Rank: 226 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/229.md b/design/state/work/229.md new file mode 100644 index 0000000..4ba686b --- /dev/null +++ b/design/state/work/229.md @@ -0,0 +1,7 @@ +# work/229 +Issue: 229 +Title: resolveApplications removes a JobOpening entirely on hire, not decrementing positionsAvailable +State: OPEN +Rank: 229 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/234.md b/design/state/work/234.md new file mode 100644 index 0000000..f18a756 --- /dev/null +++ b/design/state/work/234.md @@ -0,0 +1,7 @@ +# work/234 +Issue: 234 +Title: Spike: generic scene-presentation layer (Municipality reference) ΓÇö not engine work, will move to SubZeroDev.Presentation +State: OPEN +Rank: 234 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/237.md b/design/state/work/237.md new file mode 100644 index 0000000..6b745c7 --- /dev/null +++ b/design/state/work/237.md @@ -0,0 +1,7 @@ +# work/237 +Issue: 237 +Title: validateUnreachableItems predates `shop`: a purchasable item is reported unreachable +State: OPEN +Rank: 237 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/245.md b/design/state/work/245.md new file mode 100644 index 0000000..4947a85 --- /dev/null +++ b/design/state/work/245.md @@ -0,0 +1,7 @@ +# work/245 +Issue: 245 +Title: Nothing checks emitted ΓåÆ registered for reason codes, and hand-auditing has now failed twice +State: OPEN +Rank: 18 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/266.md b/design/state/work/266.md new file mode 100644 index 0000000..7688923 --- /dev/null +++ b/design/state/work/266.md @@ -0,0 +1,7 @@ +# work/266 +Issue: 266 +Title: Forking a session at an earlier point should be a store operation +State: OPEN +Rank: milestone/13 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/267.md b/design/state/work/267.md new file mode 100644 index 0000000..d615301 --- /dev/null +++ b/design/state/work/267.md @@ -0,0 +1,7 @@ +# work/267 +Issue: 267 +Title: Provisional simulation-kind numbers need a balancing pass +State: OPEN +Rank: 19 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/268.md b/design/state/work/268.md new file mode 100644 index 0000000..f9b02ca --- /dev/null +++ b/design/state/work/268.md @@ -0,0 +1,7 @@ +# work/268 +Issue: 268 +Title: ChainScope's "profile" value has nowhere to persist +State: OPEN +Rank: 20 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/269.md b/design/state/work/269.md new file mode 100644 index 0000000..042706a --- /dev/null +++ b/design/state/work/269.md @@ -0,0 +1,7 @@ +# work/269 +Issue: 269 +Title: Hosted MCP contract in SubZeroDev.Platform needs its W48 mirror (preview_action) +State: OPEN +Rank: 21 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/270.md b/design/state/work/270.md new file mode 100644 index 0000000..9691144 --- /dev/null +++ b/design/state/work/270.md @@ -0,0 +1,7 @@ +# work/270 +Issue: 270 +Title: Extract a shared SystemPipeline substrate for tick-driven kinds (simulation + world-graph) +State: OPEN +Rank: 22 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/273.md b/design/state/work/273.md new file mode 100644 index 0000000..c664d7d --- /dev/null +++ b/design/state/work/273.md @@ -0,0 +1,7 @@ +# work/273 +Issue: 273 +Title: Decide the replacement for /play/'s no-engine-API browser smoke (design/15 ┬º6, 13-playable-web-demo.md ┬º6) +State: OPEN +Rank: milestone/3 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/275.md b/design/state/work/275.md new file mode 100644 index 0000000..b36fffe --- /dev/null +++ b/design/state/work/275.md @@ -0,0 +1,7 @@ +# work/275 +Issue: 275 +Title: `wisdom` attribute has no consumer in the simulation kind +State: OPEN +Rank: 23 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/276.md b/design/state/work/276.md new file mode 100644 index 0000000..c81e87b --- /dev/null +++ b/design/state/work/276.md @@ -0,0 +1,7 @@ +# work/276 +Issue: 276 +Title: `SaveRecordStore.delete` has no caller anywhere +State: OPEN +Rank: 24 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/277.md b/design/state/work/277.md new file mode 100644 index 0000000..1806bcf --- /dev/null +++ b/design/state/work/277.md @@ -0,0 +1,7 @@ +# work/277 +Issue: 277 +Title: There is no per-player save query, and two hosts have now invented one +State: OPEN +Rank: 25 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/278.md b/design/state/work/278.md new file mode 100644 index 0000000..f92fd5b --- /dev/null +++ b/design/state/work/278.md @@ -0,0 +1,7 @@ +# work/278 +Issue: 278 +Title: `VisibleStat` omits the declared range, so clients read `Campaign.content` to get it +State: OPEN +Rank: 26 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/279.md b/design/state/work/279.md new file mode 100644 index 0000000..8b8c2d8 --- /dev/null +++ b/design/state/work/279.md @@ -0,0 +1,7 @@ +# work/279 +Issue: 279 +Title: `listCampaigns()` is synchronous, so no remote store can implement it +State: OPEN +Rank: 27 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/280.md b/design/state/work/280.md new file mode 100644 index 0000000..c15b645 --- /dev/null +++ b/design/state/work/280.md @@ -0,0 +1,7 @@ +# work/280 +Issue: 280 +Title: Reproducing a stored session's blob requires pinning `IdSource.newGameId` +State: OPEN +Rank: 28 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/281.md b/design/state/work/281.md new file mode 100644 index 0000000..0715c86 --- /dev/null +++ b/design/state/work/281.md @@ -0,0 +1,7 @@ +# work/281 +Issue: 281 +Title: `SessionStore` has no concept of a caller, so authorization lives entirely outside it +State: OPEN +Rank: 29 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/282.md b/design/state/work/282.md new file mode 100644 index 0000000..1534c15 --- /dev/null +++ b/design/state/work/282.md @@ -0,0 +1,7 @@ +# work/282 +Issue: 282 +Title: `Kind.outcome` has no shape a host can read generically +State: OPEN +Rank: 30 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/287.md b/design/state/work/287.md new file mode 100644 index 0000000..61be263 --- /dev/null +++ b/design/state/work/287.md @@ -0,0 +1,7 @@ +# work/287 +Issue: 287 +Title: `CampaignSummary` carries only a `titleKey`, and no session-free way to resolve it, so campaign selection cannot render +State: OPEN +Rank: milestone/13 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/292.md b/design/state/work/292.md new file mode 100644 index 0000000..dcf2026 --- /dev/null +++ b/design/state/work/292.md @@ -0,0 +1,7 @@ +# work/292 +Issue: 292 +Title: Content packs compose at campaign granularity, but dynamic content injection needs node granularity +State: OPEN +Rank: milestone/6 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/293.md b/design/state/work/293.md new file mode 100644 index 0000000..67ac44c --- /dev/null +++ b/design/state/work/293.md @@ -0,0 +1,7 @@ +# work/293 +Issue: 293 +Title: A session belongs to exactly one campaign, and nothing decides what happens if content spans two +State: OPEN +Rank: milestone/6 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/300.md b/design/state/work/300.md new file mode 100644 index 0000000..63e3418 --- /dev/null +++ b/design/state/work/300.md @@ -0,0 +1,7 @@ +# work/300 +Issue: 300 +Title: Campaign sources here are fixtures now, but nothing in the tree says so +State: OPEN +Rank: milestone/1 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/302.md b/design/state/work/302.md new file mode 100644 index 0000000..4dc6317 --- /dev/null +++ b/design/state/work/302.md @@ -0,0 +1,7 @@ +# work/302 +Issue: 302 +Title: The engine package published public, and two plan documents still specify private +State: OPEN +Rank: milestone/1 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/349.md b/design/state/work/349.md new file mode 100644 index 0000000..61f4665 --- /dev/null +++ b/design/state/work/349.md @@ -0,0 +1,7 @@ +# work/349 +Issue: 349 +Title: world-graph: deferred building-meter effects mark 'applied' before system 14 composes the total +State: OPEN +Rank: 349 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/350.md b/design/state/work/350.md new file mode 100644 index 0000000..9e5dd0c --- /dev/null +++ b/design/state/work/350.md @@ -0,0 +1,7 @@ +# work/350 +Issue: 350 +Title: design/20-contract.md: audit codes table missing building_broken (W83) +State: OPEN +Rank: 350 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/364.md b/design/state/work/364.md new file mode 100644 index 0000000..85b217c --- /dev/null +++ b/design/state/work/364.md @@ -0,0 +1,7 @@ +# work/364 +Issue: 364 +Title: Mirror the simulation-kind lifecycles once GameOfLife S7 lands ΓÇö and do not write them here first +State: OPEN +Rank: 364 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/365.md b/design/state/work/365.md new file mode 100644 index 0000000..037a7c8 --- /dev/null +++ b/design/state/work/365.md @@ -0,0 +1,7 @@ +# work/365 +Issue: 365 +Title: Kit sync is 11 commits behind, and the sync will silently drop the four design-state -Skip: guards +State: OPEN +Rank: 365 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/design/state/work/369.md b/design/state/work/369.md new file mode 100644 index 0000000..7141926 --- /dev/null +++ b/design/state/work/369.md @@ -0,0 +1,7 @@ +# work/369 +Issue: 369 +Title: W88 ΓÇö The Simulation Kind Nobody Outside the Package Can Use +State: OPEN +Rank: 369 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: W88.1, W88.2, W88.3, W88.4, W88.5, W88.6 diff --git a/design/state/work/370.md b/design/state/work/370.md new file mode 100644 index 0000000..d609190 --- /dev/null +++ b/design/state/work/370.md @@ -0,0 +1,7 @@ +# work/370 +Issue: 370 +Title: W89 ΓÇö A Game-Length Life +State: OPEN +Rank: 370 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: W89.1, W89.2, W89.3, W89.4, W89.5, W89.6, W89.7, W89.8 diff --git a/design/state/work/371.md b/design/state/work/371.md new file mode 100644 index 0000000..aae9c3a --- /dev/null +++ b/design/state/work/371.md @@ -0,0 +1,7 @@ +# work/371 +Issue: 371 +Title: A kind's event severities are literals at each emit call; only the core fixes them in one table +State: OPEN +Rank: 371 +MirroredAt: 9da79de055bdb13513121bfe7681b9c03443ea39 +Criteria: diff --git a/docs/docs/engine/OPEN-QUESTIONS.md b/docs/docs/engine/OPEN-QUESTIONS.md index 85c50db..ee15be7 100644 --- a/docs/docs/engine/OPEN-QUESTIONS.md +++ b/docs/docs/engine/OPEN-QUESTIONS.md @@ -257,20 +257,24 @@ and was deliberately regularised is the reasoning a later reader of [issue #285](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/issues/285) will want. That issue's premise no longer holds and it is `/track`'s to close. -**`incidents[].onStart` is the one effect list with no building-meter rule, and W47 has to pick -one.** After W83, every other list is accounted for: `products[].effects` and a building's -`operation.effects` defer as `service`, `scheduledChanges` and `policies[].whileActive` as -`policy`, a staff-resolved `onResolve` as `staff`, and a `wear` delta on `objectives.onCompleted`, -`failures.onTriggered`, or a duration-bearing `onResolve` is rejected (§9.2). `onStart` is neither, -and nothing misbehaves today only because no system applies it — it is declared, shape-validated, -and dead. W47 makes it live, and the answer depends on a choice W47 owns rather than W83: if -`onStart` runs only for system 16's rolls, it runs after system 14 and a `wear` delta there could -never reach §4.16's broken transition, so the §9.2 rejection should extend to it; but if W47 also -applies `onStart` at the `start_incident` call sites in systems 1 and 4, those run *before* 14 and -could defer legitimately, and extending the rejection would forbid content W47 wants. Both readings -are defensible, so W83's review pass deliberately left the code alone rather than pick one. Note -the contract's own MVP worked example (§13's litter incident) puts a `cleanliness` delta in -`onStart`, not a `wear` one, so the wear-only rule would not contradict it either way. +**`incidents[].onStart`'s building-meter rule — closed by W84, not W47.** After W83, every +other effect list was accounted for: `products[].effects` and a building's `operation.effects` +defer as `service`, `scheduledChanges` and `policies[].whileActive` as `policy`, a +staff-resolved `onResolve` as `staff`, and a `wear` delta on `objectives.onCompleted`, +`failures.onTriggered`, or a duration-bearing `onResolve` is rejected (§9.2). `onStart` was +neither, and nothing misbehaved only because no system applied it yet — it was declared, +shape-validated, and dead. This entry named W47 as the unit that would make it live and own +the choice; the incidents family that actually did so is **W84**, and it resolved the +question this entry left open. `tick/pipeline.ts` calls `applyWorldEffects` on +`incidentDefinition.onStart` from exactly one site — system 16/17's roll, after system 14 has +already closed its broken-transition check for the tick — and `validate.ts`'s +`forbidUndeferrableWearDelta(entry.onStart, …)` extends the §9.2 rejection to it, with the +comment recording why: "onStart only ever runs from system 16's roll, always after system 14 +closed its broken-transition check for the tick, so a wear delta there can never be seen +(W84)." No `start_incident` call site in systems 1 or 4 applies `onStart`, so the +legitimate-deferral reading was not the one built. The contract's own MVP worked example +(§13's litter incident) puts a `cleanliness` delta in `onStart`, not a `wear` one, so the +wear-only rule does not contradict it. **Nothing checks *emitted → registered* for `StateChange.reason`, and it has now failed twice.** `20-contract.md` §13 says so in its own words — a reason threaded through `EffectContext` is not diff --git a/docs/docs/engine/TODO.md b/docs/docs/engine/TODO.md index 4c564eb..bf45231 100644 --- a/docs/docs/engine/TODO.md +++ b/docs/docs/engine/TODO.md @@ -3381,7 +3381,7 @@ system is half named after. for the drift rates, which are content and belong with Sun Trap; guest opinion changes driven by quality, which the utility model already reads. -### [ ] W84 — Incidents That Happen On Their Own {#w84} +### [x] W84 — Incidents That Happen On Their Own {#w84} **Delivers:** Things go wrong in the resort without the player causing them — a breakdown, a spill, a security problem — and the player has to deal with them. Today the only incident that can @@ -3402,7 +3402,7 @@ it succeeds, and a rejected candidate consuming nothing. a new fixture pair under `src/engine/fixtures/replay/`. - **Depends on:** [W83](#w83), so a rolled incident has meters to act on and a resolution path that is not litter-only. -- **Status:** Not started. +- **Status:** Done — [PR #360](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/pull/360). - **Done when:** - W84.1 An eligible definition rolls against its declared chance from the tick's incidents handle, and a successful roll allocates an occurrence with its start effects applied @@ -3435,7 +3435,7 @@ it succeeds, and a rejected candidate consuming nothing. narrative around an incident, which is a client concern; alerts raised for an active incident, which is [W85](#w85); balance values for chances and cooldowns, which are content. -### [ ] W85 — The Resort Tells You What Needs Attention {#w85} +### [x] W85 — The Resort Tells You What Needs Attention {#w85} **Delivers:** A player is told when something needs them — a building has broken, an incident is running, the scenario has resolved — instead of having to notice it in the numbers; and the @@ -3462,7 +3462,7 @@ core behaviour this kind has never exercised. can reach zero. [W84](#w84) is not required, since guest litter already raises incidents, but is sequenced ahead so the incident family is proved against an incident the player did not cause. -- **Status:** Not started. +- **Status:** Done — [PR #361](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/pull/361). - **Done when:** - W85.1 Still-locked achievements are evaluated by definition id against post-resolution state, an unlock writes the achievement's existence row with the core `achievement_unlocked` @@ -3513,7 +3513,7 @@ core behaviour this kind has never exercised. > the emit sites are named, and adding one is declaring it and emitting it. These two units are > that, and nothing else. -### [ ] W86 — The Story-Graph Kind Says Why {#w86} +### [x] W86 — The Story-Graph Kind Says Why {#w86} **Delivers:** A campaign author can find out why a choice was refused or greyed out, and a developer can find out why a replay diverged, without adding logging to the engine by hand. Today @@ -3532,7 +3532,7 @@ audiences the channel exists to serve. `src/engine/src/kinds/story-graph/kind.ts`. `src/engine/src/core/observability/` is **read, not modified**. - **Depends on:** nothing. -- **Status:** Not started. +- **Status:** Done — [PR #362](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/pull/362). - **Done when:** - W86.1 All six remaining rows are emitted at the step the table names, with exactly the `data` fields it lists and no others, and all ten are declared in `Kind.eventNames`. A test @@ -3561,7 +3561,7 @@ audiences the channel exists to serve. event names; changing any severity the table fixes; the sink implementations, which already ship. -### [ ] W87 — The World-Graph Kind Says What Its Ticks Did {#w87} +### [x] W87 — The World-Graph Kind Says What Its Ticks Did {#w87} **Delivers:** Someone diagnosing a resort that behaves oddly can watch what happened inside a tick — which guests could not reach a building, which tasks were assigned and cancelled, what each @@ -3580,7 +3580,7 @@ difference is invisible in the projection and obvious in the stream. `src/engine/src/kinds/world-graph/kind.ts`. - **Depends on:** [W81](#w81)–[W85](#w85), which each deliver the rows their own system owns. This unit takes what is left rather than duplicating them. -- **Status:** Not started. +- **Status:** Done — [PR #363](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/pull/363). - **Done when:** - W87.1 Every row in [12 §12](12-world-graph-kind.md#12-events)'s table is emitted at the system it names and declared in `Kind.eventNames`; the status column has no remaining *specified,