The reported bug ("the outer shell of marbles sometimes gets a blue tint
at some locations or angles") was the BOOST GLOW, not the glass.
On ignition the racer wrote emissive 0x2255ff @0.9 to every material it
owned — including the transmission SHELL. A transmission material adds
emissive on top of the refracted core rather than blending with it, so
that flooded the whole sphere with one flat colour; the same saturated
blue on the CORE at 0.9 then swamped each racer's identity. Result: every
boosting marble rendered the same periwinkle, which is why the tint came
and went with location (boost corridors) and took the whole pack at once.
Now: the shell never emits, and the glow is the racer's OWN colour pushed
30% toward white at 0.55 — the marble lights up from inside its glass and
stays itself. Same law at pack scale (instance colour brightens instead of
lerping 3/4 to a shared blue).
Also, found while measuring: the shell carried two stacked reflection
layers (RoomEnvironment PMREM at 1.2 plus clearcoat 1), spraying ~8% of an
environment brighter than this world over every marble pixel, which the
zone's mood lighting tinted. Measured on a red-cored marble, the wash
lifted G/B from ~4 to ~47. One layer at 0.5 now; core saturation at race
distance is up ~40% and the refractive rim and highlights are unchanged.
210 tests pass.- commit
- 62919dbc41c478b3801086a082584cd42b8503a4
- parent
- fbf553171d5e53703fc34093e7c2c8485ec2955a
- author
- Dami <dami.etoile@gmail.com>
| M | .gitignore | 1 | + |
| M | progress.md | 12 | ++++++++++ |
| M | src/ | 35 | ++++++++++++++++++++++-------- |
| M | src/ | 20 | ++++++++++++++--- |
@@ -52,6 +52,18 @@ cross composite, quantitative metrics CI).
5252 transparent shells (only opaques land in the transmission buffer).5353 Escalation if close-ups demand it: port a MeshTransmissionMaterial-5454 style shared-buffer material (drei-vanilla reference).55+- **Nothing emits on the glass shell.** A transmission material ADDS56+ emissive on top of the refracted core instead of blending with it, so57+ any glow written to the shell floods the whole sphere with one flat58+ colour and hides the marble inside. Boost glow lives on the CORE, in59+ the racer's own hue pushed toward white — the old fixed blue at 0.960+ repainted every boosting marble identical (user: "the outer shell61+ gets a blue tint at some locations or angles").62+- **One reflection layer, half strength.** The RoomEnvironment PMREM is63+ brighter than this world, and `clearcoat: 1` stacked a second full64+ Fresnel over the glass's own — together they sprayed ~8% of a hot env65+ over every marble pixel, which the zone mood then tinted. Measured on66+ a red-cored marble: the wash lifted G/B from ~4 to ~47.5567 5668 ## Generation5769 @@ -550,7 +550,11 @@ class Session {
550550 private readonly instQuat = new THREE.Quaternion();551551 private readonly instScale = new THREE.Vector3(1, 1, 1);552552 private readonly instGlow = new THREE.Color();553- private readonly glowBlue = new THREE.Color(0x66aaff);553+ /** Boost glow runs HOT, not blue: it lifts a racer toward white in its554+ * own hue. A fixed blue repainted every boosting marble the same colour555+ * (user: "the outer shell gets a blue tint"), erasing identity exactly556+ * when the pack is most worth reading. */557+ private readonly glowHot = new THREE.Color(0xffffff);554558 private readonly rails = new Map<number, Rail>();555559 /** Braid alt-lane rails (M17): built alongside `rails` for every556560 * segment carrying an alt branch; per-racer lane choice picks between@@ -734,10 +738,11 @@ class Session {
734738 opacity: 0.3,735739 roughness: 0.05,736740 metalness: 0,737- clearcoat: 1,738- clearcoatRoughness: 0.08,741+ // Same one-layer, half-strength reflection law as the742+ // transmission shell (see glassShellMaterial) — a hot env plus743+ // a stacked clearcoat washed the marble to a pale blue.739744 envMap: marbleEnv(ctx.renderer),740- envMapIntensity: 1.1,745+ envMapIntensity: 0.5,741746 depthWrite: false,742747 });743748 heroMaterials.push(g);@@ -2307,19 +2312,31 @@ class Session {
23072312 if (r.obj) {23082313 for (const m of r.materials) {23092314 const std = m as THREE.MeshStandardMaterial;2310- if (std.emissive) {2311- if (r.boost.boosting) std.emissive.setHex(0x2255ff);2312- else if (r.baseEmissive) std.emissive.copy(r.baseEmissive);2315+ if (!std.emissive) continue;2316+ // The glass SHELL never emits. A transmission material adds2317+ // emissive straight on top of the refracted core instead of2318+ // blending with it, so any glow here floods the whole sphere2319+ // with one flat colour and hides the marble inside.2320+ if ((std as THREE.MeshPhysicalMaterial).transmission > 0) continue;2321+ if (r.boost.boosting) {2322+ // Glow in the racer's OWN colour, pushed toward white — the2323+ // marble lights up from inside the glass and stays itself.2324+ std.emissive.copy(std.color).lerp(this.glowHot, 0.3);2325+ std.emissiveIntensity = 0.55;2326+ } else {2327+ if (r.baseEmissive) std.emissive.copy(r.baseEmissive);23132328 else std.emissive.setHex(0x000000);2314- std.emissiveIntensity = r.boost.boosting ? 0.9 : 1;2329+ std.emissiveIntensity = 1;23152330 }23162331 }23172332 } else if (this.peerMesh) {23182333 const base = this.peerColors[r.idx - 1];23192334 this.peerMesh.setColorAt(23202335 r.idx - 1,2336+ // Same law at pack scale: brighten the peer's own colour rather2337+ // than lerp it 3/4 of the way to a shared blue.23212338 r.boost.boosting2322- ? this.instGlow.copy(base).lerp(this.glowBlue, 0.75)2339+ ? this.instGlow.copy(base).lerp(this.glowHot, 0.35)23232340 : base,23242341 );23252342 if (this.peerMesh.instanceColor) {@@ -15,7 +15,10 @@
1515 * Environment: glass is reflection + refraction; the void has neither,1616 * so ONE PMREM RoomEnvironment texture feeds material.envMap on glass1717 * materials only — scene.environment stays unset (it would relight the18- * whole dark world).18+ * whole dark world). That environment is BRIGHTER THAN THE WORLD, so19+ * marble shells keep exactly one reflection layer at half intensity —20+ * never a clearcoat stacked over the glass's own Fresnel (see21+ * glassShellMaterial for the measurement).1922 *2023 * Tunnels: glass tunnels use NON-transmission transparent glass — a2124 * transmissive arch would erase transmissive marbles behind it (only@@ -66,10 +69,19 @@ export function glassShellMaterial(
6669 look.shell[2],6770 ),6871 attenuationDistance: ghost ? 0.6 : 0.15,69- clearcoat: 1,70- clearcoatRoughness: 0.06,72+ // ONE reflection layer, at half strength (user: "the outer shell gets a73+ // blue tint at some locations or angles"). Transmission ADDS specular on74+ // top of the refracted core rather than blending with it, so a hot75+ // environment washes the core out. RoomEnvironment's PMREM is an indoor76+ // room with emissive panels well above 1.0, and `clearcoat: 1` stacked a77+ // SECOND full Fresnel layer over the glass's own — together ~8% of a very78+ // bright env sprayed neutral light over the whole sphere, which the zone's79+ // mood lighting then tinted (periwinkle under the lavender moods). Measured80+ // on a red-cored marble: the wash lifted G/B from ~4 to ~47. Dropping the81+ // redundant clearcoat and halving the intensity cuts it to ~a fifth, and82+ // core saturation at race distance rises ~40%.7183 envMap: marbleEnv(renderer),72- envMapIntensity: 1.2,84+ envMapIntensity: 0.5,7385 iridescence: look.effect.kind === "pearl"7486 ? 0.35 + 0.5 * look.effect.intensity7587 : 0,