Skip to content

W83 — Buildings Get Dirty, Wear Out, and Break - #348

Merged
The-Running-Dev merged 11 commits into
mainfrom
slice/W83
Aug 20, 2026
Merged

W83 — Buildings Get Dirty, Wear Out, and Break#348
The-Running-Dev merged 11 commits into
mainfrom
slice/W83

Conversation

@The-Running-Dev

@The-Running-Dev The-Running-Dev commented Aug 20, 2026

Copy link
Copy Markdown
Owner

What changed, and why. Wires cleanliness-wear (world-graph system 14) for real: systems 1,
4, and 11 now defer their building_meter_delta effects to scratch instead of applying them
immediately, and system 14 sums the service/litter/staff/policy sources once per building/meter,
in contract order, before clamping once — replacing the prior implementation that applied only
one of five delta sources and never moved wear at all. Wear reaching zero now breaks an open or
closed building; cleanliness reaching zero does not, on its own. The contract's fifth ordered
source, incident, has no mechanism independent of staff's deferred onResolve effect and is
left a documented no-op placeholder (design/90-decisions.md).

Verified

Ran and passed:

  • Typecheck — npm --prefix src/engine run typecheck
  • Lint — npm --prefix src/engine run lint
  • Test — npm --prefix src/engine test — 79 files, 1121 tests passed
  • Pack package — npm pack --silent produced the tarball with no errors
  • Inspect tarball — 311 entries; no src/, no tsconfig*.json, no .test.*; package/dist/ present (309 entries)
  • Consumer smoke — fresh install from the packed tarball, build, and node dist/smoke.js exited 0
  • Parse-check PowerShell scripts — 21 files parsed, 0 errors
  • Run Pester tests — 94 tests, 0 failed
  • Validate Markdown links, terminology, and generated files — ./build/Test-Documentation.ps1 passed across 18 generated engine pages and 123 Markdown files
  • Build and verify landing page — npm --prefix site run check (lint, typecheck, unit, browser, build, merge) all passed

Did not run:

  • Build documentation — docs.ps1 is not present in this checkout (generated by Invoke-SetupDocs, not committed); a Docker host-image build was also attempted and its docs stage was cancelled when a sibling stage failed for an unrelated reason (below). Deferred to docs-ci.yml's "Build documentation" check on this PR.
  • Merge landing page into documentation build — needs a completed production docs build in artifacts/docs, which the above did not produce locally. Deferred to docs-ci.yml on this PR.
  • Test the host — dotnet test for the host needs NUGET_GITHUB_TOKEN for the sibling-repo NuGet feed, not set locally. Deferred to host-image.yml on this PR.
  • Positive route and probe smoke — needs the built host image; the build's dotnet restore of SubZeroDev.Platform.Hosting failed locally with Value cannot be null or empty string. (Parameter 'username') because no NuGet credentials are present in this environment. Deferred to host-image.yml on this PR.
  • Negative fixture -- corrupted artifact must fail to start — needs the base image from the step above, which did not build locally for the same reason. Deferred to host-image.yml on this PR.

Agent detail
  • Slice: W83 — design/30-slices.md § W83 @ cbc0d1b
  • Criteria met: W83.1, W83.2, W83.3, W83.4, W83.5, W83.7
  • W83.6: Both committed world-graph replay outcomes are unchanged; a new fixture pair (world-graph-mvp-cleaning) was added rather than an existing outcome moving, so there is nothing to record as an intended change.

Implements cleanliness-wear (system 14) for real: systems 1, 4, and 11 now
defer their building_meter_delta effects to scratch instead of applying them
immediately, and system 14 sums the service/litter/staff/policy sources once
per building/meter before clamping once, per 20-contract.md §9 and §4.16.
Wear reaching zero now breaks an open or closed building; cleanliness alone
never does. The contract's fifth ordered source, `incident`, has no
independent mechanism distinct from `staff`'s deferred onResolve effect and
is left a documented no-op placeholder — see design/90-decisions.md.
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts
Comment thread src/engine/src/kinds/world-graph/tick/effects.ts
Comment thread design/90-decisions.md Outdated
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts
Comment thread src/engine/src/kinds/world-graph/tick/effects.ts
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts Outdated
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts Outdated
…t event severity

