Lane B R39 (Fable's ruling): the magpie takes E's GLB use-if-ready, and the fallback's white moves
+1 draw / +894 tris with the bird on screen, +0 with it off — measured same page, same frame, by toggling the InstancedMesh count. ?noassets keeps the 182-tri procedural bird (+1 / +182), so this is furniture.js's use-if-ready SECOND path, not a substitution. state.asset reports which bird is bound. THREE THINGS MEASURED BEFORE THIS WAS SAFE, AND ALL THREE WOULD HAVE SHIPPED WRONG: · E's bird faces +Z and this file flies -Z (Matrix4.lookAt's -Z column is the travel direction). Sliced the GLB's 894 tris along Z in ten bins: +Z end mean y 0.31, half-width 0.009 (head + bill); -Z end mean y 0.028 (tail). Without rotateY(PI) the magpie swoops TAIL-FIRST. This is the FIFTH -Z/+Z instance and the first caught BEFORE it shipped — because R39's lineage table said to point a measurement at it. Verified in pictures: perched head-leading, and a swoop frame taken head-on. · Its origin is at the FEET (GLB law minY=0) while PERCH_Y is a body-centre height: dropped 0.20 m so it stands on the lamp arm (4.12) instead of hovering. `folded` is 1 for the GLB — it IS the perch. · THE FETCH IS ARMED BY createMagpie(), NOT BY THE IMPORT. index.html imports this module unconditionally and gates only the CONSTRUCTION, so a module-scope loadGLB (furniture.js's shape) would have put a brand-new GLB request on the ?classic=1 boot — a fetch delta on the one boot that is not allowed one. Asserted on the network log, not on the code: default 1 request (asset 'glb', 894 tris) · ?classic=1 0 · ?magpie=0 0 · ?noassets=1 0 (asset 'primitive', 182). AND ONE MY EDIT INTRODUCED INTO ANOTHER LANE'S TOOL: a module-scope loadGLB also fires GLTFLoader's fetch path under node, so Lane E's pipeline/dump_bird.mjs (which imports this module unmodified) died with "ProgressEvent is not defined". Gated on IN_BROWSER as well as ?noassets; E's tool then ran against my edit and reported 182 triangles / 216 verts — the placement fixes are exactly +0 tris. THE FALLBACK, FIXED AND THEN MEASURED — a half-win, stated as one. E is right that the white bars sat 3 mm ABOVE horizontal wing planes, so from below (the player is always below: perch 4.32 m, EYE 1.62) the black wing occluded its own flash: bar y 0.048 -> 0.042. Same class of fix, my own arithmetic, for the other two marks: the nape was BURIED IN THE HEAD (head-sphere surface at z=-0.10 is y 0.0728, the box topped out at 0.067) so it moved behind the head to z -0.07; nape and rump thickened 0.014->0.030 and 0.02->0.034, because from the flank a 14 mm slab on a 166 mm bird is 1.3 px at 64 px. All +0 tris. Measured in the same crop of the same pose: pale area 180 -> 399 px, dark 3,017 -> 2,755 — but TRUE white 33 -> 28, because a downward-facing surface gets no sun. E's GLB reads 755. The fallback cannot be made pied with primitives at zero cost; the problem is photometric as well as geometric. Stale comments corrected: magpie.js:9 and :123 said 154 triangles; it is 182 (E's ask 1). FILED, NOT FIXED: the arcade's awning posts land +/-0.50 m from the centreline — two rows 1.00 m apart, 34 posts down a 5.00 m lane (my own measurement from the plan, confirming E's). AWNING_DEPTH 2.2 is tuned for a 9 m footpath. One clamp fixes it, but it changes default-boot geometry for a district whose whole treatment is a scheduled item, and the post line should be designed with the roof. Lane E's transmission recall checked, not assumed: longbench, streetlight and magpie as staged in web/assets/models/ carry no KHR_materials_transmission. My lane wires neither of the first two today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a982f3ce67
commit
f0b9a1d30f
@ -34,11 +34,29 @@ first frame; it now draws what has been walked past and **frames itself to it**.
|
||||
test**. `?classic=1` / `?fog=0` / `?game=0` map canvases are **hash-identical to HEAD's**.
|
||||
- **Lane A's address layer consumed** — street names along walked streets, deduped by name; the
|
||||
synthetic gets district labels from the same line. No town-type branch anywhere.
|
||||
- **FILED, NOT FIXED: `index.html`'s front normal is inverted** at `:384` (Lane D patronage door points)
|
||||
and `:450` (the R32 spawn). `buildings.js`, `dbg.js` and `minimap.js` all use local +Z; measured, the
|
||||
shell's point sits **15.25 m behind the facade** and **0 of the shops within 25 m of the shipped spawn
|
||||
are in front of the player** on all four towns tested. Two characters, but it moves the default boot's
|
||||
opening pose mid-round — Fable's call.
|
||||
- **THE SIGN, landed on Fable's ruling (`a982f3c`)** — `index.html:428` (Lane D patronage door points)
|
||||
and `:500` (the R32 spawn) computed the shop front as `(−sin ry, −cos ry)`, the **back** of the
|
||||
building. Patronage door points **on the footpath: 0/493 → 427/493 synthetic, 0/72 → 67/72 katoomba,
|
||||
0/139 → 139/139 fitzroy, 0/30 → 28/30 bowral**. At spawn, shops within 25 m that are in front of you:
|
||||
**0 → 4 on both towns tested.** Katoomba's shipped boot opened *standing in a paddock facing a blank
|
||||
back wall* — the R31 playtest's own complaint, which R32 was written to fix and never did. **No
|
||||
golden moves** (runtime pose; selfcheck `0x5f76e76` either side). **F: the R32 spawn gate and the R35
|
||||
first-five-minutes smoke photograph this pose and need re-baselining.** Written up as the **FOURTH
|
||||
instance of the −Z/+Z confusion** (R13 `RY_FLIP` · R15 the poster on the back wall · R27 every
|
||||
real-town shopfront facing the paddock · R39 these two) with the lineage table in LANE_B_NOTES §39.
|
||||
- **THE MAGPIE, on Fable's second ruling** — E's 894-tri GLB wired as `furniture.js`'s **use-if-ready**
|
||||
path (`+1 draw / +894 tris` with the bird on screen, `+0` off; `?noassets` stays on the 182-tri
|
||||
procedural bird). **Two things measured before wiring, both would have shipped wrong:** E's bird
|
||||
faces **+Z** and this file flies **−Z** (proved by slicing the GLB along Z — head end mean y 0.31,
|
||||
tail end 0.028), so without `rotateY(π)` it swoops **tail-first** — *the fifth −Z/+Z instance, and
|
||||
the first caught before it shipped*; and its origin is at the **feet**, dropped 0.20 m onto the lamp
|
||||
arm. The fallback's white is re-placed (+0 tris, verified by E's own dump: still **182 tris**) and
|
||||
**measured**: pale area 180 → 399 px but true white 33 → 28, because a downward-facing surface gets
|
||||
no sun — the GLB reads 755. My edit also broke E's `dump_bird.mjs` (module-scope `loadGLB` under
|
||||
node); gated on `IN_BROWSER` and re-run green. Two stale `154 tris` comments corrected to **182**.
|
||||
- **FILED:** the arcade's awning posts land **±0.50 m from the centreline — two rows 1.00 m apart, 34
|
||||
posts down a 5.00 m lane** (my own measurement, confirming E's). One clamp fixes it, but it belongs
|
||||
with the scheduled arcade treatment, not ahead of it.
|
||||
|
||||
## Round 38 (v8 WAVE 1 — INHABITATION) — all four runtime items, **+1 walked draw for the lot**
|
||||
|
||||
|
||||
@ -125,7 +125,7 @@ synthetic, 40.3% katoomba, 43.4% bowral, 43.5% fitzroy** — and on a shopper's
|
||||
refuses **0–2**, which is exactly the shape you want: free where a shopper stands, decisive behind the
|
||||
buildings.
|
||||
|
||||
### THE SIGN WAS WRONG — the FOURTH instance of the −Z/+Z confusion *(Fable ruled: landed, `1b7fa5f`)*
|
||||
### THE SIGN WAS WRONG — the FOURTH instance of the −Z/+Z confusion *(Fable ruled: landed, `a982f3c`)*
|
||||
|
||||
**The lineage, because the next lane to touch a heading should find it.** Every time, the rule is the
|
||||
same: **the rig/pose convention is local −Z, the visible front is local +Z**, and every time it
|
||||
@ -249,6 +249,80 @@ argument: the map canvas FNV hash at a fixed pose is **identical to HEAD's on al
|
||||
because classic also turns the gig layer and the yards off, which is the control proving the hash is
|
||||
sensitive to the map's content at all).
|
||||
|
||||
### THE MAGPIE, SETTLED ON E'S PICTURE — the GLB is the path, the fallback is fixed anyway
|
||||
*(Fable's second R39 ruling. Lane E's sheet: `docs/shots/laneE/r39_magpie_ab.png`.)*
|
||||
|
||||
**Wired as `furniture.js`'s use-if-ready second path, not a substitution** — `?noassets` and `?classic`
|
||||
still get the procedural bird, so it had to be fixed too. `loadGLB('depot:procity_street_magpie_01.glb')`
|
||||
once at module init, a synchronous ready-check in `createMagpie`, and the geometry/material swapped
|
||||
into **the one InstancedMesh**. `state.asset` reports which bird is bound so a gate reads it instead of
|
||||
inferring it from a fetch.
|
||||
|
||||
| same page, same frame, bird on vs off | draws | tris |
|
||||
|---|---|---|
|
||||
| GLB (default boot) | **+1** | **+894** |
|
||||
| procedural (`?noassets=1`) | **+1** | **+182** |
|
||||
| procedural on HEAD (before the re-placement) | +1 | +182 — **the placement fix is +0 tris, in-engine** |
|
||||
|
||||
**TWO THINGS I HAD TO MEASURE BEFORE WIRING IT, AND BOTH WOULD HAVE SHIPPED WRONG.**
|
||||
1. **E's bird faces +Z; this file flies −Z** (`Matrix4.lookAt`'s −Z column is the travel direction, and
|
||||
the primitive's nose is modelled along −Z). Sliced the GLB's 894 tris along Z, ten bins: the **+Z**
|
||||
end is high and narrow (mean y **0.31**, half-width **0.009** — head and bill), the **−Z** end low
|
||||
and thin (mean y **0.028** — tail). Without `rotateY(π)` the magpie swoops **tail-first**. *This is
|
||||
the FIFTH −Z/+Z instance — the first one caught before it shipped, and only because the lineage
|
||||
table above told me to point a measurement at it.* Verified in a picture both ways: perched with
|
||||
the head leading, and a swoop frame taken **head-on**.
|
||||
2. **Its origin is at the feet** (GLB law `minY = 0`) while `PERCH_Y = 4.32` is a body-centre height —
|
||||
dropped 0.20 m so the feet land on the lamp arm at 4.12 (`furniture.js`: pole 4.2, arm 4.1) instead
|
||||
of hovering over it. And `folded` (the 0.42× X squash that makes the primitive's flight geometry
|
||||
sit) is **1 for the GLB**, which is already the perch pose.
|
||||
|
||||
**A defect my own edit introduced into ANOTHER LANE'S TOOL, caught by running it.** A module-scope
|
||||
`loadGLB` fires GLTFLoader's fetch path under node, and Lane E's `pipeline/dump_bird.mjs` imports this
|
||||
module unmodified — `ReferenceError: ProgressEvent is not defined`, E's A/B tool dead, my fault. The
|
||||
load is now gated on `IN_BROWSER` as well as `?noassets`. E's tool then ran against my edited file and
|
||||
reported **182 triangles, 216 verts** — the placement changes are exactly +0 tris, confirmed by the
|
||||
lane that filed them.
|
||||
|
||||
**THE FALLBACK FIX, AND ITS MEASURED CEILING — which is not the answer E hoped for.** E's finding is
|
||||
right: the white bars sat **3 mm above** horizontal wing planes, so from below (perch 4.32 m, EYE
|
||||
1.62 m — the player is *always* below) the black wing occluded its own flash. I moved the bar under
|
||||
the wing (y 0.048 → **0.042**), and did the same class of fix for the other two marks with my own
|
||||
arithmetic: the **nape was buried in the head** (head-sphere surface at z = −0.10 is y **0.0728**, the
|
||||
box topped out at **0.067**) so it moved back to z −0.07 where the body's back is at 0.0675, and both
|
||||
nape and rump were **thickened** (0.014 → 0.030, 0.02 → 0.034) because from the flank — 80% of viewing
|
||||
— a 14 mm slab on a 166 mm bird is **1.3 px at 64 px** and a slab you can see the *side* of is not.
|
||||
All +0 triangles.
|
||||
|
||||
**Then I measured the result instead of trusting it, and it is a half-win:** counting pixels in the
|
||||
same crop of the same pose, pale area (lum 110–150) went **180 → 399**, dark (<70) **3,017 → 2,755** —
|
||||
but *true* white (>150) went **33 → 28**. **A downward-facing surface gets no sun**, so the white I
|
||||
moved into view reads **grey**. E's GLB in the same crop: **755 px above 150.** The fallback is
|
||||
measurably better and it cannot be made pied with primitives at zero cost, because the problem is
|
||||
photometric as well as geometric. Worth recording as the reason the GLB is the path rather than a
|
||||
preference.
|
||||
|
||||
**Stale comments corrected:** `magpie.js:9` and `:123` both said **154 triangles**; it is **182**
|
||||
(sphere(8,6) 80 + sphere(6,5) 48 + cone(5) 10 + 3 boxes 36 + 4 planes 8), and E's dump of my own
|
||||
`birdGeometry()` agrees — 216 verts, index length 546.
|
||||
|
||||
### → LANE E / a later round: THE ARCADE'S AWNING POSTS — filed, measured, NOT fixed
|
||||
Confirmed E's number independently from the plan: the arcade lane is **5.00 m** (facades at ±2.50 m
|
||||
from the centreline) and `buildings.js:643`'s post line lands at **±0.50 m — two rows 1.00 m apart,
|
||||
34 posts down a 42 m lane.** `AWNING_DEPTH = 2.2` is tuned for a main street with a ~9 m footpath; in
|
||||
a 2.5 m half-lane it reaches nearly to the middle, and it reads as a colonnade standing in its own
|
||||
doorway (no collider, so it walks). **The fix is one clamp** — awning depth ≤ a fraction of the
|
||||
front clearance, which needs a `lot.block → block.kind` lookup `buildings.js` does not do today.
|
||||
**Not this round:** it changes shipped geometry on the default boot for a district whose whole
|
||||
treatment (roof lights, tiled floor, reverb, E's 5.86 m `longbench`, a mouth) is a scheduled item, and
|
||||
the post line should be designed with the roof rather than ahead of it.
|
||||
|
||||
**Lane E's transmission recall, checked rather than assumed:** the three republished GLBs staged in
|
||||
`web/assets/models/` carry **no `KHR_materials_transmission`** (only `KHR_materials_specular`/`ior`,
|
||||
benign) — `longbench`, `streetlight` and `magpie` all clean on disk. My lane wires none of the first
|
||||
two today (`furniture.js` GLB_FILES is bench / food_cart / bin / bus_shelter), so there was nothing of
|
||||
mine to re-fetch; the magpie I *am* wiring is clean.
|
||||
|
||||
### Held — for Fable / for John
|
||||
1. **The inverted front normal at `index.html:384` and `:450`** (above). Two characters, measured to
|
||||
death, not applied: it moves the default boot's opening pose mid-round.
|
||||
|
||||
@ -16,6 +16,14 @@ both sides. Every number behind these pictures is in `docs/LANES/LANE_B_NOTES.md
|
||||
| `sign-*-BEFORE.png` | the default boot's spawn **as shipped since R32**: synthetic — a footpath facing a plaza with not one shopfront in frame; katoomba — **standing in a paddock facing the blank back wall of a building**. 3 shops within 25 m, **0 of them in front of you**, 0 discovered |
|
||||
| `sign-*-AFTER.png` | the same code with the front normal corrected (`+sin`): **on the footpath under the awnings, signage down the strip**, 4 shops within 25 m, **4 in front**, 4 discovered |
|
||||
|
||||
| `magpie-AFTER-glb-zoom.png` | Lane E's 894-tri GLB **in the engine**, perched on the lamp arm, `asset:'glb'` — pied, legged, unmistakable. **755 px of true white** in the crop |
|
||||
| `magpie-BEFORE-primitive-zoom.png` / `magpie-AFTER-primitive-zoom.png` | the procedural fallback (`?noassets=1`) before and after the re-placed white: pale area **213 → 427 px**, but true white **33 → 28** — the underside gets no sun, so the fix reads grey. The measured ceiling on a primitives-only fallback, and the reason the GLB is the answer |
|
||||
| `magpie-AFTER-glb-swoop.png` | the swoop, **head-on** — proof the `rotateY(π)` is right in flight, not just on the perch |
|
||||
| `magpie-AFTER-glb-flank.png` | the honest in-situ frame (70° FOV, 14 m): the bird is a speck. E's A/B sheet owns the size question; the `-zoom` frames are 12° FOV **inspection** shots |
|
||||
|
||||
*(All three magpie arms measured `+1 draw` with the bird on screen and `+0` with it off, same page, same
|
||||
frame: GLB +894 tris, primitive +182, and the placement fix is +0 tris in-engine.)*
|
||||
|
||||
The `sign-*` pair is **booted code on both sides** — two port-isolated no-store servers, `:8702`
|
||||
serving the fog commit (old sign) and `:8701` the fix. Nothing is monkey-patched.
|
||||
|
||||
|
||||
BIN
docs/shots/laneB_r39/magpie-AFTER-glb-flank.png
Normal file
BIN
docs/shots/laneB_r39/magpie-AFTER-glb-flank.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 505 KiB |
BIN
docs/shots/laneB_r39/magpie-AFTER-glb-swoop.png
Normal file
BIN
docs/shots/laneB_r39/magpie-AFTER-glb-swoop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 185 KiB |
BIN
docs/shots/laneB_r39/magpie-AFTER-glb-zoom.png
Normal file
BIN
docs/shots/laneB_r39/magpie-AFTER-glb-zoom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/shots/laneB_r39/magpie-AFTER-primitive-zoom.png
Normal file
BIN
docs/shots/laneB_r39/magpie-AFTER-primitive-zoom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/shots/laneB_r39/magpie-BEFORE-primitive-zoom.png
Normal file
BIN
docs/shots/laneB_r39/magpie-BEFORE-primitive-zoom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@ -6,8 +6,11 @@
|
||||
// ONE town-wide `InstancedMesh`, `count` 0 or 1 ⇒ **+1 draw while a bird is on screen, exactly 0
|
||||
// otherwise** (three.module.js:2193-2199 returns before the GL call at count 0; `render.calls++` is
|
||||
// downstream at :4094 — the charter's own vendored-line verification). Geometry is a merged
|
||||
// primitive bird, **154 triangles** against the ≤900 allowance. There is no atlas, no GLB, no fetch:
|
||||
// procedural by construction, so `?noassets` is the same code path as everything else.
|
||||
// primitive bird, **182 triangles** against the ≤900 allowance — sphere(8,6) 80 + sphere(6,5) 48 +
|
||||
// cone(5) 10 + 3 boxes 36 + 4 planes 8. *(This comment and `:123` both said 154 for two rounds; Lane
|
||||
// E re-measured it three ways in R39, including a dump of this file's own `birdGeometry()` — 216
|
||||
// verts, index length 546. Corrected.)* The fallback bird needs no atlas, no GLB and no fetch, so
|
||||
// `?noassets` is the same code path as everything else.
|
||||
//
|
||||
// ── TERRITORY SCALES WITH THE ROAD GRAPH (Fable's R38 binding correction 4) ─────────────────────
|
||||
// A fixed handful of anchors is the trap: the synthetic default has ~6.8 km of street and
|
||||
@ -35,19 +38,37 @@
|
||||
// thing that makes a moving object gate-checkable at all. The only stateful part is *when* a launch
|
||||
// starts, which is recorded as three numbers.
|
||||
//
|
||||
// ── LANE E'S 894-TRI `magpie` GLB: NOT ADOPTED THIS ROUND, AND THE REASON IS THE WHITE ──────────
|
||||
// E shipped it mid-round with a caveat traced (not guessed) to the operator's PBR bake: **it reads
|
||||
// as a CROW** — the white nape, wing bar and tail are gone, and E confirms the loss is already in
|
||||
// the raw 500k mesh, so our decimation is exonerated. Measured against what is in this file:
|
||||
// procedural bird **182 tris**, white nape + rump + wing bars baked as vertex colours, 0 fetches
|
||||
// E's GLB **894 tris** (4.9×), no white, +1 fetch, +1 material
|
||||
// A magpie you see for 1.2 seconds in your peripheral vision at speed IS its white bar — that black
|
||||
// -and-white flash is the entire read. A uniformly dark bird is a currawong or a crow, and the joke
|
||||
// this item exists for does not land. So **§1.3 does not hold for the tint; the GLB does.** When E
|
||||
// hand-tints the 512² atlas, the swap is `birdGeometry()` / `birdMaterial()` below — two factory
|
||||
// singletons, one call site each, the same use-if-ready shape `furniture.js` uses for the depot
|
||||
// props. Deliberately NO speculative `loadGLB` here: the asset is not in the manifest, and a boot
|
||||
// that probes for a file nobody has shipped is the blind probe R26 spent a round killing.
|
||||
// ── LANE E'S 894-TRI `magpie` GLB: ADOPTED IN R39 AS THE USE-IF-READY PATH (Fable ruled on the
|
||||
// picture, `docs/shots/laneE/r39_magpie_ab.png`) ───────────────────────────────────────────────
|
||||
// R38 refused the GLB because it read as a crow. E re-tinted it, then shot both birds through the
|
||||
// same rig at the size the bird is actually seen and found the thing neither of us had: **the player
|
||||
// is looking at the PERCH, not the swoop** — simulated on this file's own clock, the bird is drawn in
|
||||
// 54.1% of frames and of those it is **perched 80.5%**, swooping 7.7%. E's asset IS a perch pose (a
|
||||
// standing bird with legs); this file's perch is the flight geometry squashed 0.42× in X.
|
||||
// And E found the mechanism behind "reads as a dark aeroplane", which is geometric, not a tint:
|
||||
// **the white wing bars sat 3 mm ABOVE horizontal wing planes**, so from underneath — and the player
|
||||
// is ALWAYS underneath, perch 4.32 m against EYE 1.62 m — the black wing occluded its own white bar.
|
||||
// The flash was painted on the roof of the wing. That is fixed below, and it is fixed *whatever* the
|
||||
// GLB does, because `?noassets` and `?classic` show the procedural bird forever.
|
||||
//
|
||||
// So: **two paths, one draw.** `birdGeometry()`/`birdMaterial()` stay the fallback; the GLB is loaded
|
||||
// once at module init exactly the way `furniture.js` upgrades the depot props (`loadGLB('depot:…')`
|
||||
// → a synchronous ready-check, never an async write into a live build) and swapped into the ONE
|
||||
// InstancedMesh when it lands. **+712 tris at one instance; draws are a wash** — both are the same
|
||||
// town-wide InstancedMesh at count 0 or 1. It is not a blind probe: `magpie` has been in
|
||||
// `manifest.json` and on the depot since R38 §7a, which is precisely the thing R26's ruling asked for.
|
||||
//
|
||||
// TWO THINGS I HAD TO MEASURE MYSELF BEFORE WIRING IT, and both would have shipped wrong:
|
||||
// • **E's bird faces +Z; this file flies −Z.** `place()` builds its basis with `Matrix4.lookAt`,
|
||||
// whose −Z column is the travel direction, and the primitive bird's nose is modelled along −Z.
|
||||
// Sliced the GLB's 894 tris along Z: the +Z end is high and narrow (mean y 0.31, half-width
|
||||
// 0.009 — the head and bill) and the −Z end is low and thin (mean y 0.028 — the tail). Without
|
||||
// `rotateY(π)` the magpie would swoop tail-first. *This is the fifth −Z/+Z instance and it was
|
||||
// caught by pointing the measurement at it — see §39's lineage table.*
|
||||
// • **its origin is at the feet** (GLB law: minY = 0), while `PERCH_Y = 4.32` is a body-centre
|
||||
// height for the primitive. Dropped by `GLB_FOOT_DROP` so the feet land on the lamp arm (4.12,
|
||||
// `furniture.js`: pole to 4.2, arm at 4.1) instead of hovering 0.22 m over it.
|
||||
// The GLB is rigid (no `applyWind`) and is NOT X-squashed when perched — it is already the perch.
|
||||
//
|
||||
// The other two Lane E R38 props are a decided NO, on arithmetic rather than taste: `paling_fence`
|
||||
// is 1,121 tris and this town wants **905 fence runs** (5 × 181 lots) ⇒ **1.01 M triangles** against
|
||||
@ -62,6 +83,7 @@ import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
|
||||
import { rng, frange } from '../core/prng.js';
|
||||
import { resolveEdges } from './planutil.js';
|
||||
import { applyWind } from './wind.js';
|
||||
import { loadGLB } from '../core/loaders.js'; // [R39] Lane E's magpie GLB, use-if-ready
|
||||
|
||||
const TERRITORY_SPACING = 1200; // metres of street per swooping bird (see header)
|
||||
const TERRITORY_MAX = 400; // sanity cap on the candidate list (bendigo lands ~157)
|
||||
@ -120,7 +142,22 @@ export function swoopPos(launch, t) {
|
||||
|
||||
// ── the bird ────────────────────────────────────────────────────────────────────────────────────
|
||||
// Merged primitives with baked vertex colours: black body/head/wings, white nape, white wing bar,
|
||||
// white rump. 154 triangles. One geometry, one material, one InstancedMesh.
|
||||
// white rump. **182 triangles** (was written as 154 here for two rounds — see the header).
|
||||
// One geometry, one material, one InstancedMesh.
|
||||
//
|
||||
// [R39] THE WHITE IS NOW WHERE THE PLAYER IS. Every mark was placed for a bird seen from the side at
|
||||
// eye level; the bird is seen from BELOW, on a 4.32 m lamp arm, and 80.5% of its drawn frames are the
|
||||
// perch. Three placements changed, all **+0 triangles** (a box is a box, a plane is a plane):
|
||||
// 1. the wing bar y 0.048 → **0.042**: under the wing plane (y 0.045) instead of 3 mm over it, so
|
||||
// the black wing stops occluding its own flash. Both are DoubleSide, so nothing is lost above.
|
||||
// 2. the nape moved off the head it was buried in. Measured: the head sphere's surface at z = −0.10
|
||||
// is y **0.0728** and the old box topped out at 0.067 — only its rear ~12 mm was ever proud. It
|
||||
// now sits at z −0.07, *behind* the head (head spans z −0.195…−0.085), where the body's back is
|
||||
// at y 0.0675, and it is **0.030 thick instead of 0.014** so it has a face you can see edge-on.
|
||||
// 3. the rump likewise: 0.034 thick at y 0.062, sitting on the body's back (y 0.0566 at z = 0.10)
|
||||
// rather than half-buried in it.
|
||||
// Thickness is the point, not height: from the flank — 80% of viewing — a horizontal slab 14 mm
|
||||
// thick on a 166 mm bird is **1.3 px of white at 64 px**. A slab you can see the side of is not.
|
||||
const BLACK = new THREE.Color('#191a1c'), WHITE = new THREE.Color('#e6e4dd'), BILL = new THREE.Color('#b9bcc0');
|
||||
function paint(geo, color) {
|
||||
const n = geo.attributes.position.count;
|
||||
@ -137,15 +174,15 @@ function birdGeometry() {
|
||||
parts.push(paint(new THREE.SphereGeometry(0.085, 8, 6).scale(1, 0.9, 1.75), BLACK));
|
||||
parts.push(paint(new THREE.SphereGeometry(0.055, 6, 5).translate(0, 0.035, -0.14), BLACK)); // head
|
||||
parts.push(paint(new THREE.ConeGeometry(0.021, 0.08, 5).rotateX(-Math.PI / 2).translate(0, 0.03, -0.22), BILL));
|
||||
parts.push(paint(new THREE.BoxGeometry(0.052, 0.014, 0.055).translate(0, 0.06, -0.10), WHITE)); // nape
|
||||
parts.push(paint(new THREE.BoxGeometry(0.11, 0.02, 0.13).translate(0, 0.055, 0.10), WHITE)); // rump/back
|
||||
parts.push(paint(new THREE.BoxGeometry(0.05, 0.022, 0.30).translate(0, 0.0, 0.26), BLACK)); // tail
|
||||
parts.push(paint(new THREE.BoxGeometry(0.052, 0.030, 0.055).translate(0, 0.0745, -0.07), WHITE)); // nape (R39: off the head, thick enough to read edge-on)
|
||||
parts.push(paint(new THREE.BoxGeometry(0.11, 0.034, 0.13).translate(0, 0.062, 0.10), WHITE)); // rump/back (R39: on the back, not in it)
|
||||
parts.push(paint(new THREE.BoxGeometry(0.05, 0.022, 0.30).translate(0, 0.0, 0.26), BLACK)); // tail
|
||||
// wings: flat, spanning ±WING_SPAN in x — the `wing` wind weight is |x| / WING_SPAN
|
||||
for (const s of [-1, 1]) {
|
||||
parts.push(paint(new THREE.PlaneGeometry(WING_SPAN, 0.15).rotateX(-Math.PI / 2)
|
||||
.translate(s * WING_SPAN / 2, 0.045, -0.01), BLACK));
|
||||
parts.push(paint(new THREE.PlaneGeometry(WING_SPAN * 0.42, 0.05).rotateX(-Math.PI / 2)
|
||||
.translate(s * WING_SPAN * 0.72, 0.048, 0.03), WHITE)); // the wing bar
|
||||
.translate(s * WING_SPAN * 0.72, 0.042, 0.03), WHITE)); // [R39] the wing bar — BELOW the wing
|
||||
}
|
||||
for (const p of parts) { p.deleteAttribute('uv'); if (!p.attributes.normal) p.computeVertexNormals(); }
|
||||
_geo = mergeGeometries(parts, false);
|
||||
@ -161,6 +198,66 @@ function birdMaterial() {
|
||||
return _mat;
|
||||
}
|
||||
|
||||
// ── [R39] Lane E's GLB, use-if-ready — `furniture.js`'s pattern, not a probe ─────────────────────
|
||||
// Loaded ONCE at module init (skipped entirely under ?noassets, like furniture.js's depot props);
|
||||
// `_glb` is checked SYNCHRONOUSLY by createMagpie and again when the promise lands, so a bird built
|
||||
// before the GLB arrives keeps the primitive and swaps to the GLB the moment it exists. One mesh in,
|
||||
// one mesh out: this stays ONE InstancedMesh and therefore one draw either way.
|
||||
const GLB_FILE = 'procity_street_magpie_01.glb'; // manifest.furniture.magpie (depot since R38 §7a)
|
||||
const GLB_FOOT_DROP = 0.20; // GLB origin is the FEET (minY=0); PERCH_Y is a body-centre height
|
||||
// NOT-IN-A-BROWSER is its own arm, and it is not pedantry: Lane E's `pipeline/dump_bird.mjs` imports
|
||||
// THIS MODULE unmodified to dump `birdGeometry()` for the A/B render, and a module-scope `loadGLB`
|
||||
// fires GLTFLoader's fetch path there — `ProgressEvent is not defined`, E's tool dead, my file's
|
||||
// fault. Caught by running E's tool against my own edit before committing it.
|
||||
const IN_BROWSER = typeof window !== 'undefined' && typeof document !== 'undefined';
|
||||
const NOASSETS = !IN_BROWSER || (() => { try { const p = new URLSearchParams(location.search); return p.has('noassets') && p.get('noassets') !== '0'; } catch { return false; } })();
|
||||
let _glb = null; // { geo, mat, tris } once (and if) it loads
|
||||
const _glbSubs = []; // createMagpie instances waiting for it
|
||||
|
||||
function extractBird(gltf) {
|
||||
const geos = []; let material = null;
|
||||
gltf.scene.updateWorldMatrix(true, true);
|
||||
gltf.scene.traverse((o) => {
|
||||
if (!o.isMesh || !o.geometry) return;
|
||||
const g = o.geometry.clone(); g.applyMatrix4(o.matrixWorld);
|
||||
for (const name of Object.keys(g.attributes)) if (name !== 'position' && name !== 'normal' && name !== 'uv') g.deleteAttribute(name);
|
||||
g.morphAttributes = {};
|
||||
if (!g.attributes.normal) g.computeVertexNormals();
|
||||
if (!g.attributes.uv) g.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(g.attributes.position.count * 2), 2));
|
||||
geos.push(g);
|
||||
if (!material) material = Array.isArray(o.material) ? o.material[0] : o.material;
|
||||
});
|
||||
if (!geos.length) return null;
|
||||
let merged = null;
|
||||
try { merged = mergeGeometries(geos, false); } catch (e) { merged = null; }
|
||||
geos.forEach((g) => g.dispose());
|
||||
if (!merged || !material) return null; // heterogeneous → the primitive stays
|
||||
// E's bird is modelled head-to-+Z and standing on its feet; this file flies −Z from a body centre.
|
||||
merged.rotateY(Math.PI).translate(0, -GLB_FOOT_DROP, 0);
|
||||
merged.computeBoundingSphere();
|
||||
const idx = merged.getIndex();
|
||||
return { geo: merged, mat: material, tris: (idx ? idx.count : merged.attributes.position.count) / 3 };
|
||||
}
|
||||
|
||||
// THE FETCH IS ARMED BY createMagpie(), NOT BY THE IMPORT — and that is the classic covenant, not
|
||||
// tidiness. `index.html` imports this module unconditionally; only the CONSTRUCTION is gated
|
||||
// (`?classic=1` / `?magpie=0` never call createMagpie). A module-scope load would therefore have put
|
||||
// a brand-new GLB request on the classic boot — a fetch delta on the one boot that is not allowed one.
|
||||
// furniture.js can load at module scope because furniture is built on every boot; this cannot.
|
||||
let _glbAsked = false;
|
||||
function ensureGLB() {
|
||||
if (_glbAsked || NOASSETS) return;
|
||||
_glbAsked = true;
|
||||
loadGLB('depot:' + GLB_FILE)
|
||||
.then((g) => {
|
||||
const ex = g && extractBird(g);
|
||||
if (!ex) return; // fail-soft: no file, no merge → primitive forever
|
||||
_glb = ex;
|
||||
for (const fn of _glbSubs.splice(0)) { try { fn(ex); } catch (e) { /* one bird's problem */ } }
|
||||
})
|
||||
.catch(() => { /* the primitive is the answer, silently */ });
|
||||
}
|
||||
|
||||
/**
|
||||
* createMagpie({ scene, plan, citySeed, camera, chunks, force }) → { group, update, dispose, state }
|
||||
* `force` (?magpie=1) forces the season open. The shell never constructs this under ?classic=1 or
|
||||
@ -171,6 +268,9 @@ export function createMagpie({ scene, plan, citySeed, townKey = null, camera, ch
|
||||
const state = {
|
||||
territories: 0, roadKm: 0, active: null, mode: 'absent', swoopsToday: 0,
|
||||
gotYouToday: false, day: 1, dayRolls: 0, inSeason: false, forced: !!force, launches: 0,
|
||||
// [R39] which bird is actually bound — 'primitive' (182 tris) or 'glb' (Lane E's 894). F's gate
|
||||
// reads this rather than guessing from a fetch: ?noassets must stay 'primitive' forever.
|
||||
asset: 'primitive', tris: 182,
|
||||
// Diagnostics so "is there a magpie in this town?" is answerable without a 200-day soak.
|
||||
townKey, seasonOffset: seasonOffset(citySeed >>> 0, townKey),
|
||||
get daysToSeason() { return daysToSeason(this.day, citySeed >>> 0, townKey); },
|
||||
@ -226,6 +326,19 @@ export function createMagpie({ scene, plan, citySeed, townKey = null, camera, ch
|
||||
group.add(mesh);
|
||||
scene.add(group);
|
||||
|
||||
// [R39] the use-if-ready swap. `foldScale` is 0.42 for the primitive (whose perch is the flight
|
||||
// geometry squashed in X) and 1 for the GLB, which IS the perch pose. Geometry/material are
|
||||
// swapped on the ONE InstancedMesh — no second mesh, no second draw — and the primitive singletons
|
||||
// are NOT disposed: they are module-level and a second createMagpie would want them.
|
||||
let foldScale = 0.42;
|
||||
function adoptGLB(ex) {
|
||||
mesh.geometry = ex.geo; mesh.material = ex.mat;
|
||||
foldScale = 1;
|
||||
state.asset = 'glb'; state.tris = ex.tris;
|
||||
}
|
||||
if (_glb) adoptGLB(_glb);
|
||||
else if (!NOASSETS) { _glbSubs.push(adoptGLB); ensureGLB(); }
|
||||
|
||||
// ── proximity-first territory selection, refreshed on the R37 §0.4 chunk seam ──
|
||||
let dirty = true, home = null;
|
||||
const markDirty = () => { dirty = true; };
|
||||
@ -266,7 +379,7 @@ export function createMagpie({ scene, plan, citySeed, townKey = null, camera, ch
|
||||
// ±0.34 m wing planes to ±0.14 m reads as a bird sitting with its wings in, which is what a
|
||||
// perched magpie looks like and what the first screenshot showed it emphatically was not.
|
||||
function place(x, y, z, fx, fz, folded = false) {
|
||||
_s.set(folded ? 0.42 : 1, 1, 1);
|
||||
_s.set(folded ? foldScale : 1, 1, 1);
|
||||
_p.set(x, y, z);
|
||||
_fwd.set(fx, 0, fz);
|
||||
if (_fwd.lengthSq() < 1e-6) _fwd.set(0, 0, 1);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user