feat(gig): club dancers play real dance clips, not procedural sway
loadPedFleet gains dance:!CLASSIC (mirrors the sit/look gate) → fleet.danceClips = 4 mesh-free mixamorig club dances (party/medium/drink/sway). _make picks one per dancer off a fresh gigdance seeded stream (zero impact on existing draws / the same-seed-same-crowd covenant), spawnRig plays it, and update() drops the fake bounce for real-clip dancers (procedural sway stays as the classic/noassets fallback). ?classic fetches nothing, byte-identical. Verified: default boot fetches all 4 dance GLBs (gated load runs), both modules parse + export clean, page boots. Same clips + _canon/_rotOnly/spawnRig proven animating in 90sDJsim (its crowd/rigs.js is this file ported). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b3394027be
commit
046668e5af
@ -187,7 +187,7 @@ const minimap = createMinimap(plan);
|
||||
// unless) a GLB lands, so the town is fully playable the instant it boots.
|
||||
// • rig fleet: models/peds/*.glb → shared GLB actors; CitizenSim + keepers upgrade from placeholders.
|
||||
// • manifest: Lane E's assets/manifest.json → interior fitting GLBs (loaded from the 3GOD depot).
|
||||
const fleet = NOASSETS ? null : loadPedFleet('models/peds/', { sit: !CLASSIC, look: !CLASSIC }); // [R16] sit.glb (the seated drummer) + [R29] look.glb (the street glance) on the default boot; ?classic keeps the zero-fetch-delta covenant
|
||||
const fleet = NOASSETS ? null : loadPedFleet('models/peds/', { sit: !CLASSIC, look: !CLASSIC, dance: !CLASSIC }); // [R16] sit.glb (the seated drummer) + [R29] look.glb (the street glance) on the default boot; ?classic keeps the zero-fetch-delta covenant
|
||||
if (!NOASSETS) preloadManifest(); // prime the manifest so the first interior can upgrade its fittings
|
||||
// [Lane F integration] interior bridge — owns the 'interior' branch (Lane C buildInterior + Lane D keeper)
|
||||
// [Lane F R31 — THE DIG FLIP] The crate-riffle is DEFAULT-ON: `?dig=0` opts out, `?classic=1` forces it off
|
||||
|
||||
@ -138,7 +138,7 @@ export class GigCrew {
|
||||
// one seeded rig (or placeholder) actor, planted + facing ry. `key` seeds identity per gig+slot.
|
||||
// `seated` (R16): the drummer — use E's sit.glb pose (fleet.sitClip) + spawnRig's foot-replant + tag the
|
||||
// fig for F's stature gate; falls back to the standing idle (caller sinks it via SEAT_DROP) if no sitClip.
|
||||
_make(target, key, { x, y, z, ry, height, pedIndex = null, seated = false }) {
|
||||
_make(target, key, { x, y, z, ry, height, pedIndex = null, seated = false, dance = false }) {
|
||||
const r = rng(this.citySeed, 'gig', key);
|
||||
const h = height != null ? height : 1.6 + r() * 0.3;
|
||||
let actor, kind, usedIndex = null;
|
||||
@ -146,7 +146,10 @@ export class GigCrew {
|
||||
const pk = (pedIndex != null && this.fleet.all[pedIndex]) ? { index: pedIndex } : pickRig(this.fleet, r());
|
||||
const rig = pk && this.fleet.all[pk.index];
|
||||
const useSit = seated && !!this.fleet.sitClip; // real seated pose iff E's sit clip is loaded
|
||||
const sp = rig && spawnRig(rig, { ry, height: h, clip: useSit ? this.fleet.sitClip : this.fleet.idleClip, phase: r(), seated: useSit });
|
||||
// dancers play a real dance clip (separate 'gigdance' stream keeps the pick off the existing draws)
|
||||
const dcs = this.fleet.danceClips || [];
|
||||
const danceClip = (dance && dcs.length) ? dcs[(rng(this.citySeed, 'gigdance', key)() * dcs.length) | 0] : null;
|
||||
const sp = rig && spawnRig(rig, { ry, height: h, clip: useSit ? this.fleet.sitClip : (danceClip || this.fleet.idleClip), phase: r(), seated: useSit });
|
||||
if (sp) { actor = sp; kind = 'rig'; usedIndex = pk.index; if (useSit) sp.fig.userData.procitySeated = true; }
|
||||
}
|
||||
if (!actor) { actor = makePlaceholder(rng(this.citySeed, 'gig-body', key), { height: h }); actor.fig.rotation.y = ry; kind = 'placeholder'; }
|
||||
@ -200,8 +203,8 @@ export class GigCrew {
|
||||
pts.forEach((w, i) => {
|
||||
const who = roster[i] || null;
|
||||
const { actor, kind, pedIndex } = this._make(roomGroup, `crowd:${gid}:${w.slotIndex}`,
|
||||
who ? { x: w.x, y: 0, z: w.z, ry: w.ry, pedIndex: who.pedIndex, height: who.height }
|
||||
: { x: w.x, y: 0, z: w.z, ry: w.ry });
|
||||
who ? { x: w.x, y: 0, z: w.z, ry: w.ry, pedIndex: who.pedIndex, height: who.height, dance: !!w.dance }
|
||||
: { x: w.x, y: 0, z: w.z, ry: w.ry, dance: !!w.dance });
|
||||
if (who) fromRoster++;
|
||||
this.members.push({ actor, kind, pedIndex, part: 'crowd', role: 'fan', dance: !!w.dance, seated: false, fromRoster: !!who,
|
||||
base: { x: w.x, y: 0, z: w.z, ry: w.ry }, phase: rng(this.citySeed, 'gigp', `c${gid}:${w.slotIndex}`)() * Math.PI * 2, extra: [] });
|
||||
@ -284,10 +287,14 @@ export class GigCrew {
|
||||
f.rotation.z = Math.sin(t * 3.1 + ph) * 0.055;
|
||||
f.position.y = b.y + Math.abs(Math.sin(t * 3.1 + ph)) * 0.02;
|
||||
}
|
||||
} else if (m.dance) { // dancer: bounce + sway + a little step
|
||||
} else if (m.dance) { // dancer
|
||||
if (this.fleet && this.fleet.danceClips && this.fleet.danceClips.length) {
|
||||
f.rotation.y = b.ry + Math.sin(t * 0.6 + ph) * 0.10; // real dance clip drives the body; just a slow facing drift
|
||||
} else { // fallback (classic/noassets): procedural bounce + sway
|
||||
f.position.y = b.y + Math.abs(Math.sin(t * 5.2 + ph)) * 0.075;
|
||||
f.rotation.z = Math.sin(t * 2.6 + ph) * 0.07;
|
||||
f.rotation.y = b.ry + Math.sin(t * 1.7 + ph) * 0.13;
|
||||
}
|
||||
} else { // stander: slow weight shift, feet planted
|
||||
f.rotation.z = Math.sin(t * 0.9 + ph) * 0.022;
|
||||
f.rotation.y = b.ry + Math.sin(t * 0.5 + ph) * 0.03;
|
||||
|
||||
@ -56,10 +56,10 @@ function loadRig(ref) {
|
||||
// sit.glb fetch would break the ?classic=1 zero-fetch-delta covenant — F passes sit:true on the gig/default
|
||||
// boot, sit:false (default) under ?classic. Absent/off → fleet.sitClip stays null and the drummer falls
|
||||
// back to the standing-sunk hack, so this never breaks a boot. Does NOT touch fleet.ready / determinism.
|
||||
export function loadPedFleet(base = 'models/peds/', { sit = false, look = false } = {}) {
|
||||
export function loadPedFleet(base = 'models/peds/', { sit = false, look = false, dance = false } = {}) {
|
||||
const fleet = {
|
||||
normal: [], comical: [], all: [], // all = normal ++ comical, stable index for the impostor atlas
|
||||
walkClip: null, idleClip: null, sitClip: null, lookClip: null,
|
||||
walkClip: null, idleClip: null, sitClip: null, lookClip: null, danceClips: [],
|
||||
ready: false, whenReady: null,
|
||||
};
|
||||
// DETERMINISM: fill fixed slots by PED_NAMES index, NOT push-on-resolve — otherwise the array
|
||||
@ -90,10 +90,19 @@ export function loadPedFleet(base = 'models/peds/', { sit = false, look = false
|
||||
if (look) jobs.push(loadRig(`${base}look.glb`).then(r => {
|
||||
const c = r && r.anims && r.anims[0];
|
||||
if (c) { c.tracks.forEach(t => t.name = _canon(t.name)); fleet.lookClip = _rotOnly(c); } }));
|
||||
// dance clips (mesh-free mixamorig): the club dancers play a REAL move instead of procedural sway.
|
||||
// GATED like sit/look — ?classic fetches nothing so the zero-fetch-delta covenant holds. The per-dancer
|
||||
// pick is deterministic (a separate seeded stream in _make), so 'same seed -> same crowd' is preserved.
|
||||
const DANCE_CLIPS = ['dance_party', 'dance_medium', 'dance_drink', 'dance_sway'];
|
||||
const danceSlots = new Array(DANCE_CLIPS.length).fill(null);
|
||||
if (dance) DANCE_CLIPS.forEach((n, i) => jobs.push(loadRig(`${base}${n}.glb`).then(r => {
|
||||
const c = r && r.anims && r.anims[0];
|
||||
if (c) { c.tracks.forEach(t => t.name = _canon(t.name)); danceSlots[i] = _rotOnly(c); } })));
|
||||
fleet.whenReady = Promise.all(jobs).then(() => {
|
||||
fleet.normal = nSlots.filter(Boolean);
|
||||
fleet.comical = cSlots.filter(Boolean);
|
||||
fleet.all = [...fleet.normal, ...fleet.comical];
|
||||
fleet.danceClips = danceSlots.filter(Boolean);
|
||||
fleet.ready = fleet.all.length > 0 && !!fleet.walkClip;
|
||||
return fleet;
|
||||
});
|
||||
|
||||
BIN
web/models/peds/dance_drink.glb
Normal file
BIN
web/models/peds/dance_drink.glb
Normal file
Binary file not shown.
BIN
web/models/peds/dance_medium.glb
Normal file
BIN
web/models/peds/dance_medium.glb
Normal file
Binary file not shown.
BIN
web/models/peds/dance_party.glb
Normal file
BIN
web/models/peds/dance_party.glb
Normal file
Binary file not shown.
BIN
web/models/peds/dance_sway.glb
Normal file
BIN
web/models/peds/dance_sway.glb
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user