Skip to content

Aliveness-4: follow-ups, live fixes, on-demand cars & ridesharing (130/131) - #104

Merged
Maudfer merged 41 commits into
mainfrom
task/simulation-aliveness-4
Jul 21, 2026
Merged

Aliveness-4: follow-ups, live fixes, on-demand cars & ridesharing (130/131)#104
Maudfer merged 41 commits into
mainfrom
task/simulation-aliveness-4

Conversation

@Maudfer

@Maudfer Maudfer commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

The aliveness-4 branch: the round-4 visual-truth arc's deferred follow-ups, a long-standing world bug, three live-play bug fixes, and the on-demand-cars + ridesharing rework (tasks 130 + 131). One PR, code + docs + tests only — the regenerated history asset lands as its own dedicated final commit (a partial mid-regen asset that git add -A accidentally swept into the code commits was reset out; see the chore: commit).

Aliveness-4 follow-ups (tasks 123–128)

  • 128 street wander graph — gathering-venue loiter nodes + a seeded, loiter-biased wander pick.
  • 126 guardianship depth — a home-alone caring_for_children anchor + a jailed-caregiver rehousing fan-out.
  • 123 business-draw coherence — placement:"amenity" fences beach/cemetery/park to the construction menu + an unrepresented-category draw boost.
  • 127 homeless day-shape — a Brain hard-gate so the homeless never free-time-pick a home-located action.

The stampFootprint world bug (found live)

Field.stampFootprint registered a structure's destinations/roadAnchors key before tearing down the grass it replaced — and a grid-aligned structure shares that anchor key with the grass, so destroyStructure wiped it. roadAnchors/destinations came up permanently empty in built towns (so V2 ambulatory roam had no targets and 128 loiter nodes never registered). Fixed by registering after the teardown; regression-tested + revert-danced.

Live bug fixes

  • Pause buttonGameManager.emit's if (!payload) clobbered a falsy setTimeScale(0).
  • City Services "No facility" — the ledger only recomputed at midnight; now recomputes on business placement/teardown.
  • People stuck at 50×advanceTime skipped crossed in-game minutes (the per-minute departure pump missed them); now steps in ≤1-minute increments.

Task 130 — on-demand cars + coordinated ridesharing

Reverts task 129's persistent cars back to on-demand spawn/despawn (car appears as the driver leaves the origin, vanishes as they enter the destination), and builds ridesharing:

  • Multi-occupant Vehicle (driver + passenger list) that ejects every occupant on despawn.
  • startGroupRide — one car, a board window, driver routes.
  • canDrive gate + driver election — kids/severely-ill can't drive, kids can't reach a far school alone (a parent drives them), the ill are driven, nobody stranded.
  • Narrated flagships — a group ride reads "Drove {kid} to school" / "Drove {relative} to the hospital" in the per-person log.

Task 131 — proactive ride producers

Delivered more cleanly than the ticket assumed — one reactive mechanism + a thin co-scheduler compose:

  • Opportunistic carpooling — co-located, same-destination people share one car at the departure seam (R3/R5/R6/R9).
  • Household-outing producer — a weekend family trip to a venue, which the carpool folds into one car; a childless couple rides it as date night.
  • Direction-aware narration — school pickups and hospital discharges on the return leg.
  • Deferred: R2 (a cruising police carpool — a new movement model) and bespoke R7.

Asset / regeneration

Everything here is live-only glue (LiveWorld carpool, City-scheduled outings, live-only narration; new events are manual-only and never rolled off-map), so it's asset-neutral — the in-progress regeneration remains valid. The regenerated asset will be committed on top.

Test plan

  • Full Jest suite 2008 pass; npm run typecheck + npm run lint clean.
  • New coverage: carpool (one-car / walk / no-driver / overflow / LiveWorld-fold / different-destination), ride narration (outbound + return), the outing producer's weekend/venue/adoption/lone-adult gates, the stampFootprint revert-dance, and the three live bug fixes.
  • The execution arcScenarios live↔bootstrap keystone + W8 sprite audit stay green.

🤖 Generated with Claude Code

Maudfer and others added 23 commits July 20, 2026 00:04
…nistic pick