cleanlinessWear's clamp reused effects.ts's safeAdd/clamp instead of a bare
Math.max/min, restoring the overflow guard every other meter-delta path relies
on. building.status.changed now emits at debug everywhere, matching the
existing setBuildingOpen call site (severity is fixed per event name, not per
call site). Also drops the redundant buildings-array re-walk in favor of the
byId map's own preserved iteration order.
Reject building_meter_delta in objective/failure effect lists and in a
duration-bearing incident's onResolve: those paths run after system 14 and
never defer, so a delta reaching them would double-clamp and could never
trigger the broken transition. Reject initialWear: 0 on a building
definition too, since a building created already at the wear floor can
never re-trigger that transition either. Both were flagged in review as
latent, content-dependent gaps rather than live bugs.
Comment thread src/engine/src/kinds/world-graph/validate.ts Outdated
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts Outdated
Comment thread src/engine/src/kinds/world-graph/validate.ts
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts Outdated
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts Outdated
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.test.ts
Comment thread src/engine/src/kinds/world-graph/validate.ts Outdated
Four review findings from the cleanliness-wear pass:

- `cleanliness-wear` accumulated deferred deltas with unchecked `+` while
  every peer accumulator uses `safeAdd`, and `building_meter_delta.delta`
  is unvalidated. Three sources summing across 2**53 rounded the
  intermediate and left the meter silently off by one, which the final
  clamp cannot detect. Now checked at the point precision is lost, with a
  test that fails when the fix is reverted.
- pipeline.ts held four raw NUL bytes as map-key separators, so file(1)
  classified it as binary and grep reported no matches without -a. The
  same separator is written as an escape in effects.ts and changes.ts.
  Two of the four came in with W82; escaping only the W83 pair would have
  left the file binary, so all four are escaped. Identical at runtime.
- `invalid_initial_wear` guards `<= 0` but its message claimed the value
  was zero, which misleads on a negative wear.
- `incident` is the contract's third ordered slot, not the fifth, in both
  the system doc comment and the 90-decisions entry.

Two contract-level findings are reported on the PR and deliberately not
applied here, since both need a contract amendment rather than a review
edit: `undeferrable_building_meter_effect` rejects content 20-contract.md
§9.2 permits ("Systems after 14 apply their own group locally"), and
`building_broken` is an eleventh `visible: true` audit reason where the
audit table states there are ten.
W83's guard forbade every `building_meter_delta` on the effect lists owned
by systems that run after 14 and never defer to it. The reasoning held for
`wear` and not for `cleanliness`, and as written it contradicted §9.2's own
"Systems after 14 apply their own group locally": an objective reward as
ordinary as `onCompleted: [{ building_meter_delta, cleanliness, +20 }]`
failed the whole campaign.

Only `wear` has a status transition hanging off it, so only `wear` can be
silently wrong when applied late — it clamps independently and can never
reach §4.16's `broken`. `cleanliness` has no transition, so a late delta is
merely clamped locally, which is what §9.2 already describes.

- `forbidBuildingMeterDelta` becomes `forbidUndeferrableWearDelta` and
  matches on `meter === "wear"`.
- §9.2 states the rule and names the three lists it covers.
- 90-decisions records the decision and what the two alternatives cost.
- Tests assert exact counts both ways: the wear variant produces 3
  rejections, the cleanliness variant 0. Verified by widening the guard
  back, which fails the positive case.

