Fire VISUALS wave: the authored firesmoke sheet, vertex-alpha effect cards, the case-4 wreck dressing

The "fireballs like the demo vids" arc, decomp/content-grounded end to end,
plus the live-play UX batch verified over the same sessions:

FIRESMOKE SHEET (the PFX fireball fix): every firesmokeN_scr_tex in BTFX.VMF
maps the SAME 64x64 tileable noise image bintA (variants differ only in SCROLL
rate) and firesmoke1_mtl colours it through the "fiery" ramp (0.3,0.1,0.1)->
(0.9,0.7,0.3).  The particle layer now bakes ramp(lum(bintA)) as its sprite
colour (noise detail in alpha) and SCROLLS it at firesmoke1's authored rate
via a texture-transform; the port's radial soft-edge mask moved to a second
CLAMPed stage so the WRAPPED scroll rolls flame through the sprite without
scrolling the edge away.  Old grit x radial bake kept as the no-BINTA fallback.
Impact hits, damage-band smoke and death booms all ride this layer.

AUTHORED TEXTURE SCROLL in the model path: the BMF TEXTURE records carry
SPECIAL " SCROLL u0 v0 du dv" (tag 0x2037); the draw path always supported
per-op scrolling (SetTextureScrolling) but the BGF loader never parsed it, so
every scrolling material rendered frozen.  Wired TexRef -> MatInfo -> batch ->
L4TEXOP.doScroll: the flame cards (flamebig/fire5) now roll fire noise.

VERTEX-ALPHA EFFECT CARDS (the "twisted drill bit of fire" fix): FLAMEBIG's
verts carry authored float RGBA -- white-hot base (1.0,0.99,0.97) -> dark-red
tip fading to alpha -0.2 (the DPL clamp convention).  The loader kept a flat
batch colour and drew it OPAQUE = a solid orange spike.  Corpus sweep: exactly
14 shipped BGFs carry vertex alpha, ALL effect cards (flames, MUZFLASH,
EXDISK_A/B/C, TMST_A/B/C, beam models, DECLOUDS).  Such batches now keep the
authored per-vertex gradient and route to the alpha-blend pass, unlit,
colour = texture x gradient, alpha = the vertex fade; sky objects excluded
(drawAsSky + alphaTest passes NEITHER pass filter -- DECLOUDS stays in the
sky pass).  MUZFLASH/EXDISK render correctly for free when the muzzle-model
work lands.

WRECK DRESSING (the 1996 ExplosionScripts case-4 transcription): pieces spawn
HIDDEN and reveal 0.25s after the boom (the InstanceSwitch delay, behind the
dnboom flash); flamebig hangs over the pile, Y-BILLBOARDED at the camera
(SetOffsetYaw + a camera-pos getter -- the dpl_SetDCSReorientAxes analog);
the MakeDCSFall settle arms at the reveal with the two authored rates (hulk/
debris -0.025 t^2, fires -0.01 t^2 -- the flames ride above the sinking pile
and die with it at burial).  EMPTY-PLACEHOLDER hulk guard: THRDBR.BGF is a
153-byte zero-geometry stub that "loads fine" -- vertex-count check now routes
it to the gendbr fallback (a Thor wreck was invisible).  Hulk content census
recorded: AVADBR==MADDBR==VULDBR geometry (palette-only prefix diffs),
RAPDBR==SNDDBR==STIDBR byte-identical -- wreck variety is materials + the
dressing, not unique piles.

LIVE-PLAY BATCH: muzzle resolve uses the named segmentIndex (raw +0xdc read
was layout garbage); forward launch frame (authored MuzzleVelocity +Z vs the
mech's -Z facing); dock-bottom single window (gauge strip appended below the
world viewport, 1100x600 default, BT_DEV_GAUGES_WINDOW=1 restores the separate
window); portrait sec surface unrotated CW; ammo counters live via typed
bridges (BTAmmoBinCountPtr/BTAmmoBinFeeding/BTWeaponAmmoBin -- raw bin+0x180
and a hand-rolled link walk were garbage); fourth fire key ('4' = Pinky);
panel/arc probes de-aliased (%61 prime).

KB: rendering.md (vertex-alpha card family + scroll), combat-damage.md (hulk
census + THRDBR stub), gauges-hud.md (ammo bridges).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-12 20:04:29 -05:00
co-authored by Claude Fable 5
parent bb795e2805
commit 48c9c8444f
23 changed files with 701 additions and 109 deletions
+62 -2
View File
@@ -423,7 +423,14 @@ d3d_OBJECT* d3d_OBJECT::LoadObjectBGF(LPDIRECT3DDEVICE9 device, char *fileName)
m.Diffuse.a = 1.0f;
m.Ambient = m.Diffuse; // matches the .x path; lit by scene ambient even w/o direct lights
object->mDrawOps[i].alphaTest = object->mIsShadow != 0; // shadow -> blend pass
// shadow -> blend pass; vertex-alpha effect cards (flame fans) too.
// SKY objects are excluded: a drawAsSky op with alphaTest set passes
// NEITHER pass filter (sky pass rejects alphaTest, blend pass rejects
// drawAsSky) and vanishes -- DECLOUDS carries vertex alpha but must
// stay in the sky pass.
const bool vtxAlphaOp = data.batches[i].vertexAlpha && !isSkyObj;
object->mDrawOps[i].alphaTest = (object->mIsShadow != 0) || vtxAlphaOp;
object->mDrawOps[i].vertexAlphaBlend = vtxAlphaOp;
object->mDrawOps[i].drawAsDecal = false;
object->mDrawOps[i].drawAsSky = isSkyObj; // sky dome -> PASS_SKY (fullbright)
// PUNCH (dpl_Punchize; BT_PUNCH=0 disables): cutout batch -- black texels
@@ -435,7 +442,12 @@ d3d_OBJECT* d3d_OBJECT::LoadObjectBGF(LPDIRECT3DDEVICE9 device, char *fileName)
object->mDrawOps[i].texture.texture = NULL;
object->mDrawOps[i].texture.wrap_u = L4TEXOP::REPEAT;
object->mDrawOps[i].texture.wrap_v = L4TEXOP::REPEAT;
object->mDrawOps[i].texture.doScroll = false;
// Authored TEXTURE SCROLL (BMF SPECIAL " SCROLL u0 v0 du dv"): the
// firesmoke family (flamebig/fire5 flame cards) rolls its fire noise.
// SetTextureScrolling in the draw path honours these per-op.
object->mDrawOps[i].texture.doScroll = data.batches[i].texScroll;
object->mDrawOps[i].texture.scrollUDelta = data.batches[i].texScrollU;
object->mDrawOps[i].texture.scrollVDelta = data.batches[i].texScrollV;
const std::string &tp = data.batches[i].texPath;
if (!tp.empty())
@@ -863,6 +875,15 @@ void d3d_OBJECT::SetCameraPosition(float x, float y, float z)
gCamPos.x = x; gCamPos.y = y; gCamPos.z = z;
}
// The camera's world position (set once per frame above) -- the wreck flame
// billboard (dpl_SetDCSReorientAxes analog) yaws its card toward this.
void d3d_OBJECT::GetCameraPosition(float *x, float *y, float *z)
{
if (x) *x = gCamPos.x;
if (y) *y = gCamPos.y;
if (z) *z = gCamPos.z;
}
void d3d_OBJECT::SetCullFrustum(const D3DXMATRIX *viewProj)
{
if (viewProj == NULL) { gCullValid = 0; return; }
@@ -1203,6 +1224,34 @@ void d3d_OBJECT::DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time target
// 3. re-draw the mask writing stencil 0 (clean up for other punch groups)
// The stencil never touches z, so entities drawn later still show through
// the apertures correctly.
// VERTEX-ALPHA effect card (the FLAMEBIG flame fan): unlit -- the
// authored per-vertex gradient (white-hot base -> dark faded tip) IS
// the shading -- colour = texture x gradient, alpha = the vertex fade
// (SRCALPHA/INVSRCALPHA). Runs only in the blend pass (the op filter
// above routed it there; ALPHABLEND is on and z-write off pass-wide).
DWORD sVLight = 0, sVSrc = 0, sVDst = 0, sVCop = 0, sVCa1 = 0, sVCa2 = 0, sVAop = 0, sVAa2 = 0;
const bool vtxAlphaCard = (drawOp->vertexAlphaBlend && pass == PASS_ALPHABLEND);
if (vtxAlphaCard)
{
mDevice->GetRenderState(D3DRS_LIGHTING, &sVLight);
mDevice->GetRenderState(D3DRS_SRCBLEND, &sVSrc);
mDevice->GetRenderState(D3DRS_DESTBLEND, &sVDst);
mDevice->GetTextureStageState(0, D3DTSS_COLOROP, &sVCop);
mDevice->GetTextureStageState(0, D3DTSS_COLORARG1, &sVCa1);
mDevice->GetTextureStageState(0, D3DTSS_COLORARG2, &sVCa2);
mDevice->GetTextureStageState(0, D3DTSS_ALPHAOP, &sVAop);
mDevice->GetTextureStageState(0, D3DTSS_ALPHAARG2, &sVAa2);
mDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
mDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
mDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
mDevice->SetTextureStageState(0, D3DTSS_COLOROP,
drawOp->texture.texture != NULL ? D3DTOP_MODULATE : D3DTOP_SELECTARG2);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
}
const bool copCut = (drawOp->copRole == 2 && iOp > 0
&& mDrawOps[iOp - 1].copRole == 1
&& mDrawOps[iOp - 1].bgfPrimCount > 0
@@ -1266,6 +1315,17 @@ void d3d_OBJECT::DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time target
mDevice->SetRenderState(D3DRS_ALPHAREF, sARef);
mDevice->SetRenderState(D3DRS_ALPHAFUNC, sAFunc);
}
if (vtxAlphaCard)
{
mDevice->SetRenderState(D3DRS_LIGHTING, sVLight);
mDevice->SetRenderState(D3DRS_SRCBLEND, sVSrc);
mDevice->SetRenderState(D3DRS_DESTBLEND, sVDst);
mDevice->SetTextureStageState(0, D3DTSS_COLOROP, sVCop);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG1, sVCa1);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG2, sVCa2);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, sVAop);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, sVAa2);
}
if (drawOp->lodDepthBias != 0.0f)
mDevice->SetRenderState(D3DRS_DEPTHBIAS, sDB);
}