Ambulatory walks now prefer the curb in front of a gathering venue (park/beach/
bar/cafe/restaurant/cinema/gym/library/church) instead of drifting to an
arbitrary road anchor, and the wander pick runs on a seeded per-(worldSeed,
tick, person) stream rather than the global unseeded Phaser.Math.RND.

- Field.getLoiterAnchors: the road-anchor subset in front of gathering venues,
  recomputed lazily behind a dirty flag (marked stale on build/teardown, and by
  City on business assignment since a Workplace has no business at stamp time).
- Person.updateDestination: takes an optional WanderContext (loiter set + seed +
  tick); a SeededRandom(worldSeed).fork(personId).fork(tick).fork(pick) draw
  prefers a reachable loiter node (LOITER_BIAS 0.65) else the full roam set.
  Debug test people keep the legacy unseeded building wander.
- Field.update threads the loiter set, worldSeed and tick to ambulatory people.

Live movement stays frame-paced (arrival timing best-effort); the choice is
deterministic per seed. Tests: loiter bias, fallback, per-person determinism,
and the Field recompute filter/adjacency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two of the three V3-remainder pieces the aliveness-4 audit surfaced (a toddler
left home alone while both parents commuted; a single parent hauled to jail
leaving a child unminded):

- Home-alone care. City.unattendedYoungDependentAtHome reads households + live
  presence: the last available adult AT home with a co-resident child under
  CARE_AGE_YEARS (10) also home, no other available adult around. A new
  guardianshipHook anchors caring_for_children (obligation band, below a work
  shift) so that parent minds the child instead of drifting to a discretionary
  activity. Live-only resolver (presence is a map concept) → inert off-map.

- Dependent fan-out on caregiver loss. resolveRehousing now counts a DETAINED
  adult as an unavailable guardian, so a jailed sole caregiver's minors fan out
  to a relative exactly as an orphaning does (the death-only path never fired
  while the parent was alive). Illness keeps the parent home (not relocated).

Deferred: accompaniment (requirement 1) — V3's minAge gate already blocks young
children's discretionary located trips, leaving only school (on foot), so a
joint-plan escort primitive has almost nothing to bind to; held for later.

Tests: the reader's presence/other-adult/detained cases, the hook's four
branches, and the resolveRehousing jail fan-out (+ the not-jailed control).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit's generic work-lot draw put a beach between the bar and the bakery
and stacked duplicate supermarket/school while whole categories went unserved.

- beach/cemetery/park now carry placement:"amenity" and are fenced from all
  three random draw paths (generic draw, re-occupancy, entrepreneurship) via a
  shared isMenuOnlyBlueprint predicate (civic OR amenity). They are placeable
  through the construction menu (added to construction.json — their only spawn
  path, validator-enforced like civic) and stay venue-mapped so visiting_beach/
  park resolve when one is deliberately placed.
- The first-placement demand-deficit draw folds in an unrepresented-category
  boost, so the town spreads across categories instead of stacking a second
  supermarket/school while dining/leisure sit empty.
- Validators: economyContent accepts placement 'amenity'; the construction
  validator requires amenity blueprints be menu-pinned (like civic).

Asset byte-unaffected: the offline generator's logical world builds its roster
by round-robin over all blueprint keys (not openBusiness/the deficit draw) and
generateBusiness ignores placement, so no regen and no perf re-baseline (perf
suite green). Only the live map draw changed; determinism per seed holds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A person with homeless == true never has a location:'home' domestic action
proposed for free time — it would only request a transition to a home that
resolves to nothing and block on no_route tick after tick (the audit's
"spending time at home / watching TV at the rubble"). The outdoor repertoire
(walks, looking_for_a_home, park/bench rest) takes their weight instead.

Scope notes:
- The broad location:home data pass was reconsidered and NOT done: the named
  domestic actions are DISCRETE (they ignore location), and their real domestic
  wrappers (spending_time_at_home / sleep / resting_at_home_sick) are already
  location:home and already block for the homeless. The selection hard-gate is
  the correct, sufficient mechanism; resting/napping location-less actions stay
  valid outdoors (a homeless person on a bench is coherent).
