Lane F R4: wire citizens.setExposure (F1) + permittedMax storeys-checker fix (F2)

F1: pass renderer.toneMappingExposure into CitizenSim each street frame so mid-tier
impostors track the animated day/night exposure (per LANE_D_NOTES). Verified: boots
clean, 0 console errors, exposure animates night 1.18 <-> midday 0.98.

F2: scope the consistency storeys check to permittedMax = max>=2 ? min(max+1,3) : max
(Lane A's corner-anchor rule, LANE_A_NOTES R3). Consistency now 0 warnings across 6
seeds; a single-storey type at >1 still trips. qa.sh --strict GREEN 4/4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-14 18:15:42 +10:00
parent 5899924da1
commit 9416d0ae08
2 changed files with 11 additions and 4 deletions

View File

@ -83,15 +83,21 @@ for (const seed of seeds) {
if (h[0] < 0 || h[0] > 24 || h[1] < 0 || h[1] > 24) fail(`shop ${s.id}: hours ${JSON.stringify(h)} out of range`);
if (h[1] >= 22) lateCount++; // Lane A encodes hours as simple 24h ints (open<close, no wrap)
}
// storeys must sit inside the type's registry [min,max] — unless it's a taller corner anchor.
// storeys must sit inside the type's registry [min,max], EXCEPT tall-capable types may reach a
// 3-storey corner anchor (CITY_SPEC). Lane A's rule (LANE_A_NOTES R3, verified across 8 seeds):
// permittedMax = registryMax>=2 ? min(registryMax+1, 3) : registryMax. Anything outside
// [registryMin, permittedMax] is REAL drift — a single-storey type at >1 still trips.
const sr = def.storeys;
if (Array.isArray(sr) && sr.length === 2 && (s.storeys < sr[0] || s.storeys > sr[1])) storeysOff++;
if (Array.isArray(sr) && sr.length === 2) {
const permittedMax = sr[1] >= 2 ? Math.min(sr[1] + 1, 3) : sr[1];
if (s.storeys < sr[0] || s.storeys > permittedMax) storeysOff++;
}
if (/[']s[']s\b/.test(s.name || '')) dblApostrophe++;
}
if (skinOffPool) warn(`${skinOffPool} shop(s) use a facadeSkin outside their registry type pool `
+ `(ok if Lane A widened pools intentionally; else a plan↔registry drift)`);
if (storeysOff) warn(`${storeysOff} shop(s) have storeys outside their registry type range `
+ `(ok if these are the CITY_SPEC "occasional 3-storey corner anchor"; else a plan↔registry drift — ask Lane A)`);
if (storeysOff) warn(`${storeysOff} shop(s) have storeys outside [registryMin, permittedMax] `
+ `(corner-anchor 3-storey is allowed for tall types; this is REAL plan↔registry drift — Lane A)`);
if (dblApostrophe) warn(`${dblApostrophe} shop name(s) have a doubled possessive ("X's's") — Lane A names.js`);
if (lateCount === 0) warn(`no open-late shop (close ≥22:00) — LANE_F §3.5 wants at least one`);
else ok(`${lateCount} open-late shop(s) (close ≥22:00) — §3.5 satisfied`);

View File

@ -242,6 +242,7 @@ function frame() {
lighting.update(dt, player.position);
const clk = lighting.getClock();
citizens.setTimeOfDay((clk.seg + clk.frac) / 6); // [Lane F] 0..1 day fraction → density (busy midday, empty at night)
citizens.setExposure(renderer.toneMappingExposure); // [Lane F R4] keep mid impostors matched to the animated day/night exposure (LANE_D_NOTES)
citizens.update(dt); // [Lane F] reads camera pos internally for LOD tiers
hud.tickToast(dt);
hud.update(dt, { clock: clk, chunks: chunks.count }); // reads prev frame's total