Lane D R17 (v3.2): the drummer's pelvic-lean (ledger #1)

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 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-16 14:32:58 +10:00
parent 6fffd19043
commit 6671cf08f4
3 changed files with 35 additions and 2 deletions

View File

@ -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`,

View File

@ -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

View File

@ -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