From 9fc80c38ebcd2b35901b3c8fd47875f4bdbfd547 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Mon, 21 Sep 2026 20:47:53 +0200 Subject: [PATCH] Ride: roadside figures always visible, statue-scale, glowing Forced actor slots on like the billboard (sparse setups hid them), figures normalized to landmark scale against houses, crew-like emissive so they read at night. They were present but effectively invisible: gated, unlit, person-scale at the frame edge. --- flow-state/src/venues/ride.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/flow-state/src/venues/ride.js b/flow-state/src/venues/ride.js index 055daf1..09893d7 100644 --- a/flow-state/src/venues/ride.js +++ b/flow-state/src/venues/ride.js @@ -1105,17 +1105,21 @@ export const ride = { const dense = (arrangement % 2 === 1); const showUpTo = dense ? n : Math.max(3, Math.floor(n * 0.45)); // A billboard shot with a hidden billboard is an empty road: the - // board's own slot is always on while it is the subject. + // board's own slot is always on while it is the subject. Actor + // figures likewise always show: they are the cast, not dressing, + // and at most two slots ever hold them. const forced = billboard ? billSlotIndex(instance) : -1; for (let i = 0; i < n; i++) { - instance.roadside.children[i].visible = opacity > 0.001 && (i < showUpTo || i === forced); + const kid = instance.roadside.children[i]; + const isActor = !!kid.userData.actorSlot; + kid.visible = opacity > 0.001 && (i < showUpTo || i === forced || isActor); } } // Roadside actors: figures of the track's cast standing among the // props on some builds (seeded at build; static all video, so they // stream past with their slot and never pop). Grown here, on first // update, because identity only arrives via update — the crew pattern. - // They turn slowly to feel alive; analytic f(t), seek-stable. + // They glance around to feel alive; analytic f(t), seek-stable. if (instance.roadside && identity && actorSpec && actorSpec.form) { for (const g of instance.roadside.children) { if (!g.userData.actorSlot) continue; @@ -1133,20 +1137,24 @@ export const ride = { const c = pal[pPal % pal.length] || pal[0]; m.material = new T.MeshStandardMaterial({ color: new T.Color(c[0], c[1], c[2]), + emissive: new T.Color(c[0] * 0.3, c[1] * 0.3, c[2] * 0.3), + emissiveIntensity: 1.0, roughness: 0.5, metalness: 0.08, transparent: opacity < 0.999, opacity, }); fig.add(m); } - // Feet on the ground, person-scale: normalize the form - // to ~1.6 units tall (houses run 2-4) and plant the - // lowest point at the roadside level regardless of - // the form's own extent or origin. Local box - // (unparented) over slot scale: exact. + // Feet on the ground, landmark-scale: person-scale + // figures are invisible from chase distance, so these + // stand ~2.6 units against 2-4 unit houses — statues + // of the cast among the props. Lowest point planted + // at the roadside level regardless of the form's own + // extent or origin. Local box (unparented) over slot + // scale: exact. if (fig.children.length) { const _box = new T.Box3().setFromObject(fig); const _h = _box.max.y - _box.min.y; - const _s = _h > 1e-6 ? 1.6 / _h : 1; + const _s = _h > 1e-6 ? 2.6 / _h : 1; fig.scale.setScalar(_s); fig.position.y = -1.15 / g.scale.x - _s * _box.min.y; } @@ -1168,6 +1176,7 @@ export const ride = { ? actorSpec.paletteMap[pIdx % actorSpec.paletteMap.length] : pIdx; const c = pal[pPal % pal.length] || pal[0]; m.material.color.setRGB(c[0], c[1], c[2]); + m.material.emissive.setRGB(c[0] * 0.3, c[1] * 0.3, c[2] * 0.3); m.material.opacity = opacity; m.material.transparent = opacity < 0.999; m.visible = opacity > 0.01;