diff --git a/docs/LANES/LANE_E_NOTES.md b/docs/LANES/LANE_E_NOTES.md index 7665d3b..4583a6d 100644 --- a/docs/LANES/LANE_E_NOTES.md +++ b/docs/LANES/LANE_E_NOTES.md @@ -25,16 +25,20 @@ a node hook) and dumps `birdGeometry()` vertex for vertex. three views, still and under two linear motion-blur levels. E is pied and unmistakable at 64 and 48 and still black-and-white at 32, and survives a 25 % smear. B is a grey dash at every size in every view except top-down. -3. **And the mechanism is geometric, which is why no re-tint could have saved it.** Three of B's - four white marks cannot be seen from where the player stands: - - the **wing bars are 3 mm ABOVE the wing planes** (`0.048` vs `0.045`, both horizontal after - `rotateX(-π/2)`, both DoubleSide) — so from **underneath**, which is where the player always - is, the black wing occludes its own white bar. `magpie.js`'s header says *"that black-and-white - flash is the entire read"*; the flash is on the roof of the wing. - - the **nape box** (`0.052×0.014×0.055` at y 0.06, z −0.10) sits **inside the head sphere** except - for a ~10 mm sliver — at z = −0.10 the head's surface is at y 0.0728 and the box tops out at 0.065. - - the **rump box** (y 0.045–0.065 at z 0.035–0.165) is **inside the body ellipsoid** over its - front half (body top at z 0.035 is y 0.0744). +3. **And the mechanism is geometric, which is why no re-tint could have saved it.** All four of B's + white marks are invisible from where the player stands, and the arithmetic is short: + - **THE WING BARS ARE 3 mm ABOVE THE WING PLANES** — bar at `y 0.048`, wing at `y 0.045` + (`:145-148`), **both horizontal** after `rotateX(-π/2)`, both `DoubleSide`, and the bar's + footprint (x 0.174–0.316, z 0.005–0.055) is **entirely inside the wing's** (x 0–0.34, + z −0.085–0.065). So from **underneath** — and the player is always underneath: perch 4.32 m + against EYE 1.62 m — **the black wing occludes its own white bar.** `magpie.js`'s header says + *"that black-and-white flash is the entire read"*. The flash is painted on the roof of the wing. + - **the nape box is inside the head.** `0.052×0.014×0.055` at y 0.06, z −0.10: at z = −0.10 the + head sphere's surface is at **y 0.0728** and the box tops out at **0.067**. Only its rear + ~12 mm (z > −0.085, where it clears both head and body) is ever proud. + - **and from the flank, which is 80 % of the time, there is nothing to see anyway** — the nape and + rump marks are horizontal slabs **14 mm and 20 mm thick** on a **166 mm** tall bird. At 64 px + for a 0.67 m bird, one pixel is ~10 mm: **1.3 px and 2 px of white.** **Cost of the swap, stated plainly:** **+712 triangles at one instance.** Draws are a **wash** — both are one town-wide `InstancedMesh` at count 0 or 1. The GLB adds +1 fetch, +1 material, +1 512² @@ -126,7 +130,17 @@ that; it can only look different. Generated anyway (see AUDIT §Round 39) so the (`use:'verge'`) and `reddust` (`use:'outback'`) as selected by nothing *by accident*. These two are selected by nothing *on purpose and with the price printed*: the arcade cannot ride `footpath`, because that is one town-wide merged mesh and re-using it would lay terrazzo over every footpath in -the town. Both must also be **classic-gated** — `?classic=1` does not move on dressing. +the town. Measured for #3: **arcade lots span exactly 2 chunks on all five seeds** (`0,0` and `0,1`; +`CHUNK` 64 m), so a per-chunk arcade class is +1 draw in 2 chunks and +0 in the other 23. + +> **⚠ AND THE CLASSIC ARGUMENT IS THE OPPOSITE ONE THIS TIME — do not reuse R38's.** R38's ground +> palette is safe under `?classic=1` **by absence**: `character.js:83-87` returns the frozen v1 +> literals for a null town key, and classic has no town key, so there is *no* `if (classic)` in that +> path. **The arcade is the reverse case: the arcade district exists ONLY on the synthetic, and the +> synthetic IS the classic boot.** Every piece of this kit therefore lands squarely on the covenanted +> town and needs an **explicit flag test** — `ground.js:41-47`'s `VERGE` pattern, where `?classic=1` +> forces it off — not the by-absence pattern. Getting this backwards would move the golden render on +> a dressing change. ### → EVERY LATER ASSET ROUND: the sizing law, now standing in AUDIT diff --git a/pipeline/AUDIT.md b/pipeline/AUDIT.md index d8d74f3..3b558a4 100644 --- a/pipeline/AUDIT.md +++ b/pipeline/AUDIT.md @@ -903,9 +903,9 @@ marks cannot be seen from where the player stands: | mark | `magpie.js` | why it does not read | |---|---|---| -| wing bars ×2 | `:147-148`, plane at **y 0.048** | the wing plane is at **y 0.045** and both are horizontal (`rotateX(-π/2)`) and DoubleSide ⇒ **from underneath the black wing occludes its own white bar**, and the player is always underneath (perch 4.32 m, EYE 1.62 m) | -| nape | `:140`, box `0.052×0.014×0.055` at y 0.06, z −0.10 | at z −0.10 the head sphere's surface is **y 0.0728** and the box tops out at **0.065** ⇒ inside it but for a ~10 mm sliver | -| rump | `:141`, box y 0.045–0.065, z 0.035–0.165 | at z 0.035 the body ellipsoid's top is **y 0.0744** ⇒ the front half is inside the body | +| wing bars ×2 | `:145-148`, bar plane at **y 0.048**, wing plane at **y 0.045** | both horizontal (`rotateX(-π/2)`), both DoubleSide, and the bar's footprint (x 0.174–0.316, z 0.005–0.055) lies **entirely inside** the wing's (x 0–0.34, z −0.085–0.065) ⇒ **from underneath the black wing occludes its own white bar**, and the player is always underneath (perch 4.32 m vs EYE 1.62 m) | +| nape | `:140`, box `0.052×0.014×0.055` at y 0.06, z −0.10 | at z −0.10 the head sphere's surface is **y 0.0728**; the box tops out at **0.067** ⇒ buried, and only its rear ~12 mm (z > −0.085) ever clears both head and body | +| nape + rump, from the flank | `:140-141` | horizontal slabs **14 mm and 20 mm** thick on a **166 mm** tall bird. At 64 px for a 0.67 m bird one pixel is ~10 mm ⇒ **1.3 px and 2 px of white** — and the flank is 80 % of the frames | **VERDICT — Lane E recommends TAKING THE GLB**, with the cost stated: **+712 tris at one instance**, draws a **wash** (both are one town-wide `InstancedMesh` at count 0 or 1), **+1 fetch / +1 material / @@ -954,7 +954,15 @@ undistorted (1024×768 → square → 1.29:1 quad ≈ 1.03 net). Recorded; not r **`use` values that name no slot — this time on purpose.** R38 recorded `gravel`/`reddust` as selected by nothing *by accident*. `arcade-floor` and `arcade-ceiling` are selected by nothing *with the price printed*: the arcade cannot ride `footpath` (one town-wide merged mesh — re-using it lays -terrazzo over every footpath in town), so each needs its own class and each is classic-gated. +terrazzo over every footpath in town), so each needs its own class. Measured for the ceiling: +**arcade lots span exactly 2 chunks on all five seeds** (`0,0` and `0,1`, `CHUNK` 64 m). + +**⚠ THE CLASSIC ARGUMENT IS INVERTED HERE relative to R38, and reusing R38's would move a golden.** +R38's ground palette is classic-safe **by absence** — `character.js:83-87` returns frozen v1 literals +for a null town key and classic has none, so that path contains no `if (classic)` at all. **The +arcade district exists ONLY on the synthetic, and the synthetic IS the classic boot**, so every piece +of this kit lands on the covenanted town and needs an **explicit** flag test: `ground.js:41-47`'s +`VERGE` shape, where `?classic=1` forces it off. ### 4. `pipeline/seamless_tile.py` — and the guard it needed diff --git a/pipeline/arcade_measure.mjs b/pipeline/arcade_measure.mjs index 1af68f2..1afbdd7 100644 --- a/pipeline/arcade_measure.mjs +++ b/pipeline/arcade_measure.mjs @@ -83,6 +83,19 @@ console.log(' 2.2 was sized for a 3.5 m FOOT band, not a 2.5 m half-lane. → L console.log(` ground.js pave: the arcade branch emits ONE footpath quad ${f(rows[0].len)} × ${f(rows[0].width + FOOT * 2)} m`); console.log(` = ${f(rows[0].len * (rows[0].width + FOOT * 2), 0)} m² — the single biggest surface in the district, and it wears footSkin today.`); +// ── how many CHUNKS hold arcade lots (a per-chunk class costs +1 draw in each) ─────────────────── +{ + const CHUNK = 64; // planutil.js CHUNK (CITY_SPEC law) + for (const s of seeds) { + const plan = generatePlan(s); + const arc = plan.districts.find((d) => d.kind === 'arcade'); + const bIds = new Set(plan.blocks.filter((b) => b.district === arc.id).map((b) => b.id)); + const keys = new Set(plan.lots.filter((l) => bIds.has(l.block)) + .map((l) => `${Math.floor(l.x / CHUNK)},${Math.floor(l.z / CHUNK)}`)); + console.log(` seed ${String(s).padEnd(9)} arcade lots span ${keys.size} chunk(s): ${[...keys].join(' ')}`); + } +} + // ── the budget line every arcade asset has to answer ──────────────────────────────────────────── console.log('\nINSTANCE COUNTS THE CONSUMING LANE IMPLIES (the number to generate against):'); const n = rows[0].shops;