Compare commits

..

No commits in common. "046668e5af55515029cd11b9f6fc1b89d0c7ff9d" and "3c5d0a1e95d44e79879ac0a935baae699f0768f6" have entirely different histories.

12 changed files with 12 additions and 29 deletions

View File

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

View File

@ -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, dance = false }) {
_make(target, key, { x, y, z, ry, height, pedIndex = null, seated = false }) {
const r = rng(this.citySeed, 'gig', key);
const h = height != null ? height : 1.6 + r() * 0.3;
let actor, kind, usedIndex = null;
@ -146,10 +146,7 @@ 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
// 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 });
const sp = rig && spawnRig(rig, { ry, height: h, clip: useSit ? this.fleet.sitClip : 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'; }
@ -203,8 +200,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, dance: !!w.dance }
: { x: w.x, y: 0, z: w.z, ry: w.ry, dance: !!w.dance });
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 });
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: [] });
@ -287,14 +284,10 @@ 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
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 if (m.dance) { // dancer: bounce + sway + a little step
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;

View File

@ -18,8 +18,7 @@ export const PED_NAMES = {
normal: ['man_worker_hivis_01', 'man_suit_01', 'man_casual_01', 'man_casual_02', 'man_casual_03',
'man_grunge_01', 'man_elder_01', 'man_athletic_01', 'man_bouncer_01', 'man_soldier_ww2_01',
'man_dj_streetwear_01', 'woman_casual_01', 'woman_casual_02', 'woman_business_01',
'woman_athleisure_01', 'woman_smart_01', 'woman_chef_01',
'woman_dj_01', 'woman_dj_02', 'woman_dj_03', 'dj_techno_01', 'dj_phrtt_01'], // trellis'd DJ bodies (fbx_to_ped, 4k tris)
'woman_athleisure_01', 'woman_smart_01', 'woman_chef_01'],
comical: ['comical_luchador_01', 'comical_boy_01'],
};
@ -56,10 +55,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, dance = false } = {}) {
export function loadPedFleet(base = 'models/peds/', { sit = false, look = false } = {}) {
const fleet = {
normal: [], comical: [], all: [], // all = normal ++ comical, stable index for the impostor atlas
walkClip: null, idleClip: null, sitClip: null, lookClip: null, danceClips: [],
walkClip: null, idleClip: null, sitClip: null, lookClip: null,
ready: false, whenReady: null,
};
// DETERMINISM: fill fixed slots by PED_NAMES index, NOT push-on-resolve — otherwise the array
@ -90,19 +89,10 @@ 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;
});

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.