- Asset byte-unaffected: the generator's logical world has elastic housing (no
  evictions → homeless is never set off-map), so the gate never fires during
  generation. No regen, no perf re-baseline.
- Deferred: a sleeping_rough/shelter action for the survival-sleep gap.

Tests: a housed person picks home actions; a homeless one never does but still
has an (outdoor) day-shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…28) in CLAUDE.md §4.18

124/125/128/126/123/127 landed on the branch; 123 & 127 turned out asset-byte-
unaffected (round-robin logical roster + elastic off-map housing). 129
(persistent household cars) remains deferred to the live-observation pass for
its W8 sprite-invariant check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s it replaced

Live-play finding: in a freshly-built town, Field.destinations AND roadAnchors
came up permanently EMPTY, so V2's ambulatory street roam had no road targets
and every walk fell back to building-entrance wander (the audit's persistent
entrance-clustering, finding 2.3) — and task 128's loiter nodes never
registered.

Root cause (confirmed live via add/delete instrumentation — ops were
"del K, add K, del K"): a road or building placed on a supertile anchor shares
its key "row-col" with the grass footprint it overwrites (grass footprints are
anchored on the SAME 3k+1 grid roads snap to). stampFootprint registered the new
structure's anchor and THEN ran the overwritten-teardown loop, whose
destroyStructure(grass) deletes that shared key — wiping the just-added entry.
Roads always snap to the grid, so roadAnchors was always empty.

Fix: move the destinations/roadAnchors registration to AFTER the teardown loop.
This also explains (and removes) the "ts-jest instanceof quirk" the loiter test
worked around — it was this same bug (grass anchors are grid-aligned in tests too),
not module duplication; loiterAnchors.test now uses the real build path.

Regression: two fieldMutations tests assert a grid-aligned road/building lands in
roadAnchors/destinations; revert-dance verified (buggy order → both fail).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A commute car now PARKS on arrival and is re-boarded on the next trip, instead
of the per-trip spawn-at-origin / despawn-on-arrival churn (the audit's "magic
pop-in/pop-out cars everywhere").

- Person.processTravel Arrived: disembark but KEEP the car linked + controlled
  (parked) instead of despawning. A controlled+unoccupied car sits still
  (Field.update skips wander on controlled; drive() gates on occupied) and is
  not an orphan (still linked → the runWakePass sweep skips it).
- City.startCommute: reuse the owner's PARKED car when it's near the body (the
  common round-trip case, since a trip parks it at the building the next leaves
  from). A car stranded far away is released (Person.releaseVehicle) and a fresh
  one spawns at the origin. A mid-drive (occupied) car is never reused — that
  keeps the W8 148-car-leak despawn via setVehicle.
- Field.removePerson despawns the owner's parked car (no orphan on removal).
- SaveManager marks a restored linked car controlled so it doesn't wander;
  parked cars re-derive across save/load (no save-version bump).

Live-verified in the observation pass: across two in-game days the vehicle count
stayed bounded (1-5, no leak), drivers retained parked cars, and every W8 sprite
invariant read zero at every sample. Tests: arrival parks (personTravel),
re-board reuse + removal despawn (commute); the re-plan 148-car guard still green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tale until midnight

Two live-play bugs found via in-browser analysis.

Bug — Pause does nothing. GameManager.emit/emitSingle defaulted the payload with
`if (!payload) payload = {}`, which clobbers a FALSY-but-valid payload. The Pause
button emits setTimeScale(0); 0 is falsy → became {} → setTimeScale({}) fell back
to 1×, so time never paused. Fixed the guard to `payload === undefined || null`
(0/false/'' now pass through). Also fixed a latent `await context ? … : …`
precedence bug in emitSingle (it awaited the context, not the callback result).

Bug — City Services shows "No facility" for placed buildings. recomputeServices
ran ONLY on newDay, so a hospital/police/fire/school placed mid-day didn't
register until the next midnight sweep. Now recompute on setupBusiness (placement,
incl. construction-menu civics) and closeBusiness (teardown/bankruptcy).

Tests: emit passes falsy 0/false/'' to handlers (phaser mocked so GameManager
loads headless); a placed hospital registers its healthcare facility immediately.
Both revert-dance verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…at 50x)

GameManager.advanceTime advanced the clock by the whole frame delta then emitted
the time-cadence signals ONCE, comparing only the final timestamp. At high speed
a single frame (a hitch at 50x: 100ms cap × 50 = two in-game minutes) jumped the
clock past a minute whose commute-departure / transition pump then never fired at
that exact minute-of-hour (LiveWorld schedules departures per minute) — so people
mid-transition (e.g. leaving a relative's house) stalled, worse the faster you ran
(the user's "orphaned people stuck near their relatives' house, more at 50x").

Fix: advance the clock in <=1-in-game-minute steps, emitting the cadence after
each step, so no minute/tick/day is ever skipped. At 1x a frame is a fraction of
a minute -> a single iteration, no behaviour change (timeConsistency keystone
still green).

(The "orphaned car sprites" the user also saw are task-129 PARKED cars — empty,
still owned, sitting at the curb by design — not orphans: auditSprites reads 0
orphanControlledVehicles / orphanSprites on the live instance.)

Regression test: a 3-in-game-minute frame emits timeChanged for minutes [1,2,3],
not just [3]; a sub-minute frame emits at most once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…28 DONE, 129 REVERTED

Task 130 is the four-pass plan the maintainer asked for: revert 129's persistent
cars to on-demand spawn/despawn, then build coordinated ridesharing on the
joint-plan machinery.

- Pass 1: every shared-ride case (drive kids to school + pickup, police carpool
  patrol, group/couple/household venue trips, relatives driving the ill to
  hospital, work carpool, drive-a-friend-home, accompaniment).
- Pass 2: audit of existing collective-action coverage — the agenda/linkId
  joint-plan pattern exists (visiting->hosting), but Vehicle is single-occupant,
  the commute is solo-per-person, and none of the driving collective actions or
  guards exist.
- Pass 3: the work — revert 129; multi-occupant Vehicle (eject-all on despawn);
  the shared-ride primitive; new drive_* / ride_* actions+events; the canDrive
  gate (kids/severely-ill can't drive, far-school ride, 126 accompaniment);
  amend school/treatment/routines; no loose ends.
- Pass 4: gaps — driver election + failure, co-location/board window, return
  trips, open-map/non-enterable destinations, despawn-with-occupants ordering,
  mid-ride interruption, one-car-not-N, determinism/bootstrap/asset parity,
  save/version, perf, the empty-car gate, feed legibility.

Housekeeping: renamed 123-128 -> _DONE and 129 -> _REVERTED (reverted by 130);
updated the README index + open-work note (incl. the four live-play bug fixes).
No code changes — implementation follows the maintainer's review of 130.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cars are on-demand again: spawn as the driver leaves the origin building (the
existing ExitingBuilding->WalkingToCar sequence), despawn as they enter the
destination (Person.processTravel Arrived). No parked cars accumulating.

- Person.Arrived: despawn the car (removeVehicle + unlink) instead of parking it.
- City.startCommute: removed the parked-car reuse/release block — spawn fresh.
- Removed Person.releaseVehicle (129-only).
- Field.removePerson still despawns a removed driver's in-flight car (retagged);
  SaveManager still marks a restored linked car controlled (anti-wander, retagged).

V1's smarts are KEPT: walk-vs-drive by distance, origin truth, no zero-length
drive, minors walk. Tests updated to the despawn-on-arrival / one-car contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pawn

Vehicle occupancy is no longer a single boolean. It now tracks an occupant list
(driver + passengers, SEAT_CAPACITY 4) so a car can carry a shared ride:

- board(person, asDriver) / disembark(person); the first boarder is the driver
  unless asDriver says otherwise. drive() gates on hasDriver() (a car with only
  passengers can't move). isOccupied()/isAboard()/getOccupants()/getDriver()/
  seatsAvailable() accessors. Person is a type-only import (no runtime cycle).
- Field.removeVehicle EJECTS every remaining occupant before despawning
  (Person.ejectFromVehicle): stepped out at the car's position, sprite restored,
  vehicle link cleared — nobody is ever left invisible-inside a vanished car
  (the W8 contract generalized from one driver to N riders).
- Callers updated: Person EnteringCar/ExitingCar/abortTravel/setVehicle, City's
  orphan sweep. The W8 audit's occupiedDriverlessVehicles now means
  isOccupied() && !hasDriver() (a phantom-passenger car).

Tests: the occupancy gate uses a real occupant; a shared-car despawn ejects both
driver and passenger. Vehicle unit tests use setDebugDriver for the drive gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d window

City.startGroupRide spawns exactly ONE car for a co-located group (driver + up to
SEAT_CAPACITY-1 passengers) heading to a shared destination, never one car per
rider. The driver drives; passengers are carried by boarding the same car:

- Person.setVehicle(vehicle, asDriver) + isDriver(): a passenger boards the same
  car but doesn't route it — only the driver sets the car's destination in
  EnteringCar; the passenger's travel machine (WalkingToCar -> board -> Driving
  waits on the shared car's isDestinationReached -> ExitingCar -> walk to entrance)
  carries them for free off the driver's arrival.
- Vehicle board window: setRideExpectations(n, frames) makes the car hold at the
  curb until all expected riders board (readyToDepart), so it never drives off
  without a passenger still walking to it; the window counts down so a no-show
  can't strand the car (it leaves without them). Solo commutes (1,0) depart
  instantly — unchanged.

Triggering (which producer installs a group ride) comes in Phase D/E; this is the
mechanism, tested directly: board-window gating, window lapse, and one-car setup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tranded

City.canDrive(person): a driver must be an adult, not detained, and not severely
ill (health >= MIN_DRIVE_HEALTH 0.35, read from the event context) — so children
never drive, a jailed person never drives, and the severely ill are driven by
someone else.

startCommute now routes a drive-distance trip by driver-eligibility:
- walk if within range (anyone, incl. minors);
- a driver spawns a solo car (as before);
- a NON-driver (child / ill / detained) bound FAR gets a lift — electDriver finds
  an available co-located adult (co-residents first) and starts a group ride with
  the non-driver as passenger. This is the "kids can't reach a far school alone"
  guard and the never-strand-a-non-driver net;
- no eligible driver -> walk as a last resort (far, but never stuck).

Narrated collective actions install their own rides upstream (Phase D); this is
the safety fallback at the transition seam. Tests: canDrive age gate; a far-bound
child is driven by a co-located parent (one car, correct roles).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s) remaining

The on-demand + ridesharing CORE is done and tested: on-demand cars, multi-
occupant vehicles with eject-all, the shared-ride primitive with a board window,
and the canDrive gate + driver election (kids/ill driven, never stranded). The
narrated collective-action layer (drive_kids_to_school etc. as real actions +
producers) and far-school scoring / patrol / outings / return trips remain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lift

A group ride now writes truthful per-person log entries derived from the
destination + riders: a minor bound for a school building narrates
"Drove {kid} to school" / "Got a lift to school"; anyone bound for a
hospital narrates "Drove {relative} to the hospital" / "Was driven to
the hospital"; else a plain "Gave {target} a ride" / "Caught a ride".
Six manual, effect-free texture events (events.json), invoked live-only
from City.narrateRide — bootstrap/the generator never call startGroupRide,
so the off-map RNG stream and the committed asset are untouched.

The narration rides the SAME election path that already forms the ride
(Phase E), so no separate proactive producer is needed for the behavior
to read; Phase F's far-school preference is already the enrollment sweep's
nearest-first scoring (task 058), so a child only rides when near seats
are full. Regenerated the event-classification + sim-relationships docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… as follow-up

Records the delivered scope: on-demand cars, multi-occupant Vehicle with
eject-all, the shared-ride primitive, canDrive + election, and the narrated
reachable flagships (school run, hospital drive, plain lift). Far-school
preference is the enrollment sweep's existing nearest-first scoring, and the
open-map guard already holds (outside transitions resolve with no car). The
speculative proactive-producer catalog (patrol carpool, group/household/couple
outings, work carpool) is a proposed follow-up rather than speculative content
under §5.6. README task table + narrative updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Captures the deferred R-cases from task 130's Pass 1 as a scoped ticket:
R2 police carpool patrol, R3/R9 group/household venue outings, R5 work
carpool, R6 couple outing, R7 drive-a-friend-home, and narrated return
trips (school pickup, hospital discharge). Grounded in the real
foundations — the N-person generalization of the Planner's 2-person
mirrored-agenda pattern, City.startGroupRide, JobOrchestrator, and
routines.json — with acceptance criteria and the open planning questions
left for the task's own exploration pass (per §5.4). Linked from task 130
and the README task table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hare one car

The unifying reactive mechanism for household/co-worker/couple rides (R3/R5/
R6/R9) with no bespoke producer. LiveWorld's departure phase now gathers, when
a departure comes due, every co-located companion (same origin building) bound
to the SAME destination and hands the group to City.startCommuteGroup, which
elects one driver and forms a single group ride (reusing startGroupRide +
narrateRide). A near destination is walked by all; a group with no eligible
driver falls back per-person (never stranded); a group larger than one car
spawns a second car for the overflow. Live-only (bootstrap/the generator never
run LiveWorld.pump) — off-map stream + asset untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
narrateRide now reads the ORIGIN as well as the destination: a minor driven
FROM a school back home narrates "Picked up {kid} from school" / "Rode home
from school"; anyone driven FROM a hospital home narrates "Drove {relative}
home from the hospital" / "Was driven home from the hospital"; the outbound
school run / hospital drive and the generic lift are unchanged. Four new
manual, effect-free texture events. Regenerated the event-classification +
sim-relationships docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…l (R3/R9)

A weekend household may plan a trip to a placed venue TOGETHER: City schedules
each co-resident member old enough to be at a venue on their own
(VENUE_INDEPENDENCE_AGE — younger children stay home with a guardian) the same
leisure-outing agenda entry (visiting_beach / eating_out / night_at_the_cinema)
in the same afternoon window. When those entries come due the members set off
from home to the SAME venue at once, and the Commit-1 carpool mechanism folds
them into ONE car — the group ride is emergent, no new coordination primitive.
Adoption is deterministic per (worldSeed, household, week); a town with none of
the venues placed schedules nothing. City-scheduled (handleNewDay), so the
off-map generator never runs it — asset-neutral.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the task file to _DONE, update the README table + narrative. The
proactive ride layer shipped as one reactive mechanism (opportunistic
carpooling) + a thin household-outing co-scheduler that compose to cover
R3/R5/R6/R8/R9 + narrated school-pickup/hospital-discharge returns. R2
(cruising police carpool — a new movement model) and bespoke R7 are deferred
as distinct features. All live-only, asset-neutral.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mmits

git add -A during tasks 130/131 staged the src/history .tbz files that a
concurrent asset regeneration was rewriting, baking a PARTIAL, inconsistent
asset into the code commits. Reset src/history to main's committed asset
(index-only — the working tree's in-progress regen output is untouched) so the
branch's net diff is code + docs only. The regenerated asset lands as its own
dedicated final commit once the run completes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Maudfer Maudfer self-assigned this Jul 21, 2026
Maudfer and others added 6 commits July 21, 2026 02:34
…letion

Loose-ends pass over the car/ride surface. Removed getDriver() and
seatsAvailable() — both had zero consumers in src and test (dead since the
130 multi-occupant refactor). Fixed a stale comment referencing a never-built
joinRide() (board() is the real entry). Added an end-to-end test that a
group-ride PASSENGER (non-driver) rides through to arrival without routing the
car and is never stranded — the one production path the ride tests exercised
only indirectly. All other new symbols verified consumed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
entriesByLink() had zero callers and was the ONLY reader of the AgendaEntry
linkId field — so the whole link tag was written by three producers (the
Planner visit, planJointActivity, the 131 household outing) and never read for
anything. Removed entriesByLink() (method + AgendaAccess interface), the linkId
field, and all three writers. The joint-plan linkage was never carried by the
tag anyway — mirrored entries run together because they share the same
earliest/latestTick window; the tests now assert that real mechanism instead of
the decorative tag. Behavior-neutral (linkId drove no logic).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shortens the default offline history-asset run to 90 recorded years. Validator
only requires recordYears >= 1; no test pins the value. CLAUDE.md updated to
match (250 living × 90 years).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…erson

Live-observed bug (a "Visiting Relatives" person standing frozen on the
street): the planner picked a relative/friend to visit from the pool/social
graph without checking they're actually ON THE MAP. A person-located visit
(locationOverride 'person:<id>') to someone NOT materialized resolves to
town-wide {kind:'outside'}, so the transition "arrives" instantly and the
visitor just stands where they are, visiting a ghost — 2 of 3 active
relative-visits in the observed town targeted off-map people.

Added an optional WorldAdapter.isPresent (LiveWorld: is the person on the
field). bestRelativeTarget/bestFriendTarget/the sick-visit finder now skip
non-present targets — returning null (no visit enqueued) when none are
present. Bootstrap/logical/the generator have no isPresent impl and default to
"present" (town-wide abstract, the sanctioned seam), so the off-map RNG stream
and the committed asset are untouched. Revert-danced (4 tests fail without the
filter).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
)

- Only ONE Person detail window at a time — opening another person replaces
  it (PersonSelected now 'replaceType'). The activity-label set follows the
  open person windows, so only that one person's label shows (debug show-all
  still overrides).
- Opening a dialog whose entity is already open brings that window to the
  FRONT instead of duplicating; clicking or dragging any window raises it.
  Each window carries a z (stacking order); interaction bumps it above the
  rest via onFocus, threaded Hud → detail window → Window (zIndex + onMouseDown
  + drag-start).
- Windows are keyed by a STABLE id instead of a per-render uuid, which used to
  remount every window on every HUD state change (resetting position/z). nextZ
  is hoisted to module scope so the open/focus handlers stay stable for the
  once-on-mount effect (clean exhaustive-deps).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Person / Business / House inspectors open at 800x700; Construction at
  400x415. Every dialog is capped at 80% of the viewport in each dimension
  (min() on open + maxWidth/maxHeight on the Rnd so a resize can't exceed it).
- Windows open just below the clock and left-aligned with it (origin 20,70;
  the clock sits at left 20 / top 20, ~38px tall) instead of "directly behind"
  it, and their z-index rides above the clock/toasts (base 1001) so a focused
  window is never hidden behind the HUD chrome.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Maudfer and others added 2 commits July 21, 2026 03:55
The feed was bottom-left anchored, so it expanded UPWARD. Re-anchored it to
the top-right just below the .time-controls speed buttons (top 52px ≈ their
~30px height + a 10px gap) and right-aligned with them; being top-anchored it
now grows DOWNWARD when uncollapsed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ngs (#5)

A person walking south briefly clipped UNDER the road tiles south of them:
Road.calculateDepth() was row*10, so a road at a higher (souther) row
out-sorted the person (depth (row+1)*10+1) until they reached it. Roads never
overlap, so they need no per-row ordering — Road.DEPTH is now a constant 1,
above soil (0) and below every building ((row+1)*10 >= 10) and person. Buildings
and people keep the "more south = more on top" rule, so tall buildings still
occlude people to their north. Updated the depth-ordering tests (a southern
road never out-sorts a northern person/building) and CLAUDE.md §4.5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Maudfer Maudfer added the bug Something isn't working label Jul 21, 2026
Maudfer and others added 10 commits July 21, 2026 04:20
… gate)

The 131 ride methods on City.ts (startGroupRide/startCommuteGroup/canDrive/
electDriver/narrateRide) were tested only in the agents module (commute.test),
which the per-module coverage gate discards for execution (City.ts is
execution-owned) — leaving execution at 78.4%. Added an execution-harness ride
suite in city.test.ts (election, one-car carpool, walk/overflow, school/
hospital/return narration, LiveWorld.isPresent), lifting execution to 82.3%.
Also covered the previously-untested planner jail-visit producer, lifting
actions/Planner 79.4% → 91%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vehicle.hasDeparted() (has a route AND is cleared to leave — past the board
window) marks a car as in-motion. Person's EnteringCar step now abandons the
boarding if the car has already departed and it isn't aboard: a rideshare whose
board window lapsed, or any moving car, no longer gets a rider leaping into it —
they clear the link (without touching the driver's car) and fall back to idle so
the Brain re-plans on foot. A driver still boards its freshly-spawned, un-routed
car (not yet departed), and normal boarding is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#1+#2)

Replaces the one-shot 'person:<id>' → location snapshot (which only worked for
building targets, no-op'd for street targets, and used the 'home' key alias so
two people each at their own home falsely "co-located") with a first-class
{ kind:'person' } transition that PURSUES the target:

- LiveWorld registers a pursuit and, each pump, re-resolves the target's current
  location and routes the pursuer toward them — commute-and-enter for a building/
  home (their OWN home, never the pursuer's), a new directed outdoor walk
  (Field.walkPersonTo → Person.walkOutdoorsTo, driven by walk()) for a street
  cell, re-chasing only when they move — resolving on true co-location.
- coLocated(a,b) is PHYSICAL (same concrete building / outdoor cell), added to
  the WorldAdapter. ActionEngine's materialize, the running standing-gate, and
  the interaction contract (requiresSameBuilding) all use it, so a visit/interaction
  runs only when the two are actually together and re-pursues if the target walks
  off — no more "visiting" from your own couch across town.
- ActionEngine hands 'person:<id>' straight to the world as { kind:'person' }
  instead of pre-resolving; venue/building/home semantics unchanged.
- BootstrapWorld/the generator resolve a person target immediately to the target's
  own location (town-wide abstract — the sanctioned seam), so the asset is
  untouched; arcScenarios stays green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ral)

Two seam guards so the offline generator is unchanged by the pursuit rework:
- LogicalWorld.requestTransition resolves a { kind:'person' } target to the
  target's own current location immediately (as BootstrapWorld does) — exactly
  what ActionEngine used to pre-resolve, so the stored location + immediate
  arrival are identical.
- The running standing-gate re-checks co-location only for CONCRETE person
  targets (building/home), matching the pre-131 gate off-map (homes are building
  keys there, never the 'home' alias); an outdoor target stays ungated (reaching
  them is materialize's job). Live still uses physical coLocated. The departure
  log only fires on a pending transition, which never happens off-map.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r boarding (CLAUDE.md §4.7/§4.14)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LiveWorld is execution-owned but the pursuit was exercised only in the agents
module (commute.test), which the per-module gate discards for execution. Adds
an execution-harness test driving coLocated + a person pursuit to arrival and
the absent-target cancel, lifting execution 80.6% → 82.3%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dow (#3)

The 800×700 house window sizes the family-tree SVG big enough that, on a
viewport short enough for the 80% cap to shrink the window, the SVG overflowed
and intercepted pointer events over the resident links (integration regression:
house-resident not clickable). Fixes:
- .house-details is now a flex column; the resident list keeps its own space at
  the top with position/z-index above the family-tree SVG (which fills the rest
  and clips), so it can never be covered.
- Resident rows are content-width, so the click target is the NAME (far left) —
  a large sibling inspector opened/dragged over the house window no longer
  covers it. Also better UX.
All 25 HUD integration tests pass locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nners

The full round-trip test steps the sim with hundreds of per-chunk CDP samples;
it finishes in ~30s locally but consistently edged past the 60s default on the
CI runner (pre-existing flakiness, unrelated to the pursuit/HUD work). Bumped
its timeout to 150s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Windows had a double scrollbar: .window-body scrolled (overflow:auto) AND
  each detail window wrapped its content in a second overflow:auto container.
  Also .window-body used height:-webkit-fill-available, which overflowed the
  flex column and added its own scrollbar. Now .window-body is the SINGLE
  scroller — flex:1 + min-height:0 sizes it correctly between header and footer
  — and the redundant inner overflow containers are removed.
- Renamed 130-ridesharing-and-on-demand-cars.md → _DONE (+ updated references
  in README.md and the 129 task file).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Maudfer
Maudfer merged commit 84c1ba4 into main Jul 21, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant