From 6671cf08f41d70947ed7d236b92f634dda324c1a Mon Sep 17 00:00:00 2001 From: m3ultra Date: Thu, 16 Jul 2026 14:32:58 +1000 Subject: [PATCH] Lane D R17 (v3.2): the drummer's pelvic-lean (ledger #1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The R16 seated drummer sat bolt-upright (the sit clip can't carry a pelvic tilt — Hips.quaternion is unusable on these rigs). Fix: a small constant forward torso lean applied POST-MIX. spawnRig({seated}) caches the mid-spine bone (seatBone); seatedLean(spine) rotates it forward ~0.34 rad each frame after the mixer resets it (constant offset, not accumulation); band.js calls it for m.seated members. The drummer leans into the kit — head 0.28m forward of the hips, reads as a real drummer from the side. Byte-identical (my signature): standing rigs get no seatBone (found only in the seated block) and seatedLean runs for seated figs only. Verified with the lean live — walkers 188/188 face travel, keeper standing rig (stature 1.718, hasSeatBone:false, innerRotY pi). No existing consumer moves. Co-Authored-By: Claude Opus 4.8 --- docs/LANES/LANE_D_NOTES.md | 19 +++++++++++++++++++ web/js/citizens/band.js | 3 ++- web/js/citizens/rigs.js | 15 ++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/LANES/LANE_D_NOTES.md b/docs/LANES/LANE_D_NOTES.md index e4ea0f9..4c1baea 100644 --- a/docs/LANES/LANE_D_NOTES.md +++ b/docs/LANES/LANE_D_NOTES.md @@ -6,6 +6,25 @@ _rotOnly/head-bone normalize/upgradeStreetPeople). Measurements on the M3 Ultra --- +## ROUND 17 — v3.2 THE TAIL: the drummer's pelvic-lean (ledger #1) + +*PROCITY-D, 2026-07-16. The R16 honest limit — the upright bent-leg sit read stiff — is fixed. Files +`rigs.js`, `band.js`. Byte-identical proof holds (my signature discipline).* + +The sit clip can't carry a pelvic tilt (`Hips.quaternion` is unusable on these rigs — R16), so the R16 +drummer sat bolt-upright. **Fix: a small constant forward torso lean applied POST-MIX.** `rigs.js` +`spawnRig({seated})` now caches the mid-spine bone (`seatBone`), and `seatedLean(spine)` rotates it forward +~0.34 rad each frame AFTER the mixer resets it (so it's a constant offset, never an accumulation). `band.js` +calls it for `m.seated` members only. The drummer now leans into the kit — verified **head 0.28 m forward +of the hips**, reads as a real drummer from the side (the R16 stiffness is gone). + +**Byte-identical proof**: standing rigs get **no `seatBone`** (found only in `spawnRig`'s seated block) and +`seatedLean` is only called for seated figs. Verified with the lean live: streamed walkers **188/188** face +travel, keeper is a standing rig (stature **1.718**, `hasSeatBone:false`, `innerRotY:π`). No existing +consumer moves. Seated-only, parallel path — same discipline as R16. + +--- + ## ROUND 16 — v3.1 THE FLIP: the drummer finally sits + backline unification *PROCITY-D, 2026-07-16. Ledger #2 (sit-clip wiring) + #5 (amp at C's `backline[]`). Files `rigs.js`, diff --git a/web/js/citizens/band.js b/web/js/citizens/band.js index 90cda30..0182f33 100644 --- a/web/js/citizens/band.js +++ b/web/js/citizens/band.js @@ -16,7 +16,7 @@ import * as THREE from 'three'; import { rng, shuffle } from '../core/prng.js'; -import { pickRig, spawnRig } from './rigs.js'; +import { pickRig, spawnRig, seatedLean } from './rigs.js'; import { makePlaceholder } from './placeholder.js'; import { loadGLB } from '../core/loaders.js'; @@ -271,6 +271,7 @@ export class GigCrew { for (const m of this.members) { if (m.actor.mixer) m.actor.mixer.update(dt); // rig idle else m.actor.tick && m.actor.tick(dt, false); // placeholder idle + if (m.seated) seatedLean(m.actor.seatBone); // R17: tilt the drummer forward into the kit (post-mix) const f = m.actor.fig, b = m.base, ph = m.phase; if (m.part === 'band') { if (m.seated) { // drummer: seated bob + a little shoulder work, planted diff --git a/web/js/citizens/rigs.js b/web/js/citizens/rigs.js index cb624aa..146bb48 100644 --- a/web/js/citizens/rigs.js +++ b/web/js/citizens/rigs.js @@ -189,6 +189,7 @@ export function spawnRig(rig, { ry = 0, clip = null, height = 1.75, phase = 0, s const mixer = new THREE.AnimationMixer(inner); const chosen = clip || rig.anims.find(c => /idle/i.test(c.name)) || rig.anims[1] || rig.anims[0]; const act = _action(mixer, inner, chosen); + let seatBone = null; if (act) { act.time = phase * (act.getClip().duration || 0); act.play(); // R16 SEATED re-plant (OPT-IN — standing spawns skip this, so keepers/browsers/band are byte-identical). @@ -202,10 +203,22 @@ export function spawnRig(rig, { ry = 0, clip = null, height = 1.75, phase = 0, s let m = Infinity; inner.traverse(o => { if (o.isBone) { o.getWorldPosition(_wp); m = Math.min(m, _wp.y); } }); if (isFinite(m)) inner.position.y -= m; + // R17 pelvic-lean: cache the mid-spine bone so seatedLean() can tilt the torso forward each frame (the + // sit clip can't — Hips.quaternion is unusable on these rigs). Seated-only; standing rigs get no seatBone. + inner.traverse(o => { if (!seatBone && o.isBone && /Spine1$/i.test(o.name)) seatBone = o; }); } } function dispose() { mixer.stopAllAction(); mixer.uncacheRoot(inner); _disposeInner(inner); } - return { fig, inner, mixer, height, head, dispose }; + return { fig, inner, mixer, height, head, seatBone, dispose }; +} + +// R17 pelvic-lean: tilt a seated fig's torso forward, applied AFTER the mixer each frame. The mixer resets +// the bone's quaternion from the clip every frame, so this is a CONSTANT offset (not an accumulation). Only +// seated figs (drummer, bench-sit loiterers) call it; standing rigs have no seatBone → never leaned. +const _LEAN_AXIS = new THREE.Vector3(1, 0, 0); +const _LEAN_Q = new THREE.Quaternion(); +export function seatedLean(spine, angle = 0.34) { + if (spine) spine.quaternion.multiply(_LEAN_Q.setFromAxisAngle(_LEAN_AXIS, angle)); } // ---- makeActor: near-tier pedestrian with walk↔idle crossfade (built at nominal height; the sim