Files
arcattackandClaude Opus 4.8 82e6bb306a KB: sweep STALE "unfinished" claims -> match landed commits (context-system audit)
4-agent audit cross-checked every open/pending/stub/deferred claim in the context
system against git + code. Fixed ~13 stale spots (currency only, no code change),
each contradicted by its own file body + a landed commit:

- 0xBD3 SubsystemMessageManager (task #7, afefaee): subsystems.md frontmatter+body
  line 57; decomp-reference.md 0xBD3 row + +0x434 row -- all still said
  "MISLABELS controlsMapper / WAVE 8 untangle" (done; mech.hpp names it messageManager).
- Cylinder hit-location STEP 6 (d07ac7d): combat-damage.md frontmatter;
  decomp-reference.md "not reconstructed" line.
- Per-mech turn-rate wiring (task #64b, a83995a): multiplayer.md + locomotion.md
  frontmatter (still "bring-up constant / not yet wired").
- Mech-level update records (task #1, c9f0c2a): open-questions.md "dead Wword
  branches mech.cpp:1511/1613" (those branches are gone; named fields now).
- Collision damage + DeathShutdown (2026-07-08/12): combat-damage.md frontmatter.
- Myomers un-stub (8b36440): decomp-reference.md "INERT un-stub" row.
- Day/night BT_MATPRI (shipped, default-on): rendering.md frontmatter (LOD half kept).
- Fire-trigger path (task #5, 8ed6184): open-questions.md "still gBTWeaponTrigger
  bring-up" (globals retired/never-read).
- HEAT entry FACTUAL fix: "heatLoad -> effectiveRange 0 = overheat cutout" was false
  per aab7a8a -- effectiveRange reads host-zone damage; the real cutout is the
  FailureHeat charge-hold.

Frontmatter open_questions re-pointed at genuinely-open items. VERIFIED NOT stale
(left as-is): multiplayer body-channel-projection notes (a real open [T3] residual
per the coupled-motion section's ~2.9u leg-vs-body snap). Genuinely-open items
(factory roster loops 2-4, subsystem-panel gate, missile cluster, console-death
freeze, gyro tails, etc.) untouched. checkctx CLEAN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:35:41 -05:00

17 KiB
Raw Permalink Blame History

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
bgf-format
asset-formats
wintesla-port
locomotion
pod-hardware
cockpit-view
L4D3D
material-ramp
BSL
LOD
DPL
beam
Replacement-LOD for NON-additive multi-LOD models

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-on BT_MATPRI for the ramps): the mat\day libs carry the warm ramps; the generic GEO libs are gray. Terrain/mesas render warm tan; the sky's dsky_mtl ramp (0→0.99) gives bright clouds. hasNormals gate keeps vehicles LIT (colored trucks). Ramp tint rule: explicit diffuse + NEUTRAL ramp ⇒ tint; coloured ramp ⇒ white. [T2]
  • Sky: a *sky.bgf dome/plane drawn in a dedicated fullbright PASS_SKY (LIGHTING off, extended far=9000 + world fog); tagged drawAsSky at load (the .x sidecar path doesn't exist). [T2]
  • The env pipeline (DPLReadEnvironmentDPLReadINIPage, reads content/BTDPL.INI via L4DPLCFG): 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 with debug=True. [T2]
  • Fog — per map/time/WEATHER (task #63). BTDPL.INI authors fog=<near> <far> <r> <g> <b> (+ often nosearchlightfog=) 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 pin cavern/night/clear → page dsnitclear (near 90, far 1100, dark blue 0.1,0.1,0.12); fogdsnitfog (500), soupdsnitsoup (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-detect D3DPRASTERCAPS_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 between fog= (headlight ON) and nosearchlightfog= (OFF, darker/tighter) via PullFogRenderable::Execute (L4VIDRND.cpp:4601, edge-triggered on two light attrs → SetFogStyle(searchLightOn/OffFogStyle)). TWO independent reasons it never fires: (1) Port: PullFogRenderable is NEVER CONSTRUCTED (grep: only its .h decl + .cpp def; no new); searchLightOn/Off is called from nowhere else (BT_FOG_LOG: over 22s only SetFogStyle(0), zero 2/3). (2) Original 1995 LATENT BUG [T1, decomp]: even if wired, the Searchlight never lights — ToggleLamp (@004b860c) toggles commandedOn @0x1DC (part_013.c:6087) but SearchlightSimulation (@004b841c) reads requestedOn @0x1E0 (part_013.c:5978), which the ctor zeroes and NOTHING ever writes; no bridge 0x1DC→0x1E0 exists anywhere in the binary. So lightState@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-ON fog= values. To make the swap WORK is a DESIGNER-INTENT fix that DEVIATES from the shipped binary — repair the sim to read commandedOn (mirror ThermalSight) + construct PullFogRenderable at btl4vid.cpp MakeMechRenderables (the reticle-build/inside pass, == arcade part_014.c:5173, ExecutionType Dynamic, bound to each Searchlight lightState) + a toggle input. See open-questions. Core per-map/time/weather fog is unaffected.
  • Projection / stencil (task #55): the BTDPL.INI viewangle=60 is the HORIZONTAL FOV — authentic 60×47 frustum at 4:3; fovY derived via BTFovYFromHorizontal (L4VIDEO.cpp), applied at all 5 projection sites (it was mis-applied as vertical). The device depth-stencil is now D3DFMT_D24S8 for 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 authentic gray-metal paint now. See asset-formats. [T2]

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 n1000 (idam/ddam PFX); 100-999 = ExplosionScripts. The port's <100 → psfx slot mapping 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.py from binutils i860 opcodes, scratchpad/i860_opcodes.h). The heat model [T1]: per 30Hz board frame h *= 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 n1000. 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) gates AddValue (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]

Key Relationships