GODSIGH/docs/procity-b.txt
type-two 2a51bcad2f fix: markers bled through the globe (far-side dots didn't track rotation)
Every marker used disableDepthTestDistance: Infinity, so points/billboards on
the FAR side of the Earth drew on top of the near hemisphere instead of being
occluded — reading as 'dots stuck, not moving with the globe' when you rotate.
Changed to 50000 (50km) across all layers: the globe now occludes far-side
markers, while you can still zoom right up to one without it clipping into the
surface. Also made the billboard layers (aircraft/military/adsb/radius) explicit
for consistent behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:19:21 +10:00

206 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

b cutoff
Thanks — that's a big help. 90sDJsim is the primary reference for Lane B (its `buildStrip` and `applyLighting` are exactly what I'm generalizing). Let me pull those key patterns before continuing:
I have the key `applyLighting`/`buildStrip` patterns. Now the facade kit — the biggest module. Task 3:
Now ground.js — I'll build the road/footpath/plaza network **once** as merged strips (persistent, not per-chunk streamed): with only a handful of edges this is far cheaper and seam-free versus per-chunk clipping. Task 4:
Now furniture.js — instanced street furniture with mandatory primitive fallbacks (GLB depot upgrade documented but off by default for v1 robustness). Task 5:
Now lighting.js — sky dome + 6-segment day/night cycle (global, with fog to mask chunk pop-in). Task 6:
Now player.js. Let me first check the vendored PointerLockControls API (r175 changed some method names):
`_euler` re-syncs from the camera each move, so I can set orientation directly. Writing player.js. Task 7:
I made a sign error on the strafe vector — camera-right = cross(forward, up) = (fz, 0, fx). Let me fix:
Now hud.js. First let me attach `doorRects` to the door mesh userData so the HUD can map raycast hits to shops:
Before writing the streamer, I need the fixture town to actually span enough chunks that streaming triggers with the default radius 3. Let me widen the main street to ~6 chunks (24 shops across 4 north + 2 south blocks):
Now widen the street nodes to match:
Now the streamer. First, one efficiency fix in buildings.js — make the 2048² sign atlas lazy so shopless chunks (mid-street) don't each allocate a 16MB canvas:
Now the streamer — chunks.js. Task 8:
Now the shell — index.html. Task 10. Let me also write a small minimap module for `map` mode. First index.html:
Now the shell — index.html:
Before booting, I caught a few bugs by re-reading. Let me fix them. First, the ground quad winding — my triangles wind so the geometric normal points **down**, so `FrontSide` ground would be invisible from above:
Now fix the spawn heading (should look east into town) and add click-to-relock:
All modules written. Now let me boot the server and verify in the browser. Task 11:
Server started from the wrong directory — it needs to serve from `web/`. Let me fix that:
All paths resolve. Let me open it in the browser and check the console for errors:
No errors. Let me take a screenshot to see the start screen, then enter the town:
Start screen renders cleanly. Let me verify the full script initialized (it exposes `window.PROCITY` only at the very end) and check the plan built correctly:
I need an expression, not a return statement. Let me wrap it:
The script errored before completion. Let me reload and capture the error:
Console capture is missing module-load errors. Let me probe each module directly with a dynamic import to find the failure:
All modules parse and import fine — so the error is a runtime throw during inline setup. Let me replay the setup to find the throwing call:
Setup replays fine (26 items incl. stalls, 8 chunks warmed). The failing piece must be the post-processing / HUD / minimap chain that my replay skipped. Let me test those:
Everything works in isolation — the earlier `initialized:false` was likely a timing read during the top-level `await` on the citygen 404. Let me re-check now that it's settled:
Still failing at runtime though it replays fine — let me confirm the served `index.html` is actually my latest version and hunt the real error. Let me capture it via a fresh load with an error listener:
There's a genuine runtime error the console tool isn't surfacing. Let me add temporary global error capture at the top of the module, reload, and read it:
`__PCERR` is null and no PROCITY — meaning the module is **suspended**, not thrown. The only top-level `await` is the citygen import. Let me check whether that import hangs (rather than 404-rejecting) in this preview environment:
The import rejects cleanly, so the `catch` runs — but a later **synchronous** throw in a top-level-await module surfaces as a "module evaluation error" that `window.onerror` doesn't catch (which is why `__PCERR` is null). Wrapping the body in an async IIFE with try/catch will both fix the structure and surface the real error. Let me refactor:
Let me verify the browser is actually getting the wrapped version (cache/service-worker suspicion) and check if the module even starts:
Served version is correct, so `main()` is pending. Let me reproduce the exact `await import` in try/catch pattern to see if it hangs versus my earlier `.then/.catch` test:
That exact pattern completes — so main() is pending on something else, and the console tool isn't surfacing this tab's errors. Let me instrument `main()` with stage markers to pinpoint where it stops:
`__S` is undefined — the module body isn't executing at all. Let me wait a beat and re-check (large three.module.js fetch), and also check network for any failed import:
Module script is in the DOM but never executed — that's a **parse error in the inline script itself**. Let me extract it and validate with node:
The regex found no `</script>` — I never wrote the closing tags! Let me check the file tail:
That's the bug — the inline `<script type="module">` was never closed, so the parser never finalized/executed it. Let me add the closing tags:
It fully initializes now — "Yarraville Junction", 26 shops, 8 chunks live. The frame loop renders behind the start panel. Let me hide the panel, read perf, and screenshot the actual 3D:
`draws:1` is misleading — EffectComposer's `autoReset` leaves `renderer.info` showing only the last (OutputPass) draw. Let me measure the true scene cost directly, then I'll fix the loop to report correctly:
True scene cost from the west end: **97 draw calls, 3,626 tris** — well within the 300/200k budget. Let me see it:
The town renders — road, footpaths, kerbs, shops with a sign, streetlights, bus stop, tree, fog, sky. But the HUD shows **draws 700** because my measurement left `info.autoReset=false` and the loop never resets per-frame (EffectComposer accumulates). The true scene cost is 97. Let me fix the loop's perf accounting and strip the debug markers:
I still need to add the per-frame `renderer.info.reset()` (I set autoReset=false but the loop must reset once per frame). Let me check the remaining `S('` and view the loop:
Two fixes: strip the last stray marker on line 207, and insert `renderer.info.reset()` between `hud.update` (which reads the count) and `composer.render()` (which accumulates it) — so the HUD reports the true per-frame total with a one-frame lag:
Now reload and verify the corrected HUD, then drive through the town to test streaming, collision, and day/night:
140 draws / 5.2k tris at spawn — comfortably within budget, 22 skin materials (≤25 ✓). Now let me simulate walking the full strip to verify streaming, collision, and perf under motion:
Streaming works (chunks 8↔9, pending stayed 0, ~156m traveled) and collision held (`insideBuilding: false`). Now let me test walking straight *into* a shopfront (collision) and flip to night to verify emissive windows/lamps + bloom:
Collision is pixel-accurate (stopped at z=10.62 = 11 0.38 radius). Now the night screenshot:
Night mode is cinematic — glowing shopfront windows both sides, streetlamp bloom, dark sky, 153 draws. Now let me verify the door raycast → shop resolution works:
The door raycast missed — let me diagnose with the ray direction, door-mesh bounds, and a wider `far`:
The door raycast resolves correctly (hit at 2.45m, z=10.95). Now let me verify the `enterShop` event+toast fires, then get a daytime close-up of the shopfronts:
Let me see the daytime shopfront close-up:
Excellent — the strip reads beautifully: striped posted verandah, legible color-coded signs ("PAWN" yellow, "...LLY EMPORIUM" toy-magenta, "CIVIC EZY H[IRE]" video-blue), facade art with shop detail, brick footpath, bench/bin. One refinement: my dark window planes are opaque and obscure the facade's own shopfront art. Per spec they exist for the night glow — let me make them subtle glass in day, glowing at night:
Now the critical all-fallback test — the page must run with `assets/` renamed away (no crashes). Let me rename it, reload, verify, then restore:
No crash in all-fallback mode. Let me screenshot the flat-colour fallback town:
All-fallback mode is fully coherent — canvas signs still render (they're not JPEGs), facades show muted fallback colours, windows are now subtle glass, town is readable. The "zero assets" house law holds. Let me restore assets:
Now let me capture the 3 reference shots and save them into `docs/shots/laneB/`. I'll reload, render the 3 poses at a fixed size, and stash their dataURLs:
`P.canvas` isn't exposed — it's `renderer.domElement`. Let me fix and also drive the composer (for bloom) at the capture size:
Valid PNGs captured (drawing buffer preserved). They're ~4MB base64 each — too large to pipe through the tool. Let me stand up a tiny local save-endpoint and have the page POST them straight to disk:
Saver is up. Now POST the 3 captured shots from the page:
Cross-origin fetch to `:8131` is blocked by the preview sandbox. Let me replace the main `:8130` server with one that serves `web/` **and** accepts same-origin POST `/save`:
Combined server works (static + same-origin POST). Let me reload, re-capture the 3 shots, and POST them same-origin:
All 3 saved to disk. Let me verify them and view one to confirm they're valid renders:
The plaza overview confirms the whole layout — herringbone brick plaza, main-street asphalt with lane markings, cross street, shops both sides, streetlights, trees, dept anchor, dramatic seeded sky, fog depth. All 3 reference shots are saved.
Now, per the ultracode directive, let me run a rigorous **multi-agent adversarial review** of the Lane B code — independent lenses genuinely add value here to catch correctness/spec/perf/robustness bugs I may have missed. Let me mark a chapter and launch it:
The review is running in the background. Meanwhile, let me confirm the window-glass fix looks right in the saved daytime shot:
The plaza shot is great, but `main-street-day` framed the camera right up against a shop (black awning underside dominates). Let me check the dusk shot, then re-capture the day one with cleaner framing:
The dusk shot shows building *backs* (plain boxes) and the day shot is too close to a shopfront. Let me re-capture all three with the framing that showed the strip well — the receding shopfronts and the glowing night windows. Let me reload and re-shoot:
That day framing sits in an empty gap with too much foreground. Let me check the night shot, then re-shoot the day one as the shopfront close-up (the hero angle):
The night strip clearly shows the glowing shopfront windows receding down the block — the emissive/night system working. Let me replace the weak day shot with the shopfront close-up (the most informative angle):
Still catching building sides. Let me shoot dead-on at a shopfront with a level gaze (so the facade art + sign fill the frame, not the roof):
The look-target keeps drifting east down the street. Let me look due north (+Z), dead-on at the shopfront:
I've been fighting camera framing — let me stop optimizing shots (the 3 saved references are functional) and do one quick live sanity check at the known-good facade angle to confirm facades still render with the window fix, then move on:
The HUD now shows a **different town — "Boolarra Heads", 36 chunks, draws 283**. That name isn't in my fixture pool — **Lane A's `generatePlan` has landed** and my guarded import picked it up. This is a live integration. Let me investigate:
This is a **live Lane A integration** — `generatePlan` produces a full 523-shop, 1km² city (vs my 26-shop fixture), and my guarded import auto-adopted it. Key: Lane A stores `facadeSkin` as full filenames (`"facade-stucco-pink.jpg"`) and `lot.ry` as arbitrary angles — both handled (the skins.js normalize fix + my general oriented-rect math). My camera was just parked in an arbitrary spot. Let me spawn at the city origin and see Lane A's city through Lane B:
Two findings: (1) facades **do** render in Lane A's city (visible far-right), and (2) at the dense center, **draws hit 313 — over the 300 gate** with radius 3 and 44 live chunks. The camera also spawned inside a shell (Lane A's lots are deep, d≈15-20). The hard perf gate must hold on the real city. Let me test radius 2:
Even at radius 2 the dense city hits 424 scene draws — because the **sun shadow pass re-renders every shadow-caster**, roughly doubling draws. The lane doc explicitly permits "none" for shadows ("or none — measure"). Let me measure with shadows off:
Even radius 2 + shadows-off leaves the densest blocks at ~380 draws — Lane A's 523-shop city is far denser than the fixture the gate was sized for, and the real fix is a facade atlas (collapsing the ~6 per-chunk facade-skin draws to 1). That's a larger optimization. For now let me make **radius and shadows adaptive to plan size** — keeping the fixture fully green (my verified deliverable) and substantially reducing the city's cost — then measure realistic gameplay framing and document honestly. Let me check the review results first:
The review is surfacing real findings (e.g., the 2048² sign atlas is allocated full-size per shop-chunk regardless of sign count — a memory issue that matters a lot at Lane A's density). Let me pull the confirmed findings:
The review confirmed several real bugs — most importantly a **collision rotation-sign error that only manifests for arbitrary `ry`** (my fixture uses axis-aligned angles so it passed, but Lane A's city has angles like 1.32 rad → collision broken there). Let me verify my derivation and apply the confirmed fixes. Starting with the critical collision fix:
The review completed (20 agents, 0 errors) and confirmed my collision-fix derivation exactly. Let me apply all confirmed fixes. First, the critical collision rotation-sign fix:
Now the awning signed-shift bug (`>>` gives negative indices for ~1/3 of shops → undefined skin):
Now the house-door raycast bug — house doors are merged into the interactive door mesh but have no `doorRects`, so clicking a house "enters" a random distant shop. Let me route house doors into a separate non-interactive mesh:
Now merge the decorative house doors into a plain non-interactive mesh. Let me update the doors finalize block:
Now the sign-atlas memory fix — it's allocated at a full 2048² per shop-chunk regardless of sign count (with mipmaps ≈22MB GPU each), which blows the texture budget at Lane A's density. Let me size it to the actual sign count and drop mipmaps:
Now disable mipmaps on the sign texture:
Now the furniture fixes — `hashEdge` assumes string ids (Lane A uses numeric ids → all furniture gets the same seed), and offset furniture can land in a chunk `chunkIndex` never registered (silently dropped). First the string coercion:
Now the furniture chunk-drop — `chunkIndex` only rasterizes the edge centreline, so offset furniture lands in chunks that never get built. Let me widen the rasterization to cover the full road+footpath+verge band:
Now the `takeShots` mode-restore bug and adaptive radius/shadows for dense cities. Let me read the shot harness and renderer setup:
Now the takeShots mode-restore fix:
Now update lighting.js to honor the `shadows` option:
All confirmed fixes applied. Let me reload (default → Lane A's city with adaptive radius 2 / shadows-off) and verify: boot, perf, and especially the collision fix on an angled lot (which was broken before):
Excellent — all fixes verified:
- **Collision on angled lot (ry=1.323)**: `keptOut: true` (final local z=7.72 > half-extent 7.34) — the previously-broken arbitrary-`ry` collision now holds. ✓
- **Awnings**: no negative indices. ✓
- **Perf**: dense center now **270 scene draws** (~281 with post) — under 300, vs 313-424 before. 25 skin materials. ✓
Let me sample a few worst-case spots to confirm the budget holds citywide, then screenshot Lane A's city:
Most of Lane A's city is now within budget (230310 total draws), though the densest market/arcade block still peaks ~445 — that needs the facade atlas (the identified next optimization). The adaptive fix moved the typical experience under the gate. Let me see Lane A's city rendered through Lane B:
Lane A's city renders and streams correctly through Lane B (draws 196 here, well under budget). Now let me regression-check the **fixture** (my primary deliverable) still works after the shared-code fixes — I'll temporarily force citygen absent:
Fixture regression is clean: "Yarraville Junction", 26 shops, **148 scene draws** (~159 total), 21 skin materials, shadows on, collision stops at 10.62. All green. Let me restore Lane A's citygen:
Fixture restored to the integrated default. Let me capture representative chunk-build times for the notes, then write the docs:
Chunk builds: median 8ms, p90 15ms, max 22ms in Lane A's dense districts (heavier than the 4ms/frame budget for a single chunk, so the streamer builds ~1 heavy chunk/frame — fog-masked). I have all the measured data. Let me write the two required docs. First `LANE_B_NOTES.md`:
Now the `B-progress.md` for Fable's review: