Wreck scorch quad: kill the black-rectangle tint + cross-library textures (issue #3)

The opaque BLACK rectangle under a destroyed truck was basev:bvx9_mtl (the
MECHMD wreck's 4-vtx scorch base): the material authors NO diffuse, ambient
(0,0,0), texture bexp9_tex (MAP 'bexp' slice 8, BEXP.BSL) + ramp cdusty.
collectMaterials' AMBIENT fallback took the all-zero ambient as a REAL
colour (hasDiffuse=true, black) and tinted the whole quad black regardless
of its texture.  An all-zero ambient means UNSET, not 'tint black' (a black
tint renders geometry invisible -- never authored intent): the fallback now
requires r+g+b > 0.001.  Pixel-verified (BT_SHOT ram run): the scorch base
draws as the authored charred burn patch under the wreck debris.

Also: cross-library TEXTURE registry (globalTexMaps, BT_TEX_XLIB=0
disables) mirroring the existing globalRamps -- material libs reference
textures DEFINED in other libs (bexp9_tex is defined in BTARENA/BTFX, not
BASEV.BMF); a per-file texMaps miss left such batches untextured.  Same
first-wins sweep of the indexed BMFs on first miss.

Regression scope: only materials authoring a zero ambient AND no diffuse
change (they now ramp/texture instead of black); cross-lib refs that
previously resolved keep their same-file definitions (checked first).

Awaiting human verification in a live session (issue #3 stays open).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-19 10:12:17 -05:00
co-authored by Claude Fable 5
parent 83385b0661
commit 557e9fd243
+52 -7
View File
@@ -421,6 +421,36 @@ struct MaterialResolver {
return reg;
}
// CROSS-LIBRARY TEXTURE REGISTRY (issue #3, the black scorch quad): material
// libs also reference TEXTURES defined in OTHER libs -- MECHMD.bgf's scorch
// base uses basev:bvx9_mtl whose texture `bexp9_tex` is defined in the FX
// libs (image in BEXP.BSL), not in BASEV.BMF. A per-file texMaps lookup
// left the quad untextured, drawn with its zero ambient = an opaque BLACK
// rectangle. Same shape as the ramp registry above: on first cross-file
// miss, sweep the indexed BMFs once and cache every TEXTURE definition
// (first stem in priority order wins; same-file defs are preferred before
// this registry is hit). BT_TEX_XLIB=0 disables.
static const std::map<std::string, TexRef>& globalTexMaps() {
static std::map<std::string, TexRef> reg;
static bool built = false;
if (!built) {
built = true;
const char* v = getenv("BT_TEX_XLIB");
if (v == nullptr || v[0] != '0') {
for (const auto& kv : bmfIndex()) {
std::vector<uint8_t> buf = readFileBytes(kv.second);
if (buf.size() <= 8 || std::memcmp(buf.data(), "DIV-BIZ2", 8) != 0) continue;
std::vector<Chunk> roots;
if (!parseRange(buf.data() + 8, buf.data() + buf.size(), roots)) continue;
std::map<std::string, TexRef> maps;
collectTextureMaps(roots, maps);
for (const auto& m : maps) reg.insert(m); // first wins
}
}
}
return reg;
}
static void collectMaterials(const std::vector<Chunk>& cs,
const std::map<std::string, TexRef>& texMaps,
const std::map<std::string, std::array<float,6> >& ramps,
@@ -438,7 +468,13 @@ struct MaterialResolver {
info.color = packColor(rdF32(ch.data), rdF32(ch.data + 4), rdF32(ch.data + 8));
haveColor = true;
info.hasDiffuse = true;
} else if (ch.id == TAG_AMBIENT && ch.len >= 12 && !haveColor) {
} else if (ch.id == TAG_AMBIENT && ch.len >= 12 && !haveColor
&& (rdF32(ch.data) + rdF32(ch.data + 4) + rdF32(ch.data + 8)) > 0.001f) {
// AMBIENT fallback only when it carries a real colour: an
// all-zero ambient means "unset", not "tint black" -- e.g.
// basev:bvx9_mtl (the wreck scorch quad, issue #3) authors
// ambient (0,0,0) + texture + ramp; the black fallback
// painted the whole quad as an opaque BLACK rectangle.
info.color = packColor(rdF32(ch.data), rdF32(ch.data + 4), rdF32(ch.data + 8));
haveColor = true;
info.hasDiffuse = true;
@@ -467,16 +503,25 @@ struct MaterialResolver {
}
}
if (!name.empty() && (haveColor || !texName.empty() || info.hasRamp)) {
const TexRef* ref = nullptr;
auto tm = texMaps.find(texName);
if (tm != texMaps.end()) {
if (tm != texMaps.end()) ref = &tm->second;
else if (!texName.empty()) {
// cross-library texture reference (see globalTexMaps):
// e.g. basev:bvx9_mtl -> bexp9_tex (defined in the FX libs)
const auto& reg = globalTexMaps();
auto gt = reg.find(texName);
if (gt != reg.end()) ref = &gt->second;
}
if (ref != nullptr) {
const auto& imgs = imageIndex();
auto f = imgs.find(stemLower(tm->second.map));
auto f = imgs.find(stemLower(ref->map));
if (f != imgs.end()) {
info.texPath = f->second;
info.texChannel = tm->second.channel;
info.texScroll = tm->second.scroll;
info.texScrollU = tm->second.scrollU;
info.texScrollV = tm->second.scrollV;
info.texChannel = ref->channel;
info.texScroll = ref->scroll;
info.texScrollU = ref->scrollU;
info.texScrollV = ref->scrollV;
}
}
out[name] = info;