Two stale counts corrected in the generated guide while regenerating it:
both said five of the twenty world-graph tick systems are known-and-retained
gaps. W81, W82 and W83 each reduced that and none updated the guide; the
register now stands at two, one no-op (`alerts`) and one partial
(`incidents`). The digest gate does not check content, so neither was caught.
@The-Running-Dev
The-Running-Dev enabled auto-merge (squash) August 20, 2026 20:32
W83 shipped three codes the contract never listed. `building_broken` is the
sharp one: it rides on a `visible: true` record, and §13's audit table opened
with "All ten" while enumerating exactly that many, so the count was wrong
the moment W83 landed. `invalid_initial_wear` and
`undeferrable_building_meter_effect` were missing from the rejection table
alongside peers like `invalid_inventory`.

- Rejection table gains both validator codes at tier 1.
- Audit table gains `building_broken` and the count is re-derived from the
  rows rather than incremented — eleven.
- §13's note on why this class of omission survives every gate now records
  that W83 was its second occurrence.

The deeper fix — a test that fails when a `visible: true` reason is missing
from the audit table — is deliberately not here. §13 already says nothing
checks emitted against registered, and the manual control that replaces it
is now stated in the open register so /track can raise it as its own unit.
Parsing a markdown table from a test is a new kind of coupling and wants
deciding on its own, not as a side effect of a review pass.
After W83 every effect list has a building-meter rule except
`incidents[].onStart`, which is declared, shape-validated and dead — no
system applies it. W47 makes it live, and the rule depends on a choice W47
owns: if `onStart` runs only for system 16's rolls it lands after system 14
and a wear delta there could never reach the broken transition, so §9.2's
rejection should extend to it; if W47 also applies `onStart` at the
`start_incident` sites in systems 1 and 4, those run before 14 and could
defer legitimately, so extending it would forbid content W47 wants.

Both readings are defensible, so the review pass left the code alone and
recorded the question instead. The entry also notes the contract's own MVP
example puts a cleanliness delta in `onStart`, not a wear one, so the
wear-only rule would not contradict it either way.
W83 added a validator rejecting `initialWear <= 0`, because a building
created at the wear floor can never re-enter the broken transition — the
meter never changes, so the check never fires. The type comment beside the
field still read `integer 0..100`, so the contract advertised a value the
validator rejects.

Raised on an earlier review thread and left open there as a design call.
The design call has since been made and shipped; this is the contract
catching up to it.
Records the decision on the last open PR #348 thread: leave the
applied-before-clamp semantic drift in effects.ts rather than pursue
the under-reporting swap or the interpreter-seam fix, both out of
scope for this slice. Tracked at issue #349.
…sion

