diff --git a/docs/LANES/LANE_E_NOTES.md b/docs/LANES/LANE_E_NOTES.md index 6b6e223..ccb1a66 100644 --- a/docs/LANES/LANE_E_NOTES.md +++ b/docs/LANES/LANE_E_NOTES.md @@ -1,5 +1,43 @@ # LANE E — cross-lane notes +## Round 16 — v3.1 THE FLIP: the drummer's sit clip (E asset half) ⟨v3.1 amendment⟩ + +> Ledger #2 asset half — the drummer finally sits. **E produces the clip; D wires it** (`_rotWithHips` +> + `spawnRig` foot re-plant + F's seated-stature gate). **`sit.glb` delivered, verified, committed + +> on depot.** → **Lane D: it's ready** (`web/models/peds/sit.glb`, canon binds 195/195, seated confirmed). + +**Asset:** `web/models/peds/sit.glb` — a mesh-free animation clip, **structurally identical to the +shipped `walk.glb`/`idle.glb`**: 66 nodes (65 `mixamorig` bones + root), 0 meshes, 1 dangling skin +(inverseBindMatrices + 65 joints, referenced by 0 nodes — the vestigial artifact Blender glTF I/O +leaves, which makes glTF tools read it as an armature), 195 channels (65 bones × TRS). 109 KB, no Draco. + +**Source + provenance:** Adobe **Mixamo `Sitting_Idle.fbx`** (533 KB) — fetched by `scp` from the +tailnet source box `johnking@100.91.239.7:~/Documents/mixamo-fetch/out/` per Lane D's recipe (the +local `~/Documents/mixamo-fetch` path is stale/empty). Converted on-device via Blender headless +(`pipeline/fbx_to_clip.py`, **new tool**): import FBX → export skinned GLB → pure-python mesh-strip +that keeps the skeleton + dangling skin + animation and re-indexes/compacts. Raw Mixamo `mixamorig:` +naming is already canonical (`rigs.js._canon` normalizes `mixamorig` → `mixamorig`, so no bone +rename). **Licence zone: Adobe Mixamo royalty-free animation license — the SAME zone as the +already-shipped `walk`/`idle` clips** (the whole ped fleet is Mixamo-rigged). Mesh stripped, so only +the skeleton *motion* ships (retargeted onto the game's own peds), never Mixamo character geometry. + +**Verified (E's half — D verifies the wired in-scene pose):** +- **three.js** (vendored GLTFLoader): loads, dur 4.33 s, 195 tracks, 65 bones, **195/195 bind** to a + ped skeleton after `_canon`, **0 NaN** track values (mesh-strip re-index is clean). +- **Blender** (the tool that measures `walk` at 1.276 m standing): the clip poses **seated** — Hips + descend 0.955 m (standing) → **0.299 m**, stature 1.276 → 0.758 m. Eyes-on bone-dot render confirms + the folded seated silhouette vs walk's upright one: `docs/shots/laneE/sit_clip.png` (left walk, right sit). + +**Depot:** published to `digalot.fyi/3god` via the passwordless tailnet ingress under **John's R13 +authorization** (`_published.json` 38 → 39; confirmed 200 on the public depot). **NOTE (benign):** +`sit.glb` — like `vintage-cash-register.glb` — is non-`procity_`-prefixed, so `publish.py --verify`'s +`procity_`-only drift filter lists it as "not live" (a **false drift** — it IS live). Cosmetic only; +`validate_manifest` gates are unaffected (clips are not manifest GLBs). + +**"Manifest":** ped clips load by **convention** (`rigs.js` `base='models/peds/'`), NOT via +`web/assets/manifest.json` — so there is no asset-manifest entry, same as `walk`/`idle`. D owns the +loader wiring (`spawnRig`/fleet). Bench-sit loiter (Fable's stretch goal) reuses this same clip. + ## Round 14 — v3.0 PROVENANCE FREEZE ✅ (release round) > **FROZEN for the v3.0 tag** (2026-07-16). Lane E R14 is a provenance close-out — **no new synthesis** @@ -30,6 +68,7 @@ | Skins (2D) | 96 | 90sDJsim ports (7 facade + 2 ground) + thriftgod R1 store-art + `flux_local` gap-fill fronts/posters | `gen_skins.py --local` (on-device) | 🟢 web-ok | | Audio pack | 28 | 100% procedural numpy synthesis (`gen_audio.py`) | oscillator/noise render → ffmpeg OGG+M4A | 🟢 CC0-equiv, parody-safe | | Stock packs | 3 (350/311/273) | GODVERSE `dealgod` Postgres — **real** cover art | `build_stock_pack.py`: parody-transform metadata + atlas | 🟢 real-art / parody-metadata | +| Ped clip **⟨v3.1⟩** | `sit.glb` (E R16; + pre-existing `walk`/`idle` from D/90sDJsim) | Adobe **Mixamo** `Sitting_Idle.fbx` | `fbx_to_clip.py` (Blender, mesh-strip) | 🟢 Mixamo royalty-free (motion only, mesh stripped) | **GLB fleet = 33 manifest (23 fit + 10 furniture) + 5 `_hi` = 38 published.** Only external spend ever: ~$0.64 (fal-Hunyuan, R5). Everything else is $0 / on-device (electricity only). diff --git a/docs/shots/laneE/sit_clip.png b/docs/shots/laneE/sit_clip.png new file mode 100644 index 0000000..cb438ba Binary files /dev/null and b/docs/shots/laneE/sit_clip.png differ diff --git a/pipeline/_published.json b/pipeline/_published.json index 9d37327..a384e35 100644 --- a/pipeline/_published.json +++ b/pipeline/_published.json @@ -36,5 +36,6 @@ "procity_street_novelty_record_01.glb", "procity_street_park_bench_01.glb", "procity_street_streetlight_01.glb", + "sit.glb", "vintage-cash-register.glb" ] \ No newline at end of file diff --git a/pipeline/fbx_to_clip.py b/pipeline/fbx_to_clip.py new file mode 100644 index 0000000..7d2fb27 --- /dev/null +++ b/pipeline/fbx_to_clip.py @@ -0,0 +1,134 @@ +"""PROCITY mixamo-clip converter — a Mixamo `.fbx` animation → a mesh-free house clip GLB, the exact +shape of the shipped `web/models/peds/walk.glb` / `idle.glb`: armature + one animation, NO mesh, but +WITH the (now dangling) skin that Blender's glTF I/O leaves behind — 65 `mixamorig` joints + +inverseBindMatrices, referenced by zero nodes. That vestigial skin is what makes glTF tooling (and +Blender re-import) reconstruct the file as an armature; three.js binds the clip by track name via +Lane D's `rigs.js` `_canon` regardless, so no bone rename is needed. + +Runs headless on this box's Blender: + BL=/Applications/Blender.app/Contents/MacOS/Blender + "$BL" --background --python fbx_to_clip.py -- IN.fbx OUT.glb [ClipName] + +Two stages, one command: (1) Blender imports the FBX (mesh+armature+anim) and exports a SKINNED GLB +(the skin needs the mesh to be emitted); (2) a pure-python glTF strip removes the mesh + its geometry +accessors, re-indexing/compacting so only the skeleton + dangling skin + animation remain (≈100 KB). +""" +import bpy, sys, os, json, struct, tempfile + +ARGV = sys.argv[sys.argv.index("--") + 1:] +FBX, OUT = ARGV[0], ARGV[1] +CLIP = ARGV[2] if len(ARGV) > 2 else "Clip" +os.makedirs(os.path.dirname(os.path.abspath(OUT)), exist_ok=True) + + +def wipe(): + for o in list(bpy.data.objects): + bpy.data.objects.remove(o, do_unlink=True) + for coll in (bpy.data.meshes, bpy.data.materials, bpy.data.images, bpy.data.armatures, bpy.data.actions): + for d in list(coll): + if d.users == 0: + coll.remove(d) + + +# ── stage 2: pure-python glTF mesh-strip (keep skeleton + dangling skin + animation) ────────────── +def read_glb(p): + d = open(p, "rb").read() + off = 12; js = None; binblob = b"" + while off < len(d): + clen, ctype = struct.unpack("