PLAN3D §2's inventory was verified against the M1 Ultra, but we build on the M3 Ultra, where several of those libraries are absent or moved. Records the real paths and flags the gaps that block Lanes A and D, plus the node/anchor contracts other lanes consume. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
151 lines
12 KiB
Markdown
151 lines
12 KiB
Markdown
# 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 — ❓ 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.
|