Skip to content

Simulation Aliveness 4 — the visual-experience arc (V1–V11) - #103

Merged
Maudfer merged 19 commits into
mainfrom
task/simulation-aliveness-4
Jul 20, 2026
Merged

Simulation Aliveness 4 — the visual-experience arc (V1–V11)#103
Maudfer merged 19 commits into
mainfrom
task/simulation-aliveness-4

Conversation

@Maudfer

@Maudfer Maudfer commented Jul 20, 2026

Copy link
Copy Markdown
Owner

The round-4 arc: the movement is truthful now (rounds 1–3) — make it sensible, and make place, purpose, and company real on the street. Audit + proposal: docs/proposals/simulation-aliveness-4-audit.md; engine narrative: CLAUDE.md §4.18. One PR, all phases (V1–V11), sequential commits.

What landed

  • V8 — sprite hardening & the movement tracer. A standing per-frame movement tracer in TestHarness (state timelines, breadcrumbs, teleport/disembark/renderJump/stuck/overlap/spriteAudit anomalies); a definitive orphaned-sprite reconciliation sweep in MainScene (a sprite exists iff its entity is live); forceEvent now routes signals through the real City consumers.
  • V11 — time & clock. New games start at 09:00; the speed ladder is 1×/10×/50×; the 50× distortion pass makes Person.walk/Vehicle.drive consume the whole frame's travel budget across segments (feet keep up with the clock, no overshoot; 1×/4×/8× stay byte-identical).
  • V1 — the trip planner. Origin truth (the car spawns by the BODY, not a distant home), walk-vs-drive by distance, no zero-length drives, and the "sleepwalk" stale-path fix.
  • V2 — outdoor place-hood. Cell-scoped outdoor co-location (a lend can't cross the map; bootstrap stays town-wide — the sanctioned seam); ambulatory walks roam the roads and stop mid-block (the entrance-cluster fix).
  • V10 — locomotion & the real chase. Per-kind speed with a police chase premium; the chase resolves by physical co-location, not a dice roll.
  • V9 — collective-action integrity. visiting_friends/visiting_relatives are planner-located to a real person with a mirrored hosting_a_friend_visit, both ending together; a validator bans free-rolling social actions.
  • V3 — guardianship. A minAge gate on actions (venue default + explicit 18+ for job hunting) — no more toddlers taking solo shopping trips.
  • V4 — scene aftermath. A burning-building transition gate (nobody walks back into a fire); displacement interrupts stale home activities (no "resting at home" at the rubble).
  • V5 — service roles. Nurses treat — a nurse-only hospital is no longer a waiting room.
  • V6 — street render polish. Labels only over inspected people (debug show-all on L); formation offsets separate all co-located people, eased so re-slots glide.
  • V7 — rhythm. A night-floor on taking_a_walk (no more 3 AM strolls).

Validation

  • Full Jest suite: 1947 passing; ESLint + markdownlint clean.
  • Offline-generator perf gates re-baselined for the intentional behavior shift.
  • Playwright integration smoke test passes (the prod build boots, the HUD mounts, the window.__townbox determinism hook installs).

Maintainer pre-merge steps (the aliveness-2/3 pattern)

  • The deep in-browser observation pass (blocked this session only by dev-tooling flakiness after a mid-session port change — the prod build boots fine).
  • The asset regeneration on the arc's engines.

Deferred follow-ups are called out per-workstream in the proposal Part 6 and CLAUDE.md §4.18 (the business draw-coherence guard, evacuation rally/conclusion, deferred venue-need planning).

🤖 Generated with Claude Code

Maudfer and others added 19 commits July 19, 2026 20:17
The round-4 observation session findings (movement tracer + live Chrome
play + static sweeps) and the proposed arc, including the maintainer
additions M1-M8. This is the plan the branch executes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Commit the movement tracer (TestHarness): per-frame state timelines,
  breadcrumbs, and anomaly detection (teleport/disembark/renderJump/stuck/
  overlap/spriteAudit); activityCensus; startTrace/traceReport/etc.
- Definitive orphaned-sprite sweep (MainScene M2): every created person/
  vehicle sprite is registered and reconciled per in-game minute against
  Field's live lists — a sprite exists iff its entity is live. New
  countOrphanSprites() feeds auditSprites().orphanSprites.
- forceEvent routes invoke signals through City (M2): extracted
  consumeResultSignals from handleTick; new City.forceEventAndConsume so a
  forced crime files a real incident (the raw invoke dropped every signal).
- Teleport classifier: the by-design car-exit sync is 'disembark', so a
  real 'teleport' means a bug and alerts at zero noise. Person.getSpeed()
  keeps the threshold honest ahead of V10 per-kind speeds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New games start at 09:00 (M7): NEW_GAME_START_TICK seeds the clock in
  startNewGameWorld (new-game-only path; loads restore saved time). A town
  already awake and commuting, not a dead midnight.
- Speed ladder 1x/10x/50x (M8): TIME_SCALES = [1, 10, 50]; the HUD toolbar
  and T-key cycle derive from it automatically.
- 50x distortion pass (M8): Person.walk and Vehicle.drive now consume the
  whole frame's travel budget across as many curb/lane segments as it
  covers, clamped per axis (no overshoot). The old single-step reached one
  waypoint and RETURNED, wasting the rest — so at 50x feet fell behind the
  clock and commutes ate extra in-game hours. One iteration at <=10x, so
  1x/4x/8x are byte-identical; only large 50x/hitch deltas iterate.
- timeConsistency extended to 10x/50x (walker lands the identical position
  and clock at every speed); ladder + 09:00 assertions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit found every trip was a car trip spawning at the person's HOME
regardless of where they stood: a woman out walking would walk home, board
a car parked at her own door, drive zero metres, and get out. Three fixes
in City.startCommute:
- Origin truth: the car spawns on the road nearest the BODY (adjacent road
  if at a building, else Field.nearestRoadTile of the pixel position; body
  fallback for road-less worlds) — never the person's distant home.
- Walk vs drive: trips within WALK_COMMUTE_MAX_TILES (12) are walked; the
  car is reserved for real commutes.
- No zero-length drive: origin road == destination road → walk.

Plus Person.setAmbulatory clears the stale wander path on an ambulatory
action's end, killing the tracer's 'sleepwalk' (the body finishing a stale
stroll leg for minutes after the action was over).

Tests: walk-short + origin-truth in commute.test.ts; the 148-car W8 test
made faithful (re-plan while genuinely far from home).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit found 'outside' was ONE town-wide location: any two pedestrians
anywhere co-located, so an object was lent between people 386px (24 tiles)
apart, and street chats/witnesses/gossip crossed the whole map.

- Cell-scoped outdoor co-location: LogicalLocation's outside variant gains
  an optional ; LiveWorld.locationOf tags an outdoor person with the
  4-tile map patch they stand in, and peopleAt filters by it. A cell-less
  {kind:outside} still means 'anywhere outdoors' (the global 'is a chase
  on?' query, and bootstrap/logical worlds — the sanctioned seam, like
  venue hours, so the generator stays town-wide and deterministic).
  locationKey/parseLocationKey/sameLocation all cell-aware.
- Street roaming: ambulatory 'taking a walk' now wanders the ROAD anchors
  and stops mid-block, instead of pathing to a building entrance and
  clustering at civic doorways (the maintainer's long-standing sighting).
  Field tracks roadAnchors; Field.update routes ambulatory people to them.

Tests: cell co-location (far/near/global) in worldAdaptersGaps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
M5 — per-kind movement speed. Running was purely nominal: a fleeing
suspect, a jogger, and an evening stroller all moved at one fixed walk
speed. Person now maps its authored ambulatory kind to a real speed
(stroll 0.9x, jog 1.6x, run 2.2x), and the police pursuit gets a premium
(chase 2.5x) so an officer visibly CLOSES on a fleeing suspect. City
derives the kind each minute from the active action (chasing_a_suspect ->
chase).

M6 — the chase resolves PHYSICALLY, not by a dice roll. A suspect could be
"caught" while the officer was across town. Now City.resolveChase checks
co-location: an on-duty police officer in the suspect's own street cell
(V2's local co-location, closed by the M5 chase premium) is a catch;
otherwise the suspect outran them and got away (the impunity path). The
flee action's duration is the chase time, so no insta-catch. The off-map
generator keeps its abstract roll (LogicalWorld.resolveChase) — no sprites
off-map, the sanctioned seam.

Tests: locomotion-speed ordering; a distant officer does not catch
(evaded, not arrested); the arrest ceremony now catches via co-location.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit found visiting_friends was a free-time continuous action with no
interaction and no target: it ran wherever the body was ("Visiting friends"
floating over a business), with no friend and no host. Same for
visiting_relatives, hosting_gathering, taking_a_walk_together.

- visiting_friends / visiting_relatives are now planner-only (free-time
  weight 0) and ALWAYS located to a real person. The planner enqueues a
  MIRRORED hosting_a_friend_visit for the host (new action), linked to the
  same window — so the friend/relative is genuinely hosting at their home
  at the same time, both ending together. A visit with no real target is
  simply not made (no unlocated fallback).
- New see_relatives routine drives visiting_relatives to a living kin;
  bestFriendTarget/bestRelativeTarget pick the target.
- Anti-regression validator: a targetless, location-less social continuous
  action that satisfies the social need must be planner/joint-only
  (weight 0) — it can never free-roll again. Invalid fixture added.

Tests: the friend visit enqueues a linked host mirror; the validator
rejects a free-rolling social action; reachability + fire-evacuation tests
updated for the weight shift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit found a 2-year-old taking solo shopping trips, roaming to venues,
and job-hunting across town — no guardianship layer at all.

- New `minAge` field on actions, enforced once in ActionEngine.startAction:
  below it, a typed requirementsUnmet (the child stays home, where the
  household-meal fan-out feeds them). Venue-located actions (shops, cafes,
  gyms…) get a default independence floor (VENUE_INDEPENDENCE_AGE = 8) even
  without an explicit value; job_hunting is explicitly 18+. School (a
  `building` location) is unaffected — children still walk to school.
- Validator: minAge is a non-negative number; ACTION_KEYS + fixture.

Upkeep folded in: regenerated simulation-relationships.md (V9's
hosting_a_friend_visit bumped the action count — the Windows docs:sim env
syntax had silently no-op'd); re-baselined the generation op-counts for the
intentional V9/V3 behavior shift (fewer visit evals, +context builds from
the age check).

Tests: a toddler can't visit a venue or do an 18+ errand; an 8-year-old
clears the venue floor but not the errand; an adult does both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit watched a man go back to bed IN his burning house (evacuation
ended, he re-picked sleep at home) and a freshly-homeless woman keep
"resting at home" at the rubble (her running instance survived teardown).

- The burning gate (LiveWorld): a located transition INTO a building with
  an open fire is refused — at request AND in the pump (a building that
  ignites mid-trip cancels like one that vanished). Nobody walks back into
  a fire to sleep or work; the evacuation hook owns the exit. City wires
  isBurning via Game.incidents.openFireAt.
- Re-validate running home activities on displacement: when a household is
  evicted/demolished, each member's home-located instance is interrupted,
  so a stale "spending_time_at_home" can't keep running at a rubble lot —
  the woken brain re-plans from reality (shelter-seeking, the street).

Tests: a transition into a burning building is refused, a non-burning one
is fine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit found a nurse-only hospital treated NOBODY: the doctor-rounds
hook was gated to jobKey 'doctor' alone, so a staffed ward the coverage
ledger counted as healthcare healed no one, and a seriously-ill man wandered
off to run errands past an open-but-doctorless clinic.

- The rounds hook now admits both medical treating roles (doctor AND nurse)
  via TREATING_JOB_KEYS. treating_patient carries no role requirement, so a
  nurse runs it at the same mechanism — a staffed hospital is a treating
  one. was_treated_by_doctor still lands, so the recovery boost applies.

Test: a nurse-only ward treats a co-located patient-in-treatment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- M1: activity labels render only over INSPECTED people (a person window
  open), so the street isn't a wall of overlapping "Taking a walk" text.
  The HUD emits inspectedPeopleChanged when person windows open/close; the
  scene gates labels on it. A masterSwitch-gated L key toggles show-all for
  debugging.
- Formation offsets now separate ALL co-located visible people (grouped by
  position cell, slotted by stable id), not just same-activity groups — a
  couple's walk or a chase pair with differing activities used to render as
  one overlapping sprite. The activity alias is gone (co-location handles
  the chase pair).
- Eased offsets: the rendered offset lerps toward its target so a re-slot
  glides instead of popping 8-10px (the "jitter clips" sighting). Cleaned
  up on sprite reconciliation.

Scene-layer (browser-suite territory); full Jest suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Night-floor on taking_a_walk: a strong ×0.05 suppressor for 22:00-06:00,
  so the town stops taking 3 AM strolls (the audit's midnight-empty-but-not
  streets). evening_stroll already had a daytime floor; taking_a_walk had a
  daytime boost but no night floor.
- Re-baselined the generation op-counts for the shifted night-time picks.

Verified (no change needed): live first-offense sentencing is already
fine-only (convictSuspect checks priors BEFORE the fresh got_caught lands) —
the aliveness-3 "first offense → jail" sighting is inflated priors carried
in from the off-map crime carousel, a generator/asset-regen concern.

Deferred (own follow-up, economy/determinism/asset risk): the business
draw-coherence guard (beach/cemetery/duplicate categories) — it perturbs
the determinism-tested draw and the committed asset, so it belongs with the
maintainer's asset regeneration pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The engine narrative for V1-V11 in CLAUDE.md §4.18, and the proposal marked
implemented. Validated by the full Jest suite (1947), ESLint, the perf
gates, and the Playwright integration smoke test (prod build boots, HUD
mounts, determinism hook installs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seven task files for the workstream remainders the arc consciously held
back, each self-contained and JIRA-style with concrete code references:

- 123 Business draw coherence (V7): placement-aware draw + amenity fencing
  (no beach/cemetery downtown, no duplicate schools). Determinism/asset.
- 124 Evacuation as a scene (V4): rally + conclusion + family-notify wakes.
- 125 Deferred venue needs (V5): a closed venue defers to opening instead
  of dissolving; severity-banded illness gating.
- 126 Guardianship depth (V3): accompaniment, home-alone care, dependent
  fan-outs on caregiver jail/illness/death.
- 127 Homeless day-shape + domestic home-locations (V4/Appendix-B.1): the
  domestic-location data pass + a homeless shelter repertoire. Generator.
- 128 Street wander graph + seeded wander (V2): loiter nodes + seeded pick.
- 129 Persistent household cars (V1 stretch): park it, don't conjure it.

README table + Open-work note updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit's seriously-ill man went grocery shopping past a placed-but-CLOSED
hospital: `hasVenue` returns false for both "no hospital" and "hospital
off-hours", so the treatment producer proposed nothing and an errand filled
the slot.

- New `WorldAdapter.hasVenuePlaced` (LiveWorld: a hosting business exists
  regardless of hours) distinguishes closed from absent.
- The treatment hook now proposes `resting_at_home_sick` (need band,
  urgency-scaled) when the hospital is placed-but-closed — the sick person
  WAITS at home until it opens (the normal treatment proposal then takes
  over), instead of dissolving the need into an unrelated trip. A town with
  no hospital keeps the plain 092 behavior.

Test: a placed-but-closed hospital makes the sick rest, not shop.

Remaining for task 125 (own commits): the general shopping/dining deferral
via the same primitive, and the severity-banded illness data suppression
(requirement 2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The second half of task 125: a seriously-ill person (health < 0.35) has
their going-out/leisure/social/outdoor free-time weights collapsed to a
token in the Brain's selection (both selectActionForNeed and
computeFreeTimeAction), so serious illness visibly cancels the day — the
audit's mildly-and-not-so-mildly ill people who walked and visited until
midnight now recover at home. Home/recovery actions are untouched and
dominate.

Perf op-counts re-baselined (the Brain gate perturbs the generator stream);
the asset regeneration is the maintainer's step.

Test: a healthy person goes out; the same person seriously ill goes out
strictly less.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 6-hour "Evacuating!" wander the audit saw is already bounded by V4's
burning gate (a person can't re-enter the burning building, so evacuation
is a one-shot 2-tick action, not a re-entry loop). This adds the missing
family-notify channel:

- New `homeFire` WakeKind: a fire at an occupied HOME enqueues a reactive
  Brain wake for its residents (City.runFireHazard), so occupants elsewhere
  re-plan at the minute instead of the next hourly flip — the mood valence
  (lost_home_to_fire) already lands; this drops them out of their routine
  now. Live-only (the generator has its own fire path).
- City.hasPendingWakes() test accessor.

Test: an occupied home's ignition queues a wake (cityReactiveLoops).

Remaining (noted in the task file): a richer rally-to-a-named-safe-spot and
a dedicated kin "gather at home" hook — polish on top of the bounded
evacuation + the wake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Maudfer Maudfer self-assigned this Jul 20, 2026
@Maudfer Maudfer added the enhancement New feature or request label Jul 20, 2026
@Maudfer
Maudfer merged commit bc4100d into main Jul 20, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant