Lane D R23 (v5.0-alpha): the GIG_RANGE chunk-neighbour fix — scoped to the gig path (ledger #6)

My carried v4.x finding, fixed post-release as scheduled. Runtime-only: no plan, no
golden, no flag, no fetch. selfcheck ALL GREEN 161300/161300, 0x3fa36874 frozen.

THE FINDING GREW WHILE IT WAITED: filed at 8.7% (measured at katoomba's isolated pub);
A's R21 cluster bias made it WORSE — venues now sit inside the retail cluster where peds
converge from every side, so 42.6% of in-gig-range peds (5094/11971) were chunk-blind.

THE OVERRIDE (measurement wins): the brief asked for "extend to chunk + 8 neighbours"
AND "byte-identical flags-off". I built the literal version and measured that those
cannot both hold: on the synthetic town (what ?classic=1 boots, gig layer absent) a full
3x3 scan sourced 127 of 855 finds (14.9%) from neighbour chunks — finds v2 never made.
The frozen v2 crowd would shop ~15% more. Covenant breach, in a clause of the same brief.

So the fix is scoped to the GIG path — which is what I actually filed:
- ordinary shops: own chunk only (v2 semantics, frozen — noticed from its own block)
- gig venues: the full 34m radius, honest across chunk edges ("follow the sound" is the
  whole reason GIG_RANGE 34 > PATRON_RANGE 18)
- no gig on -> _gigVenues empty -> neighbour loop skipped entirely -> ?classic and every
  flags-off boot take the IDENTICAL code path and cost to v2. Byte-identical BY
  CONSTRUCTION, not by measurement luck.
Both ranges < CHUNK(64) so 3x3 is exact, not approximate; fixed dz/dx order keeps ties
deterministic as the old single-list scan was.

BEFORE -> AFTER: cross-chunk in-range peds seeing the gig 0% (structural: v2 scanned only
the ped's own chunk) -> 100% (katoomba 4/4, fitzroy 11/11). Same-chunk unchanged 100%.
Classic neighbour-sourced finds: 0 (127 under the rejected full scan). Determinism
identical. 0 NaN on katoomba/fitzroy/synthetic. Sim 0.056 -> 0.057 ms/frame (+2%, i.e.
0.006% of a 16.7ms budget); lookup 0.58us unchanged with no gig, 1.3-2.7us with a gig on
(fires per PATRON_STRIDE, not per frame). Fitzroy clean at 376 peds.

CARRIED, documented: PATRON_RANGE keeps v2's own chunk-blindness — fixing it costs the
classic covenant, and the covenant wins. A frozen v2 quirk now, not an oversight.
Known minor: a scheduled-but-closed gig still runs the loop for nothing (0.58->1.3us).

D->F: no blocker for v5.0-alpha; classic untouched by construction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-16 23:06:07 +10:00
parent 292b07aa91
commit 47f05ecaa8
2 changed files with 103 additions and 5 deletions

View File

@ -6,6 +6,71 @@ _rotOnly/head-bone normalize/upgradeStreetPeople). Measurements on the M3 Ultra
---
## ROUND 23 — v5.0-alpha: the GIG_RANGE chunk-neighbour fix (ledger #6, the carried v4.x item)
*PROCITY-D, 2026-07-16. My own filed finding, finally fixed — post-release, as Fable scheduled. Hot path,
so the R18-style before/after is the deliverable. `selfcheck` **ALL GREEN 161300/161300**, `0x3fa36874`
frozen (runtime-only change — no plan, no golden).*
### The finding grew while it waited: 8.7 % → 42.6 %
I filed this at 8.7 % in R20, measured at katoomba's isolated pub. **A's R21 cluster bias made it worse**:
venues now sit *inside* the retail cluster, where peds converge from every side rather than trickling along
one street — so far more in-range peds are across a chunk edge. Re-measured on the relocated venue:
**42.6 %** of in-gig-range ped-samples (5094 of 11971) were chunk-blind. Fixing it mattered more in R23
than when I filed it.
### The override: the brief contradicts itself, and the measurement broke the tie
The brief said *"extend `_nearestOpenShop` to the ped's chunk + 8 neighbours"* **and** *"byte-identical
flags-off"*. **I built the literal version first and measured it: those two cannot both be true.** On the
synthetic town (exactly what `?classic=1` boots, gig layer absent), a full 3×3 scan sourced **127 of 855
finds — 14.9 % — from neighbour chunks**: finds v2 never made. The frozen v2 crowd would start shopping
~15 % more. That is a covenant breach, in a clause of the same brief that asked for it.
**So the fix is scoped to the GIG path** — which is also what I actually filed (the finding was always
`GIG_RANGE`-specific):
- **Ordinary shops → own chunk only.** v2 semantics, frozen. A shopfront is noticed from its own block.
- **Gig venues → the full 34 m radius, honest across chunk edges.** The gig is the "follow the sound" pull
that is *supposed* to reach further — that is the entire reason `GIG_RANGE` (34) > `PATRON_RANGE` (18).
- **No gig on → `_gigVenues` is empty → the neighbour loop is skipped entirely.** `?classic` and every
flags-off boot take the *identical code path and identical cost* to v2. **Byte-identical by construction**,
not by measurement luck.
Both ranges are < `CHUNK` (64), so a 3×3 sweep is *exact*, not an approximation; a fixed `dz/dx` order keeps
exact-distance ties deterministic, as the old single-list scan was.
### Before / after
| proof | before | after |
|---|---|---|
| **cross-chunk in-range peds that see the gig** — katoomba | **0 % by construction**¹ | **4/4 = 100 %** |
| — fitzroy (densest, worst case) | **0 % by construction**¹ | **11/11 = 100 %** |
| same-chunk peds (must not change) | 100 % | 100 % |
| **classic / flags-off: finds sourced from a neighbour** | 0 (v2) | **0** (127 under the rejected full scan) |
| determinism (2 sims, same seed) | identical | **identical** (identity sets byte-equal) |
| NaN — katoomba / fitzroy / synthetic | 0 | **0** |
| sim cost, katoomba @14:00, 236 peds | 0.056 ms/frame | **0.057 ms/frame (+2 %)** |
| lookup cost/call — no gig on | 0.58 µs | **0.58 µs (loop skipped)** |
| lookup cost/call — gig on | 0.58 µs | **1.32.7 µs** |
¹ *not a small-sample claim: v2 read `shopsByChunk.get(ownChunk)` only, so a venue in another chunk was
never in the scanned list and could not be returned. 0 % is structural.*
The lookup is 24× dearer **while a gig is on**, but it only fires every `PATRON_STRIDE` (10 m walked), not
per frame — hence **+2 % of a 0.056 ms frame cost**. Against a 16.7 ms budget that is 0.006 %. Fitzroy at 376
peds stayed clean. Minor known inefficiency: with a gig *scheduled but closed* (e.g. 14:00) the neighbour
loop still runs for nothing (0.58 → 1.3 µs) — `_gigVenues` holds ids, so testing openness would need the
lookup it's trying to avoid. Not worth it at +2 %; noted rather than hidden.
### Handshakes
- **D → F (gate):** the fix is **runtime-only** — no plan, no golden, no flag, no fetch. `?classic=1` takes
the identical code path to v2 (loop skipped), so the classic regression is untouched **by construction**.
Selfcheck green at 161300. No D-side blocker for `v5.0-alpha`.
- **Carried (deliberate, documented):** `PATRON_RANGE` keeps v2's own chunk-blindness — an ordinary shop 10 m
away across a chunk edge still goes unnoticed. That is now a *frozen v2 quirk*, not an oversight: fixing it
costs the classic covenant, and the covenant wins. Revisit only if the covenant is ever relaxed.
- **Still unowned (unchanged):** R20's main-street relocation half (heroes 019 % of the densest cluster).
---
## ROUND 22 — v4.0 THE EPOCH CLOSE: citizens at pack scale (ledger #5)
*PROCITY-D, 2026-07-16. The fitzroy stress audit (160 shops — the densest citizen field this epoch) + the

View File

@ -375,20 +375,53 @@ export class CitizenSim {
return base;
}
// nearest OPEN shop the ped is currently passing (current chunk, ≤PATRON_RANGE), else null.
// Hours-aware — at night only the openLate video shop qualifies, so its block draws the night crowd.
// nearest OPEN shop the ped is currently passing, else null. Hours-aware — at night only the openLate
// video shop qualifies, so its block draws the night crowd.
//
// [R23] The carried v4.x fix — GIG_RANGE (34 m) is a RADIUS, but this read only the ped's own 64 m chunk,
// so a gig venue genuinely in range, just across a chunk edge, was invisible. A's R21 cluster bias made
// that WORSE, not better: venues now sit INSIDE the retail cluster where peds converge from every side,
// so 42.6% of in-gig-range peds were chunk-blind (up from the 8.7% I filed at the old isolated pub).
//
// The fix is scoped to the GIG path on purpose. Extending the neighbour scan to ORDINARY shops as well
// was the literal brief, but measured it breaks the same brief's "byte-identical flags-off" clause: on
// the synthetic town (what ?classic=1 boots) 127 of 855 finds — 14.9% — would newly come from a
// neighbour chunk, i.e. the frozen v2 crowd would start shopping ~15% more. So:
// · ORDINARY shops: own chunk only — v2 semantics, frozen. A shopfront is noticed from its own block.
// · GIG venues: the full 34 m radius, honest across chunk edges — the gig is the "follow the sound"
// pull that is SUPPOSED to reach further; that is the whole point of GIG_RANGE > PATRON_RANGE.
// With no gig on, `_gigVenues` is empty and the neighbour loop is skipped entirely ⇒ ?classic and every
// flags-off boot take the identical code path AND identical cost to v2. Byte-identical by construction.
// Both ranges are < CHUNK (64), so a 3×3 sweep is exact, not an approximation. Fixed dz/dx order ⇒
// exact-distance ties resolve deterministically, as the old single-list scan did.
// Carried: PATRON_RANGE keeps v2's own chunk-blindness. Revisit only if the covenant is ever relaxed.
_nearestOpenShop(c) {
if (!this.shopsByChunk) return null;
const list = this.shopsByChunk.get(this.chunkKeyAt(c.x, c.z));
if (!list || !list.length) return null;
const cx = chunkCoord(c.x), cz = chunkCoord(c.z);
let best = null, bd = PATRON_RANGE;
let gigBest = null, gbd = GIG_RANGE; // R13: the nearest gig venue in range — it wins over ordinary
for (const s of list) { // shops (follow the sound), and across concurrent venues.
const own = this.shopsByChunk.get(chunkKey(cx, cz)); // shops (follow the sound), and across venues.
if (own) for (const s of own) {
if (!this._openAt(s.hours)) continue;
const d = Math.hypot(s.x - c.x, s.z - c.z);
if (this._gigVenues.has(s.shopId) && d < gbd) { gbd = d; gigBest = s; }
if (d < bd) { bd = d; best = s; }
}
if (this._gigVenues.size) { // gig on → the 34 m pull reaches across chunk edges (gigs only)
for (let dz = -1; dz <= 1; dz++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dz === 0) continue; // own chunk already scanned above
const list = this.shopsByChunk.get(chunkKey(cx + dx, cz + dz));
if (!list) continue;
for (const s of list) {
if (!this._gigVenues.has(s.shopId)) continue; // neighbours contribute gig venues ONLY
if (!this._openAt(s.hours)) continue;
const d = Math.hypot(s.x - c.x, s.z - c.z);
if (d < gbd) { gbd = d; gigBest = s; }
}
}
}
}
return gigBest || best;
}
_beginVisit(c, shop) {