ConvertTo-HumanDocumentation.ps1 was not run after editing
90-decisions.md's marked block in 427ecd9, leaving the generated page
stale and failing Test-Documentation.ps1's drift check.
@The-Running-Dev
The-Running-Dev merged commit 04c1baf into main Aug 20, 2026
7 of 8 checks passed
@The-Running-Dev
The-Running-Dev deleted the slice/W83 branch August 20, 2026 21:43
The-Running-Dev added a commit that referenced this pull request Aug 21, 2026
## Summary
- **Branch cleanup**: deleted two remote branches fully merged into main
(`feature/sync-main-status-b5d31e`, `slice/W78`); left
`feature/bulgaria-arcs-expansion`, `slice/S1` (closed, unmerged PRs) and
`reconcile/w71-w76-tracking` (open PR #316) alone per user decision. No
local branches were eligible for deletion.
- **Track sync**: marked W78-W83 done in `design/30-slices.md`, linking
their closing issues (#326-#331) — all six had already merged (PRs #340,
#342, #343, #345, #347, #348) but the tracker still showed "Not
started".
- **CLAUDE.md**: retired the stale `qodo-code-review` reference in the
required-checks note now that automated PR review runs via
`anthropics/claude-code-action` (Qodo is retired).
- **Kit sync**: fast-forwarded `~/.agent-kit` from `af610a6` to
`e9e21bf` (9 commits) and reconciled command cores/tools into this repo.
`verify.md`'s core was `Unmigrated-Blocked` (this repo's gate table had
no companion); resolved by moving the table into
`.claude/commands/verify-local.md` under the `gate-commands` category
the core declares, recorded in `design/90-decisions.md`.
- **Regenerated human docs**: the tracker sync left `engine/TODO.md` and
its compatibility pointer stale against `design/30-slices.md`;
`build/Test-Documentation.ps1` caught it, so
`./build/ConvertTo-HumanDocumentation.ps1` was re-run and the output
committed. Also refreshed `verify-local.md`'s gate table — `verify.yml`
no longer flags `Test-Companion.ps1`/`Test-DesignState.ps1` as CI gates,
discovered while re-deriving the gate list from the workflow files
rather than trusting the memorized one.


### Verified
Ran and passed:
- Typecheck — `npm --prefix src/engine run typecheck`, exit 0
- Lint — `npm --prefix src/engine run lint`, exit 0
- Test — `npm --prefix src/engine test` (vitest) — 79/79 test files,
1127/1127 tests
- Pack package — `npm pack --silent` produced
`the-running-dev-game-engine-0.10.0.tgz`
- Inspect tarball — no `src/`, no `tsconfig*.json`, no `.test.*`
entries; `package/dist/` present
- Consumer smoke — clean install/build/smoke against the packed tarball
- Parse-check PowerShell scripts — every `tools/*.ps1` parses clean
- Validate Markdown links, terminology, and generated files —
`./build/Test-Documentation.ps1`: drift check passed across 18 generated
engine pages, compatibility pointers and the guide; 123 Markdown files
checked
- Build and verify landing page — `npm --prefix src/engine run build` +
`npm --prefix site run check`
(format/lint/typecheck/test/browser/build/merge) all green

Ran and failed:
- Run Pester tests — `Invoke-Pester -Path tools`: 241/253 passed, 12
failed. All 12 are in the newly kit-synced
`Read-DesignState.Tests.ps1`/`Test-DesignState.Tests.ps1`/`Test-CIWorkflow.Tests.ps1`/`Update-DesignProjection.Tests.ps1`
— self-referential tests (`Split-Path $PSScriptRoot -Parent`) that
validate the agent-kit's own dogfooded `design/state/` per-unit-record
mechanism and its `verify.yml` shape against whatever repo they're
copied into. This repo's `design/` uses
`00-brief`/`10-design`/`20-contract`/`30-slices`/`90-decisions`, not the
kit's `design/state/` scheme, so these fail structurally
(`StateSetAbsent`) rather than from a regression — the same gap
`Test-DesignState.ps1` itself reports as exit 2 when run directly here.
Full failing-test list and detail in `.claude/verify-report.json`.

Did not run:
- Build documentation — `docs.ps1` is absent from this checkout
(CLAUDE.md: installer-generated via `Invoke-SetupDocs`, not committed);
needs Docker and an installed `docs.ps1`, neither set up this session
- Merge landing page into documentation build — depends on the
`artifacts/docs` output of "Build documentation" above, which didn't run
- Test the host — `dotnet test` needs `NUGET_GITHUB_TOKEN` for the
sibling-repo NuGet feed, not set locally
- Positive route and probe smoke — needs a built host image, blocked on
the same missing token
- Negative fixture (corrupted artifact must fail to start) — same

---
<details><summary><b>Agent detail</b></summary>
<!-- agent:start -->

- **Not a slice** — housekeeping (branch cleanup, `/kit-sync`, tracker
sync) plus the doc-drift fix it surfaced
- **`.claude/verify-report.json`**: validated by
`tools/Test-VerifyReport.ps1` (`Valid`, 15 gates, 0 findings, 0
could-not-evaluate) before this section was rendered from it
- **Left undone:** the kit's self-referential Pester tests (12 failures)
— whether they should be scoped out of non-dogfooding targets is an open
question, not resolved here
<!-- agent:end -->
</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant