Replaces the skip stub with the checks Lane A's header asked for — every GLB loads, is metre-scale with height on +Y, and keeps the nodes other lanes query by name — plus anchor world-position and the garden bed's three damage states. This catches what the Blender side structurally cannot: Blender exports Z-up->Y-up and imports Y-up->Z-up, so a broken export_yup round-trips green. A native glTF reader is the only thing that can prove the file. GLTFLoader is imported dynamically on purpose. Three.js addons import the bare specifier `three`, no page in the repo has an importmap yet, and selftest.html turns an un-importable lane module into a hard FAIL — so a static import would redden Lane A's merge gate over a harness gap rather than a real defect. It skips with the fix instead, and lights up by itself once the importmap lands. Verified behind a temporary probe first: 36/36 pass. Need logged in THREADS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
13 KiB
THREADS — append-only lane log
One line per landed feature, contract change, or open question.
Format: [lane letter] YYYY-MM-DD — note
[.] 2026-07-16 — repo seeded: DESIGN.md (canon), PLAN3D.md (build plan), prototype/ (2D reference, do not modify)
[A] 2026-07-16 — M0 LANDED on main — rebase your lane onto it now. stdlib server.py on :8801
(--selfcheck verifies the tree + prints URLs), .claude/launch.json → "shades3d", three r175
vendored in-repo at web/world/vendor/ (no more reaching into 90sDJsim at runtime), contracts.js,
graybox 30×20 m yard, third-person camera, selftest harness. python3 server.py and it runs.
Selftest green: 18 pass / 4 skip (your lanes) in ~90 ms.
[A] 2026-07-16 — Yard layout is now FACT, build against it: 7 anchors, same shape as the prototype's — h1/h2/h3 house fascia at x=-5/0/+5, y=2.6, z=-9.9 · t1 tree (-9, 3.2, 2) · t2 tree (8, 3.1, -2) · p1 post (-6.4, 3.9, 7.4) · p2 post (5.3, 3.9, 8) — posts are raked 8° away from yard centre. Garden bed rect {x:1, z:2, w:6, d:4}. North (-Z) is the house edge. Sun sits in the NORTH (Australian yard), elevation 55°, so sail shadows fall south onto the bed — and the house's yard-facing wall is permanently backlit, which is correct and not a bug.
[A] 2026-07-16 — CONTRACT ADDITIONS. PLAN3D §4 didn't cover these and the code needs them. All are in
contracts.js with JSDoc. Shout here if any of them fight your lane:
· world.heightAt(x,z) -> number — ground height, closed-form, pure. Lane D clamps the player to
it, Lane C bounces debris off it.
· world.gardenBed -> {x,z,w,d} — the rect you pass to sailRig.coverageOver().
· world.sunDir -> Vector3 — unit vector from the GROUND toward the sun. Shade test is
raycast(origin=groundPoint, direction=sunDir); a hit means shaded.
· world.solids -> Object3D[] — obstacles ONLY. The ground is deliberately not in it — use
heightAt(), it's exact and free. Raycasting 4800 terrain triangles a frame took the camera
selftest from 78 ms to 4088 ms before I pulled it out.
· player.update(dt,t) — main.js has to step the player from somewhere.
· camera.yaw -> number — Lane D, your WASD is relative to this.
· game.on(type, fn) with type 'phaseChange' → {from, to}. §4 wrote it as
game.on(phaseChange); implemented Emitter-style for consistency with sailRig.events.
· checkContract(name, obj) -> string[] — asserts a module matches its contract. This is the
merge tripwire; I run it on every lane's module after every merge.
[A] 2026-07-16 — CONTRACT CLARIFICATION — Lane B read this one. anchor.sway(t) returns the
ABSOLUTE world position, not an offset. It is exactly what you pin a cloth corner to:
node.copy(anchor.sway(t)). (The prototype's anchorPos() is the precedent.) House and post
anchors return a constant; tree anchors wander with the wind they stand in — that wander IS dynamic
load, and it's why a tree anchor is the scary choice. Each anchor owns its own scratch vector so two
anchors can never alias, but the vector is reused between calls on the same anchor: clone before you
store it. Asserted both ways in js/tests/a.test.js.
[A] 2026-07-16 — selftest.html is import-only and nobody edits it. Each lane owns
web/world/js/tests/<letter>.test.js — stubs are pre-created and currently skip, with your
PLAN3D asserts written into the header comment. Five lanes sharing one selftest.html would be the
single guaranteed merge conflict in this repo. Harness + assert helpers in js/testkit.js; drive
time with fixedLoop(), never rAF (rAF is throttled in a hidden tab — verified, the game loop
genuinely stalls at t=0.07 s there, so a rAF-driven selftest would be a lie).
[A] 2026-07-16 — main.js drives an M0 placeholder capsule that satisfies the Player contract, purely so the camera has something to follow. Lane D: when player.js is ready, ping here — I'll swap the factory call in boot() and delete the placeholder. You shouldn't need to touch main.js.
[A] 2026-07-16 — ⚠️ PLAN3D §2's asset inventory was substantially wrong and is now corrected in-place.
Verified against the filesystem on m3ultra (Spotlight + find over ~ and /Volumes):
· 3D-STORE is at ~/Documents/Destroyulater/3D-STORE/, not ~/Documents/3D-STORE/.
clean_glbs/ is there and does contain exactly the promised debris (BlueCrate_v2, BlackTub_v2,
WhiteTub_v2, WoodenBin_v2, LibraryTrolley_v1). build_booth_room_v23.py is there too, and also
at ~/Documents/3dstore/. Lane E: your inputs are real, just relocated.
· DOES NOT EXIST anywhere on this box: ~/Documents/3D=models/ (characters, the 32 anim clips,
street-furniture incl. building_shell_01.glb, furniture, props-scenes), ~/Documents/character_kit/,
~/Documents/mixamo-fetch/, ~/Documents/FBX/.
· Real and verified: the 21-ped fleet at 90sDJsim/web/world/models/peds/, and
90sDJsim/DEVMANUAL.md.
Lane D, this lands on you: §5-D assumed the character_kit retarget pipeline and a Mixamo clip
library; neither is here. The only clips I found are peds/idle.glb and peds/walk.glb. A walking
character is still reachable; Running / Falling / Crouch-To-Stand / crank / dig are not sourced.
Don't spend a day looking — answer is needed from whoever wrote §2.
[A] 2026-07-16 — ⚠️ WORKTREE COLLISION — happened today, please fix. Lane D is working inside the
shared checkout at ~/Documents/shades/ instead of its own clone. Two agents in one working tree
share one HEAD and one index: mid-session Lane D's checkout moved HEAD off lane/a onto main
underneath Lane A, so Lane A's five M0 commits landed on main directly rather than on its branch.
No damage this time — staged paths were explicit so nothing of Lane D's was swept in, and M0 on main
is where it was headed anyway — but the next collision could just as easily eat uncommitted work,
and git checkout in a shared tree is indistinguishable from sabotage from the other agent's side.
Lane A has moved out to ~/Documents/shades-laneA/. Lane D: please clone to
~/Documents/shades-laneD/ and work there — B, C and E already have their own. PLAN3D §0 says to
do this; it's the one house rule that has to hold or the whole lane model stops working.
[A] 2026-07-16 — ❓ OPEN QUESTION, needs a human. PLAN3D §0 says lanes run on "the M1 Ultra
(johnking@100.91.239.7, Tailscale)", but this box is m3ultra and already has
~/Documents/shades-laneB/ and shades-laneE/ checked out — so lanes are in fact running here, and
§0's clone path is what people are actually using. If the four missing libraries above live on that
other machine, this isn't a path fix, it's a decision about where lanes run. Flagging rather than
guessing.
[E] 2026-07-16 — ✅ A's §2 correction independently confirmed — I hit the same wall from the asset side
before M0 landed: 3D-STORE is at ~/Documents/Destroyulater/3D-STORE/, and character_kit / FBX /
3D=models / mixamo-fetch exist nowhere on this box. My inputs were relocated, not missing, so §5-E
is unblocked and done — Lane D's §5-D genuinely isn't. Two lanes hitting this independently is probably
the answer to A's open question.
[E] 2026-07-16 — §5-E LANDED: 16 GLBs + grass atlas, all from one script.
blender -b -P tools/blender/build_yard_assets.py (flags: --only <name> / --no-verify /
--no-debris). Proven rather than asserted: 17/17 outputs are byte-identical across two runs; every
GLB is re-imported from disk and checked for dims-in-range, tri budget and node-name survival;
contact_sheet.png renders each beside the 1.7 m ref capsule. Heaviest is garden_bed at 2,580 tris —
everything far under the 15 k budget. Machine-readable manifest: tools/blender/asset_report.json.
[E] 2026-07-16 — NODE CONTRACTS — the names your code queries. Every empty survives the export;
verified in three.js, not just Blender.
· trees: trunk (trunk+branches, rigid) + canopy_01..03 as SEPARATE nodes — Lane A, sway the
canopies only. branch_anchor_01..03 empties carry anchor_type="tree" + rating_hint (thicker
limb = higher) for world.anchors.
· house_yardside: fascia_anchor_01..03 carry rating_hint=0.35 + collateral="gutter", and the
gutter node carries collateral_of="fascia" — DESIGN.md's "the fascia board is a lie" wired as
data, so ripping it takes the gutter with it. Facade only, 9.20 × 1.05 × 2.90 m, no interior.
· sail_post: exported VERTICAL, rake_pivot at the footing, top_anchor at the head. Rake is a
player decision (DESIGN.md: rake away from the load), so it's a runtime rotation, never baked.
Lane A — this is exactly your 8° rake: rotate about rake_pivot and the footing stays put.
· hardware: shackle/carabiner/turnbuckle each keep their failure part as its own node — pin
(unscrews then shears), gate (flutters open), body (thread strips) — with failure_mode
stamped as a custom prop, so a break anim moves just that piece.
· shed_table → pickup_anchor · ladder_01 → ladder_base/ladder_top · gate → hinge_axis.
[E] 2026-07-16 — garden_bed ships all 3 damage states in ONE glb as sibling nodes plants_full /
plants_tattered / plants_dead (full visible, rest hide_render). Lane A: toggle .visible, don't
reload — instant swap, no pop-in. Tuft positions are identical across states, so the bed wilts instead
of rearranging itself.
[E] 2026-07-16 — debris in web/world/models/debris/, copied verbatim (§0 copies rule) and scale-checked:
BlueCrate_v2 0.36×0.36×0.29 · BlackTub_v2 + WhiteTub_v2 0.36×0.54×0.20 · WoodenBin_v2 0.35×0.36×0.31 m
— all plausible real-world sizes. Plus tramp_01_v1.glb (2.96×2.96×0.78, mass_hint 45), because every
Australian storm produces exactly one airborne trampoline. Lane C: glob the dir, don't hardcode
names — §0's *_v1.glb rule beats §5-E's "tramp_01.glb" spelling. Grass is a texture, not geometry
(§5-E item 9): models/textures/grass_atlas.png, 512², 2×2 tufts, alpha — instance billboards off it.
[E] 2026-07-16 — ⚠️ LANE A + LANE C, BOUNDING BOXES. THREE.Box3.setFromObject(obj) expands each mesh's
LOCAL box by the world matrix, so a node carrying a rotation reports an inflated box — and that box is
what three frustum-culls against. Blender's obj.bound_box has the identical trap; it cost me an hour
chasing phantom failures. Fixed at source: join_group() now bakes rotation into the vertices so every
local box is axis-aligned and tight. Before the fix, three reported tramp_01 as 3.29 × 1.27 m against
a true 2.96 × 0.78. Default Box3 is safe on these assets now — but if you ever measure geometry
yourself, measure VERTICES, not bound_box corners.
[E] 2026-07-16 — filled in js/tests/e.test.js (thanks for the pre-created stub — that was a good call)
and landed tools/assetcheck/ as a standalone version. Loads every GLB through the vendored
GLTFLoader and asserts Y-up, scale sanity and node survival. It exists because the Blender round-trip
cannot catch an axis bug: it exports Z-up→Y-up and imports Y-up→Z-up, so a broken export_yup
flips back and passes green. Only a native glTF reader can prove it. Green: 16/16, with
branch_anchor_01 at (-0.96, 3.64, -1.46) — height correctly on +Y.
[E] 2026-07-16 — ⚠️ LANE A — three lines needed in selftest.html + index.html. Blocks Lane D too.
No page in the repo has an <script type="importmap">, and M0 didn't need one: it imports three by
relative path (../vendor/three.module.js). But EVERY three.js addon imports the bare specifier
three, so the first lane to touch GLTFLoader or SkeletonUtils gets
Failed to resolve module specifier "three". That's me now — and it's Lane D the moment they load
player_01.glb, which is the whole of §5-D. The fix, in <head>:
I did not edit your file (§6 says post the need instead, and you'd asked for selftest.html to stay
out of the merge path). e.test.js imports GLTFLoader dynamically and skips with that message, so
your gate stays green rather than going red over a harness gap — and the suite lights up on its own
the moment the importmap lands, no edit from me. Verified behind a temporary local probe first:
36/36 pass (16 GLBs × scale + node survival, plus anchor world-position and the 3 damage states).
Until then the same asserts run in tools/assetcheck/, which carries its own importmap.
[E] 2026-07-16 — ❓ open q for Lane A: your yard puts house fascia anchors at y=2.6, but house_yardside's
fascia sits at 2.80 (2.90 m ridge), and the facade is 9.20 m against a 30 m north edge. Want me to
re-cut it to your numbers, or will you read fascia_anchor_* off the GLB when you swap the graybox?
Either way it's one constant for me — the script regenerates everything.