Human playtest (2026-07-19): ramming a truck makes it disappear entirely -- no rubble, no smoke/fire, no visible explosion. The [cultvis] log evidence verified the STATE MACHINE (pointer swaps), not pixels: the rubble child component (DCSInstanceRenderable/DPLStaticChildRenderable) is itself 2007-stubbed and never draws, and the routed psfx 1008 produces nothing visible. rendering.md section corrected from 'verified live' to PARTIAL with the three remaining gaps (rubble draw, visible explosion, burning fire). Gitea #3 reinstated as open/wip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
23 KiB
id, title, status, source_sections, related_topics, key_terms, open_questions
| id | title | status | source_sections | related_topics | key_terms | open_questions | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| rendering | Rendering — the D3D9 pipeline, materials, sky, beams, performance | established | PROGRESS_LOG.md §10 (render-fidelity notes); docs/BGF_FORMAT.md |
|
|
|
Rendering
The port renders via the WinTesla L4D3D/L4VIDEO Direct3D9 path (wintesla-port), carrying our
BT loader (bgfload.cpp) + fidelity work. The goal: match the authentic pod footage. Geometry/LOD
mechanics are in bgf-format; this topic covers the D3D9 draw path + the fidelity fixes. Full
detail: docs/PROGRESS_LOG.md §10.
The IG-board shading model (from libDPL DPLTYPES.H)
Shading is selected PER-GEOMETRY by vertex type: no-normal geometry (terrain/mesas/sky/buildings/
mech — WHITE baked verts) is UNLIT, colorized by the material's 2-endpoint material-ramp
(dpl_SetMaterialRamp — texture luminance indexes a low→high gradient); normal-bearing geometry
(~150 vehicle/missile files) is LIT by the map light. Applying the ramp to lit geometry = the "dusty
white blobs" bug (gate on hasNormals). [T2]
Exception: cockpit-frame (*_cop) batches are pure-emissive constant colour (0.13,0.12,0.15),
BT_COP_FRAME overrides — see cockpit-view (task #55; the old BT_COP_RAMP_L ramp-index
approach is retired). [T2]
VERTEX-ALPHA EFFECT CARDS (2026-07-12) [T1 corpus-swept]: exactly 14 shipped BGFs carry
per-vertex float RGBA with alpha < 1 (tags 0x0082/0x008A, RGBA at +12, values OUTSIDE [0,1] —
clamp): FLAMEBIG/FIRE5 (flames), MUZFLASH, EXDISK_A/B/C, TMST_A/B/C, BLUEBEAM/GREEBEAM/RED_BEAM/
PPC, DECLOUDS — the EFFECT-CARD family. Their authored gradient (white-hot base → dark tip fading
to alpha −0.2) IS the shading: drawn opaque with a flat batch colour they render as solid shapes
(the wreck flame read as a "twisted drill bit"). The loader now keeps the authored per-vertex
RGBA for any batch with vertex alpha < 1 (BgfDrawBatch.vertexAlpha) and L4D3D routes it to the
ALPHA-BLEND pass, unlit, colour = texture × gradient, alpha = vertex fade (SRCALPHA/INVSRCALPHA).
SKY objects are excluded from the routing (drawAsSky + alphaTest passes NEITHER pass filter —
DECLOUDS must stay in the sky pass). Authored TEXTURE SCROLL (BMF TEXTURE tag 0x2037
" SCROLL u0 v0 du dv", e.g. firesmoke1 = 0.05,−0.331 uv/s) is now parsed and propagated to
L4TEXOP::doScroll — scrolling materials (firesmoke flame cards) animate in the model path. [T2]
The big render-fidelity fixes (dbase/desert matched to pod footage)
- Geometry: CONN is a flat trilist (not fan); CONN+PCONN coexist — see bgf-format. Malformed fan-garbage was the "faceted mountains" bug. [T2]
- RAMP colorize (default-on
BT_RAMP) + day/night material priority (default-onBT_MATPRIfor the ramps): themat\daylibs carry the warm ramps; the generic GEO libs are gray. Terrain/mesas render warm tan; the sky'sdsky_mtlramp (0→0.99) gives bright clouds.hasNormalsgate keeps vehicles LIT (colored trucks). Ramp tint rule: explicit diffuse + NEUTRAL ramp ⇒ tint; coloured ramp ⇒ white. [T2] - Sky: a
*sky.bgfdome/plane drawn in a dedicated fullbright PASS_SKY (LIGHTING off, extended far=9000 + world fog); taggeddrawAsSkyat load (the.xsidecar path doesn't exist). [T2] - The env pipeline (
DPLReadEnvironment→DPLReadINIPage, readscontent/BTDPL.INIviaL4DPLCFG): resolves map+time → clip/fog/ambient + a directional sun. Bring-up stubs (EnsureValidProjection / per-frame ambient / uninit cloud colours) had clobbered it — now they RE-ASSERT the env values. Debug: copy BTDPL.INI → *DBG.INI withdebug=True. [T2] - Fog — per map/time/WEATHER (task #63).
BTDPL.INIauthorsfog=<near> <far> <r> <g> <b>(+ oftennosearchlightfog=) on EVERY map/time/weather leaf. WEATHER is a fixed authored egg field (clear/fog/soup,mission->GetMissionWeather(); missing = fatal in the arcade); the env pipeline branches location→time→weather to the leaf. Shipped eggs pincavern/night/clear→ pagedsnitclear(near 90, far 1100, dark blue 0.1,0.1,0.12);fog→dsnitfog(500),soup→dsnitsoup(300). The arcade fog model is per-PIXEL linear (dpl_fog_type_pixel_lin, dpl.h:265). It rendered INVISIBLE in the port:D3DRS_FOGTABLEMODE=D3DFOG_LINEAR(table fog) reads the perspective-NONLINEAR z-buffer, so without WFOG the authored 90..1100 range collapsed to fog-factor≈1 (no fog). FIX (L4VIDEO.cpp world pass ~8194): auto-detectD3DPRASTERCAPS_WFOG→ per-pixel W-fog (smooth, == the arcade); else VERTEX fog (eye-space but per-vertex → splotchy on coarse terrain tris); FOGENABLE + mode re-asserted each world-pass frame (the old one-time set got clobbered per-pass). Task #20's "shipped maps define no fog" was WRONG — it checked.MAP/.RES, but fog lives in BTDPL.INI (the DPL env INI), as in the arcade. Night fog is dark BY DESIGN (distance→murk, not light haze); day pages author light haze. Env:BT_FOGMODE=table|vertex|off,BT_WEATHER=clear|fog|soup,BT_FOG="near far r g b"(all branches);[fog] resolved/[fog] model/[fogstyle](BT_FOG_LOG) log lines. [T1 pipeline / T0 arcade model / T2 fix] ⚠ SEARCHLIGHT SWAP IS INERT — and it was inert in the ARCADE too (task #63 workflow, 2026-07-13). The arcade swaps the active fog betweenfog=(headlight ON) andnosearchlightfog=(OFF, darker/tighter) viaPullFogRenderable::Execute(L4VIDRND.cpp:4601, edge-triggered on two light attrs →SetFogStyle(searchLightOn/OffFogStyle)). TWO independent reasons it never fires: (1) Port:PullFogRenderableis NEVER CONSTRUCTED (grep: only its .h decl + .cpp def; nonew);searchLightOn/Offis called from nowhere else (BT_FOG_LOG: over 22s onlySetFogStyle(0), zero 2/3). (2) Original 1995 LATENT BUG [T1, decomp]: even if wired, the Searchlight never lights —ToggleLamp(@004b860c) togglescommandedOn@0x1DC (part_013.c:6087) butSearchlightSimulation(@004b841c) readsrequestedOn@0x1E0 (part_013.c:5978), which the ctor zeroes and NOTHING ever writes; no bridge 0x1DC→0x1E0 exists anywhere in the binary. SolightState@0x1D8 is perpetually 0. The self-consistent sibling ThermalSight reads the SAME field it toggles (0x1DC) — the tell that Searchlight's sim reads the wrong slot. The port reproduces this faithfully (searchlight.cpp:189 reads requestedOn). DECISION (2026-07-13): left as-is + documented (faithful to the buggy original); the port keeps the static lights-ONfog=values. To make the swap WORK is a DESIGNER-INTENT fix that DEVIATES from the shipped binary — repair the sim to readcommandedOn(mirror ThermalSight) + constructPullFogRenderableat btl4vid.cppMakeMechRenderables(the reticle-build/inside pass, == arcade part_014.c:5173, ExecutionType Dynamic, bound to each SearchlightlightState) + a toggle input. See open-questions. Core per-map/time/weather fog is unaffected. - Projection / stencil (task #55): the BTDPL.INI
viewangle=60is the HORIZONTAL FOV — authentic 60×47 frustum at 4:3; fovY derived viaBTFovYFromHorizontal(L4VIDEO.cpp), applied at all 5 projection sites (it was mis-applied as vertical). The device depth-stencil is nowD3DFMT_D24S8for the cockpit punch stencil-cut (L4VIDEO.cpp:2799; sequence L4D3D.cpp:1199-1248) — see cockpit-view. [T2]
Backface culling (default CW, BT_CULL)
The bring-up D3DCULL_NONE drew interior/back faces (the "dark wedge" shapes = mesh insides). CW is
correct for the BGF quad triangulation [a b c][a c d]. [T2] Exception: cockpit *_cop batches
default DOUBLE-SIDED (BT_COP_SINGLE=1 restores single-siding, diag) — see cockpit-view. [T2]
LODs (default: shipping-engine first-LOD + ADDITIVE_LODS; BT_ADDLOD/BT_LOD_SCALE/BT_PUNCH)
See bgf-format for the √3 range decode, the ADDITIVE_LODS cumulative rule, PUNCH cutouts, and the coplanar-shell submission-order depth bias. Replacement LOD selection for NON-additive multi-LOD models is deferred (needs the board's hot-spot semantics). [T2]
Mech skins (BSL bit-slice — the rainbow-mech fix)
.BSL is a bit-sliced container (6 grayscale sub-images per file); the BMF 0x18 BITSLICE tag picks
the slice. Reading it as RGB overlaid 2-3 gray sheets = the "rainbow/graffiti" bug. Fixed:
image.cpp decodeBSL slice-decodes; bgfload threads the 0x18 channel; a cross-library RAMP registry
resolves the mech-skin softer ramp (defined in OTHER libs). Every mech renders the authentic BASE
gray-metal skin now. See asset-formats. [T2]
Per-pilot mech PAINT (color/badge/patch substitution) — NOT WIRED (2026-07 audit)
The "missing warning stripes / coloration" report is the per-pilot paint layer, fully authored but never engaged by the port. Mechanics (all [T1] from the BMF/BGF/vehicletable content + FUN_004d0cc0):
- Mech BGFs reference the BASE lib only (
blhskin:gen2_dz_rfoot_mtl…). The binary rewrites lib prefixes at load via thevehicletableresource[substitute]templates:skin:→_%color%%serno%:(blhskin:→blh_wht0:) andskin:lgo→<<<emblem%serno%:%badge%_%patch%>(emblem pages →EMBLEM0.BMFlgoN_<patch>_dz_*materials). Pilot egg values (color/badge/patch) map through the[color]/[badge]/[patch]sections to the codes. - The COLOR libs (e.g.
BLH_WHT0.BMF) are byte-identical to the base lib EXCEPT they add tags0x23(diffuse RGB) +0x24(ambient RGB) = the pilot color to the ~35 paintablegen*/mech*_dz_*materials (base leaves them untinted). 7 colors: wht(.99,.99,.99) red(.90,.11,.10) grn(.63,.83,.52) gry(.65,.60,.65) brn(.60,.44,.28) tan(.65,.55,.43) blk(0,0,0). All 8 sernos are byte-identical (verified: 0 of 193 groups differ) —%serno%was future variety, never used.<mech>SKIN.BMF(bare) ==<mech>SKIN0.BMF. - WARNING STRIPES are grayscale hazard-stripe art in the shared
GEN.BSLpages (slice 1 =gen2_tex, top-left), shown through the paintable-zone materials and tinted by the pilot color.EMBLEM0.BMFcarrieslgoN_<patchcolor>_dz_ld/rdlegmaterials (8 patch tints incl. yellow .89,.73,0) overLGO.BSLslices 0-5 (slice 5 = VGL winged logo). - WIRED 2026-07-17 (same-day fix), verified live: MP MadCat renders crimson with hip hazard
stripes + yellow VGL leg emblems; Blackhawk white + emblems; replicants painted on both nodes.
The reconstruction: (a)
Mech::resourceNameA/B/Care realCStringmembers (the binary's exact 16-byteCStringRepresentation{size,len,text@+8,refs@+0xc}; ctor = part_012.c:9841-71, deep-copy bind from the MakeMessage =CString::operator=(const char*)== FUN_00402a98 @10427-41, implicit member dtors = 11388-403) with paint-name accessors; (b)SetupMaterialSubstitutionListreads them (real egg names;[paint]log line); (c)dpl_SetMaterialNameCallbackis REAL now (L4VIDEO.cpp registry, cleared first in TearDown as FUN_004d11e8 does) andbgfload MaterialResolver::resolve()runs every name throughdpl_ApplyMaterialNameCallback; no mesh-cache issue (LoadObject/LoadBgfFile don't cache — every load resolves fresh). [T2] - DEVIATION (documented, btl4vid.cpp): on a vehicletable miss the binary Fail()ed ("Exiting"
@BTL4VID.CPP:0xbeb); the port warns + drops that placeholder (RP analogue). Real case: dev eggs
ship
color=Redbut the[color]table only has Crimson — MP_BHMC.EGG fixed to Crimson; LAST.EGG pilot-2 still says Red (unused slot). The 7 valid egg colors: Black/Brown/Crimson/Green/Grey/Tan/White; badges Davion/Kurita/Liao/Marik/None/Steiner/VGL; patches Black/Blue/Green/Grey/Red/Violet/White/Yellow. [T1]
The OLD-STYLE SPECIALFX layer (the weapon-impact action) — RECONSTRUCTED 2026-07-12 (firmware-decoded)
The per-weapon impact visuals are fully AUTHORED in content/BTDPL.INI [effects_to_load]:
specialfx3..15 = Chunks1/Chunks2/Sparks/Explosion3/MachineGun/PPCHit/LaserHit/MissileHit/
CannonHit/GroundHit/MinorHit/MajordHit/Fireball — "old-style" (version-1) descriptor sections
(texture/type/size/velocity/v_bias/y_off/cook(3f)/varience/gravity/cool(2f)/count/repeats),
most on btfx:firesmoke1_scr_tex (MissileHit size 12 ×7; Fireball size 14, gravity −1.5 rises;
Chunks1/2 = ballistic debris vel 40-55 grav 34-35; Sparks NOTEX). [T1: the 1995 binary parses
them — part_008.c:1546-1590, field strings @004f25e4-004f2663 → installer FUN_00490210 packs a
0x44-byte board command via FUN_0048e2c0.] Simulation semantics (type/cook/cool) live in the
i860 firmware content/VREND.MNG (same decode workflow as the task-#55 'damageize' handler).
- EFFECT-NUMBER ROUTING (corrects the old claim):
effect_number < 100= the dpl board EFFECT TABLE = this specialfx list (1996 case-4 comments match: 3=Chunks, 12=GroundHit, 15=Fireball), NOT a psfx slot;>= 1000= psfx slot n−1000 (idam/ddam PFX); 100-999 = ExplosionScripts. The port's<100 → psfx slotmapping renders PPCHit(8) as ddam5 smoke and Chunks(3)/Sparks(5) as ddamN smoke — the "hits are just a little smoke" report. The 2007 WinTesla loader SKIPS all version-1 descriptors (if (version < 2) continue;— it only reads the 1998 INDIE format), which is why the layer was never seen. [T1] - Live hit census (mp logs): weapon hits dispatch idam bands 1009-1013 (30×1009) + raw 8/9 (PPCHit/LaserHit, mis-rendered); MissileHit(10)/CannonHit(11) never appeared — the projectile/salvo damage path likely doesn't thread the firing launcher's subsystemID into the damage records, so messmgr never queues its explosion. [T2 census; T4 cause]
- RECONSTRUCTION (2026-07-12, firmware-first): the i860 sim decoded from VREND.MNG
(dispatch table at data+0xdd0c, VA base: code 0xf0400000, data 0x1000; handlers: sfx trigger
0xf040cda0→ca48, install 0xf040cdc0→cba0, step near 0xf0413698, spawn near 0xf0414afc —
string-anchored via "attempt to step a NULL sfx object"; disassembler REBUILT at
scratchpad/i860dis.pyfrom binutils i860 opcodes,scratchpad/i860_opcodes.h). The heat model [T1]: per 30Hz board frameh *= cool_a;RGB_ch = h_old*(h_new*cook_ch − 0.25) + 0.25(K=0.25 @VA 0xF080 = the ember floor); kill at h ≤ 1e-4 (@0xF0A0); alpha −= cool_b per frame;pos += vel, gravity as accel. y_off = the particle KILL PLANE (Chunks −1 = die at the ground) [T3 data-indicated]. Burst frame randomly spun (quadratic small-angle rot, θ from the host-fed random table cmd 0x21). Implemented in L4VIDEO.cpp:BTInstallSfx/BTStartSfx+ sfxMode particles in the BTPfx layer; parser beside the psfx page walk ([sfx] page 'effects_to_load': 13 old-style specialfx installed). Effect-number routing fixed in BOTH consumers (ExplosionClassID + DPLIndependantEffect): <100 → specialfx, ≥1000 → psfx slot n−1000. Missile/cannon hits now bundle [T2]: BTProjectile carries shooter + weaponSubsys; contact routes through the SHOOTER's SubsystemMessageManager (AddDamageMessage w/ subsystemID → mslhit/acanhit explosion at the impact), mirroring the laser path's SendDamageMessage. Cone shape (v_bias as up-bias) + per-type draw weights are [T3]; the full type-0/1/2 draw-geometry decode in the firmware draw fn (0xf0416160) remains open.
Weapon-hit forensics closeout (2026-07-12, workflow-verified) [T1]
- The HIT PACKAGES (BTL4.RES model lists, byte-verified): ppchit=[8], lzrhit=[9], mghit=[7,11], canhit=[11] (no "acanhit"), mslhit=[10, 12, 1023] (MissileHit + GroundHit + psfx23 SHKWAVE), gndhit=[12], minhit=[1002], majhit=[1003], explode=[6]; dam1-4hit=[1002-1005], desthit/stephit=[1008]; cockpit "i" variants (lzrhiti etc.) = same sfx + idam 1009-1013; ALL eight mech death lists = ["104","3","4","5","1007","15","1001"].
- "Silver mist exactly 5×, perfectly timed" ROOT CAUSE: SHKWAVE.PFX authors maxIssue=5, releasePeriod=0.2, rate=1 — the ONE shipped .PFX where rate ≠ maxIssue/releasePeriod; the emitter trusted 'rate' → 5 huge white shells (exp 350/s) at exactly 1Hz. Emission-window rule hardened: effective rate = maxIssue/releasePeriod always (BTDrawPfx emitter).
- SpecialFX palette [T1 firmware]: instance init @0xf04128d8: inst[0x19c]=cool_a (per board-frame heat decay), inst[0x1a4]=cool_b (× raw dt in the step = PER-SECOND fade), inst[0x188]=7.0s cap, heat=1.0. The heat channels are BRIGHTNESS over an inherently FIRE-coloured texture (firesmoke through its material ramp): sfx batch draws the FIERY-ramped sheet, psfx keeps GRAYSCALE (authored colours decide — DNBOOM orange, DDAM gray). Drawing sfx grayscale = silver mist; scaling fade ×30 = 0.1s pops ("nothing happens on laser hits").
- Damage-band .PFX shapes are authored GENTLE (puffy clouds): bands 1-4 of ndam/idam/ddam rise at 1-4 u/s with buoyant accel; ONLY band 5 + the BOOM/SHKWAVE files are explosive-omni. idam = scaled-down WORLD venting smoke (half radius/counts of ddam), not cockpit-only. N-set = night = same physics as D-set with fewer particles. NSRM/DSRM trails = 0.1s overbright ORANGE directional puffs (never silver).
- messmgr explosion DEDUP CONFIRMED [T1, decomp-read @0049b784]:
Find(weaponExplosions vtable slot 0xC) gatesAddValue(slot 8) — ONE explosion per unique weapon resource per frame, exactly as reconstructed. (A workflow agent claimed "one per record, staggered 0.1s" — REFUTED; the stagger applies to the deduped queue.) Missile multiplicity comes from the ORDNANCE side instead: Missile::MoveAndCollide @004bef78 detonates per round — ported as BTSpawnRoundDetonation (mech4) + rack-tube launch spread [T3]. Alpha-strike drama = distinct per-weapon-TYPE packages (lzrhit+ppchit) + the victim's band fire, not explosion count. - Remaining [T3]: sfx cone shape (v_bias as up-bias), size ×0.5 + 1.25m textured floor + hot-phase occlusion 0.45..0.9, burst scatter ×0.2; trail density is frame-rate-dependent (2/frame @60fps = 2× pod density); .PFX 'repeats' unconsumed (all shipped files author 1); replicant visual salvo chases the stale fire-time aim point (no re-lead without a target handle).
Weapon beams (the unported dpl_* layer)
The dpl_* beam renderable was never ported → beams are drawn by BTPushBeam/BTDrawBeams
(L4VIDEO). The real look is the ermlaser.bgf TUBE (native Z 0..−2000, UVs tiled) with the scrolling
bexp grit texture (from the .VMF SCROLL). ER lasers red, PPC blue, standard yellow. The beam
origin re-anchors to the live muzzle every frame (can't run out from under your own beam). See
combat-damage for fire-rate/muzzle. [T2]
Translocation warp (the respawn vortex) — see translocation-warp
The death/respawn "blue whirlwind" is tsphere.bgf (a 12-facet bicone) spun on-axis with the bintA
cloud through a wide lavender ramp, drawn as SKY. Full geometry/material/lifecycle/env in
translocation-warp. ⚠ Rendering-wide fix that came out of it: L4D3D::SetTextureScrolling
computed its texture-matrix offset as scrollDelta × absolute_time, which grew unbounded and collapsed
UV float precision → grainy/spoked scrolled textures the longer a session ran. Wrapped with fmodf(…, 1.0f). This also cleans up the scrolling bexp beam grit and any other SCROLL material. [T2]
(Bug class: reconstruction-gotchas §13.)
Performance — the 10fps→60fps fix (~50-80× render speedup)
The 10fps baseline had THREE stacked causes, all fixed (engine tree): (1) D3DCREATE_SOFTWARE_ VERTEXPROCESSING — every vertex transformed+lit on the CPU → HARDWARE first; (2) D3DX DrawSubset
attribute-table scan (our double-sided tris broke OptimizeInplace) → explicit SetAttributeTable +
direct DrawIndexedPrimitive; (3) no frustum culling → bounding-sphere culling per frame. ⚠ LESSON:
the user plays the DEBUG build, but the 10fps was NOT the debug build (RelWithDebInfo identical) —
split draw-vs-present + check the DEVICE flags before blaming the compiler. Diagnostics kept:
[spike]/[loadobj]/[rstat]. [T2]
Cultural-icon (map prop) DESTRUCTION — revived 2026-07-19 (Gitea issue #3) [T2]
Trucks/props/buildings are CulturalIcon/Landmark entities (engine/MUNGA/cultural.h, map-
streamed via INTEREST.cpp; ARENA1 = 124 icons, each with a damage zone, ExplosionModelFile,
removeOnDeath). The SIM was always complete [T0]: ram/weapon damage → CulturalIcon:: TakeDamageMessageHandler → zone damageLevel ≥ 1 → Explosion entity (Crunch variant for
collision) + WaitingToBurn (+timeDelay) → BurningState + UpdateCollisionVolumes (boxes
DELETED on removeOnDeath, else sunk by destroyedYTranslation). The mech-side crunch dispatch
(mech4.cpp ProcessCollision → BTDispatchCollisionDamage, ×0.001 ram economy) reaches icons
(≈19 walking-speed bumps kill the spawn-yard truck). What was BROKEN (the "no state change"
report): the 2007 port fully stubbed StateInstanceSwitchRenderable ("STUBBED: DPL RB
1/14/07") — the intact→rubble visual swap NEVER fired. Revived D3D9-native: the switch now
controls the draw component via the SetDrawObj in-place drawable swap (the mech
RemakeEntity mechanism); [video] Object hides at BurningState, Rubble shows; watcher =
StateIndicator::AddVideoWatcher (SetState DOES fire video watchers). ⚠ REQUIRED gotcha:
cultural objects must stay OUT of static-mesh consolidation (RecurseStaticObject — same
exclusion class as banded LODs/shadows), else the merged mesh draws the intact model forever
regardless of DrawObj. ⚠ STATUS (human playtest 2026-07-19): PARTIAL — the intact model now
hides on death (the truck "disappears"), but the RUBBLE never appears and no explosion/fire is
visible. The switch's SHOW sets DrawObj on the rubble CHILD component (DCSInstanceRenderable/
DPLStaticChildRenderable) — those child classes are themselves 2007-stubbed and never draw their
object, so the swap is a no-op on screen; and the death Explosion routes effect 1008 (ddam5
psfx) but nothing visible appears (+ the authentic BurningState fire visual is entirely
unreconstructed). REMAINING: make the rubble genuinely draw (or hang it off the drawn root),
a visible death explosion, the burning-fire state. LESSON: [cultvis] log lines verify the
STATE MACHINE, not pixels — only a human screen check verifies rendering. Diags: BT_CULT_LOG
(census/damage/death + [cultvis]). MP: replicants transition via ReadUpdateRecord → same
SetState watcher path (untested cross-pod).
Key Relationships
- Geometry/LOD: bgf-format. Base: wintesla-port (L4D3D). Shadow/visual-conform: locomotion.
- Renders on: pod-hardware (main 3D view).
- Prop destruction feeds: combat-damage (crunch dispatch) · uses the psfx layer.