Cockpit: unlit map-independent frame colour (task #55)

User report from the MP/cavern session: the canopy frame rendered flat light
gray -- unlike the dark frames in the polar exports.  Root cause: the frame
colour was routed through the LIT material path (Diffuse+Ambient), so it
multiplied with each map's lighting -- polar's dim purple light happened to
look right, cavern-night's ambient 0.8 washed it out to gray.

The canopy is unlit interior structure: the frame batches now go through the
renderer's pure-emissive UNLIT branch (black diffuse + emissive = the frame
colour), constant on every map.  The exact original value is not recoverable
(texture-less ramp = no texel luminance to index); default (0.13,0.12,0.15)
[T3] matches the near-black frame in pod gameplay footage.
BT_COP_FRAME="r g b" overrides; BT_COP_RAMP_L retired.

Verified: cavern-night and polar-day now show the identical dark frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-11 09:42:22 -05:00
co-authored by Claude Fable 5
parent 27959990ea
commit 8af278a3f5
+33 -15
View File
@@ -66,6 +66,18 @@ uint32_t packColor(float r, float g, float b) {
return 0xFF000000u | (cv(r) << 16) | (cv(g) << 8) | cv(b);
}
// The cockpit frame's constant unlit colour (task #55; see the COCKPIT FRAME
// COLOUR note in buildPmesh). BT_COP_FRAME="r g b" (0..1 floats) overrides.
float copFrameRGB(int i) {
static float rgb[3] = {-1.0f, 0.0f, 0.0f};
if (rgb[0] < 0.0f) {
rgb[0] = 0.13f; rgb[1] = 0.12f; rgb[2] = 0.15f;
if (const char* e = getenv("BT_COP_FRAME"))
sscanf(e, "%f %f %f", &rgb[0], &rgb[1], &rgb[2]);
}
return rgb[i];
}
uint32_t colorForMaterial(const std::string& name) {
if (name.empty()) return 0xFFB0B0B8u;
uint32_t h = 2166136261u;
@@ -616,23 +628,18 @@ struct Builder {
// DIAG (task #55): BT_COP_DEBUG paints texture-less ramp batches (the cockpit
// canopy shell blx_cop -- ramp 'softer', no texture) bright green, to SEE what
// the shell actually covers from the real cockpit eyepoint (frame vs open).
// COCKPIT FRAME COLOUR (*_cop shells only): ramped NO-NORMAL geometry with NO
// texture (blakskn_dz + 'softer'). Per the engine material-ramp model the RAMP is
// the surface colour and the (0,0,0) DIFFUSE is IGNORED -- the old tint rule
// collapsed it to black. With no texture there is no per-texel luminance, so
// evaluate the ramp at a flat index. Default 0.08 = near the ramp's DARK end:
// the canopy frame is unlit interior structure, and the dark value matches the
// near-black frame in the cabinet gameplay footage ([T3] tuned-to-footage; the
// board's exact unlit ramp index is not recoverable from the code).
// BT_COP_RAMP_L overrides (diagnostic).
// COCKPIT FRAME COLOUR (*_cop shells only, task #55 FINAL): the canopy is
// UNLIT interior structure -- routing its colour through the lit material
// path made the frame change with each MAP's ambient/sun (polar = dark
// purple, cavern-night ambient 0.8 = flat LIGHT GRAY, the user's report).
// The batch is forced through the renderer's pure-emissive UNLIT branch
// below with a constant dark colour, map-independent. The exact original
// value is not recoverable from the code (texture-less ramp = no texel
// luminance to index); default (0.13,0.12,0.15) [T3] matches the dark
// near-black frame in pod gameplay footage. BT_COP_FRAME="r g b" overrides.
if (useRamp && currentTex.empty() && meshIsCop) {
static float s_copL = -1.0f;
if (s_copL < 0.0f) { const char* e = getenv("BT_COP_RAMP_L"); s_copL = e ? (float)atof(e) : 0.08f; }
float cr = currentRampLo[0] + (currentRampHi[0]-currentRampLo[0])*s_copL;
float cg = currentRampLo[1] + (currentRampHi[1]-currentRampLo[1])*s_copL;
float cb = currentRampLo[2] + (currentRampHi[2]-currentRampLo[2])*s_copL;
auto CB = [](float ff){ int v=(int)(ff*255.0f+0.5f); return (uint32_t)(v<0?0:v>255?255:v); };
vcol = 0xFF000000u | (CB(cr)<<16) | (CB(cg)<<8) | CB(cb);
vcol = 0xFF000000u | (CB(copFrameRGB(0))<<16) | (CB(copFrameRGB(1))<<8) | CB(copFrameRGB(2));
}
if (useRamp && currentTex.empty() && meshIsCop && getenv("BT_COP_DEBUG"))
vcol = 0xFF00FF00u;
@@ -736,6 +743,17 @@ struct Builder {
batch.lodFar = currentLodFar;
batch.punch = currentPunch;
batch.copRole = currentCopRole; // punch stencil-kit role (task #55)
// COCKPIT FRAME = UNLIT (task #55): force the frame batches through the
// renderer's pure-emissive branch (black colour + emissive = the frame
// RGB) so the canopy stays the same dark colour on EVERY map instead of
// riding the map's ambient/sun (see the FRAME COLOUR note above).
if (meshIsCop && useRamp && currentTex.empty()) {
batch.color = 0xFF000000u;
batch.hasEmissive = true;
batch.emissive[0] = copFrameRGB(0);
batch.emissive[1] = copFrameRGB(1);
batch.emissive[2] = copFrameRGB(2);
}
batch.shadowMat = currentShadowMat;
batch.hasEmissive = currentHasEmissive;
for (int i = 0; i < 3; ++i) batch.emissive[i] = currentEmissive[i];