7b0b4f64ab58f5172b2ddcc7074d15008e9e4e10
32
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7b0b4f64ab |
Diag: add BT_SHOT backbuffer dump + correct/park the -net render glitch (task #53)
Scouted the "-net node render glitch". Two prior framings were wrong and are corrected in the KB: it is NOT device contention (retracted earlier), and it is NOT a from-load glitch -- the trigger is a RESPAWN (user correction). It's a post-respawn render-rebuild fault on the MP path. Could not reproduce in 9 automated 2-node loads + a forced death/respawn (all clean). Debug uninit is a deterministic 0xCDCDCDCD, so the intermittency implies a race -- and the task #52 respawn fixes (LoadMission re-entry, viewpoint relink, death-latch) plausibly already closed it. Parked as WATCH; re-open if it recurs. Tooling (general, off by default): BT_SHOT=<path> dumps a per-instance backbuffer PNG from the world-render path (L4VIDEO.cpp) every 90 frames -- lets us capture a specific node's frame without foregrounding the window. Used with scratchpad/hunt53.sh (N-run divergence hunter) to verify a fix if the glitch returns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0bfb3d4ab3 |
Warp: restore the translocation-vortex look + fix texture-scroll precision collapse (task #52)
The death/respawn "blue whirlwind" (tsphere) now matches the original cabinet
photo (capture.png): a smooth spinning lavender vortex with a bright core.
Root cause of the long-standing "radial spokes" artifact was NOT the warp code
but a general engine bug: L4D3D::SetTextureScrolling computed its texture-matrix
offset as scrollDelta * absolute_time, which grows unbounded and collapses UV
float precision -> a smooth scrolled cloud shatters into grainy radial steps.
Wrapped with fmodf(..., 1.0f) (identical under REPEAT tiling, full precision).
This also cleans the scrolling bexp beam grit and any other SCROLL material.
Visual reconstruction (verified against the real 45-vtx TSPHERE.BGF bicone,
offline-rasterized then ported):
- view ON-AXIS (eye centred on the throat) + spin in place -> concentric rings
(decomp FUN_00453dc4 does spin-about-local-Z + submit; the port had stubbed it)
- bintA cloud through a WIDE lavender ramp at full contrast (drawn as SKY);
no geometry "bands", no log-polar twist, no off-axis tornado (all discarded)
- tessellate the 12-facet bicone smooth; isotropic + trilinear; ramp baked into
the texture and drawn SELECTARG1(TEXTURE) to avoid double-tinting
Env knobs (BT_WARP_*) default to the verified values; BT_WARP_SELFTEST/SELFSHOT
are an off-by-default visual-verification harness (backbuffer frame dump).
Docs: new context/translocation-warp.md (geometry/material/visual/lifecycle/env);
reconstruction-gotchas.md gains the accumulated-time precision-collapse bug class;
rendering.md / multiplayer.md / decomp-reference.md / CLAUDE.md cross-linked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
a35f321ba1 |
Warp: exact POVTranslocateRenderable replica (collapse/throb/reveal + world mask)
Stop approximating -- replicate POVTranslocateRenderable::Execute() behaviorally,
byte-for-byte on the constants and transitions (engine T0, L4VIDRND.cpp:1763-2076),
per the user's "replicate the effect exactly". The five prior piecemeal attempts
each fixed one facet and broke another because they left out the WHOLE mechanism.
Full state machine (btl4vid.cpp), driven by two decoupled events (NOT the
SimulationState trigger -- that dial also drives camera/POV+targeting, the
|
||
|
|
a9d1534292 |
Warp: faithful POVTranslocateRenderable -- eye-centered, ramp color, no spin
Deep-dived the authentic engine effect (L4VIDRND.cpp POVTranslocateRenderable, T0)
and reworked our one-shot to match it, fixing the user's "blob from my own POV /
not aligned to my orientation" + "gray, not blue" reports:
1. PLACEMENT was the misalignment. The engine parents the sphere to the VTV/eye
("rotated and scaled around the VTV", L4VIDRND.cpp:1812) -- it is centered on and
oriented to the viewpoint. We world-fixed it at the mech's feet, so from your own
camera it sat off-axis and, once expanded, engulfed the eye as a shapeless blob.
Now: the own-respawn warp (BTStartWarpEffectPOV, from btplayer) uses
world = Scale(s) * inverse(view) -> centered on + carried by your eye (the tunnel
wraps you). Observing a PEER (BTStartWarpEffect from mechdmg) stays world-anchored
at the peer's point.
2. GEOMETRY SPIN was inauthentic. myRotateY/myRotateYSpeed are set in the engine
ctor but NEVER applied -- the swirl's rotation is entirely the material's texture
SCROLL (bintA, SCROLL 0.0 0.0 0.1 0.5). Dropped our world-Y geometry spin.
3. COLOR (gray) fixed at the source. The blue/white is the "sky" RAMP remapping the
grayscale bintA cloud per-texel (argb=lerp(0,0,0.6 -> .99,.99,.99, luminance),
L4D3D.cpp:480). The port bakes exactly this but GATED it off for normal-bearing
meshes; tsphere has normals so it arrived raw gray, and a MODULATE tint can't add
the blue floor (it only multiplies) -> the wash-out. Un-gate the ramp for
tsphere_mtl (bgfload.cpp) so the bound texture is already the blue-white swirl;
draw it MODULATE by white (no tint), alpha from TFACTOR.
4. SCALE to authentic 100 (collapse) / 150 (expand end) -- correct now that it's
eye-centered (you're meant to be inside it as it collapses through you and blasts
back open, the "blast off into the distance").
Still deferred (higher risk / SimulationState-coupling trap): the WaitForReincarnate
Lissajous wobble + the SetIsDead world-mask (black-out during the dead hold). Noted
for follow-up; the placement/color/motion are the parts the user flagged.
Smoke-verified 2-node: own respawn logs POV(eye), observer logs world-anchored, no
crash. BT_WARP_ADDITIVE / BT_WARP_COLOR / BT_WARP_SCALE remain as live tuning knobs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
63c1c5a460 |
Implement the translocation sphere -- the "blue warp" respawn effect (task #52)
BTTranslocationRenderable was a no-op stub; now reconstructed over the engine's POVTranslocateRenderable: loads tsphere.bgf and runs a collapse-on-arrival / expand-on-reveal sphere (scale 100->1 over 1.3s, then 1->150 over 1.0s, rotating) keyed on the player's SimulationState dial. Drawn direct from the render loop by BTDrawTranslocationSpheres (beside BTDrawBeams, PASS_ALPHABLEND) -- the same accommodation the beams/reticle use. The asset loads by FILENAME (tsphere.bgf), not through the RES table -- which is why every resource-name search missed the effect for three rounds. Trigger wiring: btl4vid.cpp MakeEntityRenderables builds the sphere for the LOCAL player (the authentic wiring builds it only for replicants + a POV fade for self, but peer player-attribute replication isn't wired -- on a replicant SimulationState/ DropZoneLocation read uninitialised). btplayer.cpp pulses SimulationState DropZoneAcquired->VehicleTranslocated at respawn (a 1.4s flip timer stands in for the engine's +1s drop-zone re-post) and writes the respawn origin into the DropZoneLocation attribute. Verified 2-node (BT_TLOC_LOG): on respawn the sphere collapses (~100->5) then expands (->150) at the valid drop-zone origin, deduped to <=2 active, respawn cycle un-regressed, no render errors. Visual appearance (colour/size) still needs a live look; the authentic see-others'-spheres path needs player replication. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
83b3f31957 |
Whirlwind respawn: authentic death->drop-zone->recreate cycle (task #52)
Death transition (mech4) now dispatches VehicleDead(-1) to the owning player; BTPlayer::VehicleDeadMessageHandler restructured to the authentic @004c05c4 three-way branch: -1 = death bookkeeping + sever playerVehicle (wreck stays) + 5s re-post; >=0 = engine drop-zone hunt -> DropZoneReply -> CreatePlayerVehicle (new mech); -2 = the acquire probe. Guarded on a DropZones group so a zone-less mission stays dead instead of aborting. Three latent bugs the respawn path exposed, all fixed: - IsMechDestroyed latched on graphicAlarm>=9 alone; a later leg hit on the wreck rewrites the alarm to 4/3, un-latching -> the death transition re-ran (double kill/score, abort). Now latches on movementMode 2||9. - Score handlers dereferenced the severed playerVehicle during the dead window; guarded. - The console score flush routed a NetworkClient::Message through the player's Entity::Dispatch, which stamps entityID past the smaller struct -> /RTC1 stack overflow. Sent via application->SendMessage instead. - Renderer LoadMission re-entry (per viewpoint-make) re-read the env INI; its light block Fail'd on stale sceneLightCount. Reset it in DPLReadEnvironment so the respawn's second read is clean. Verified 2-node self-drive: B killed repeatedly by A respawns each time (wreck stays), ticks + reloads + takes fresh damage on the new mech, no abort/hang across multiple death->respawn cycles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
47aede3094 |
Shadow: tag tshd proxies in the LOADER (V toggle broke the pipeline)
User report: the shadow broke MID-SESSION -- suddenly solid opaque black and buried on any slope. The session log pinned the trigger: pressing V. The view toggle (SetViewInside) RELOADS every segment mesh through the object loader and swaps them into the render tree -- including blh_tshd.bgf -- and the reload path never called SetIsShadow(1) (only the original tree-build call site tagged it). Untagged, the entire shadow pipeline disengages: no translucent TFACTOR state block (solid black), no depth bias (buried), wrong pass. The damage-swap reloads had the same hole. Fix: tag *tshd* filenames in LoadObjectBGF itself, next to the existing all-shadow-material detector (which misses the mech proxy -- plain black material, not shadow_mtl). Every load path -- build, damage swap, view toggle -- now yields a tagged object; the drawOps alphaTest routing reads the flag downstream. User-verified live: the shadow survives repeated V toggles, translucent and slope-correct. This also likely explains the RECURRING "shadow vanishes on inclines" reports: any V toggle silently broke it; every relaunch "fixed" it. KB (locomotion.md): loader-level tag recorded as co-dependent part 5 of the shadow arrangement; the vanish-diagnostic note now lists all three causes (reload untag / gait desync / genuine burial) with check-first guidance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b6fe686b22 |
Shadow: authentic terrain tilt + decal draw order (feet layer correctly)
User report: the ground shadow painted OVER the mech's feet. Root cause: the
flat quad + big depth-bias (-0.004 ~= 2-4 world units) workaround for slope
burial -- a bias big enough to beat the TERRAIN also beat the FEET in the
depth test, and the shadow drew in the late alpha-blend pass (after the mech),
so z-arbitration was the only layering control.
Fix, four co-dependent parts (user-verified live: feet on top, opaque,
survives inclines/declines):
- TILT (mech.cpp UpdateShadowJoint): apply the SKL's own contract for
jointshadow ("apply terrain angle to pitch and roll") -- quad up aligned to
the surface normal via an orthonormal basis fed to the ENGINE's own
LinearMatrix->EulerAngles conversion. Hand-derived Euler signs are exactly
how the prior tilt attempts "dug into the hillside"; the engine conversion
is convention-proof. ~35 deg cliff-guard cap.
- SAMPLER (mech4.cpp): surface gradient from the collision probes PLUS
BTVisualGroundLift per probe -- the quad hugs the VISIBLE terrain, whose
lift varies across a slope; world->local rotation by the TRUE yaw from
localToWorld, not the drift-prone gDriveHeading mirror (the task-#48 bug
class). BT_SHADOW_LOG traces the normals.
- DRAW ORDER (new PASS_SHADOW, l4d3d.h/L4VIDRND/L4VIDEO): shadows draw
between the STATIC terrain and the DYNAMIC opaque bodies -- the classic
decal order. The mech, drawn after, z-passes over the shadow: the bias
can never paint the shadow over the feet, structurally.
- BIAS pairing (L4D3D.cpp): tilt on (default) -> decal epsilon -0.0008;
BT_SHADOW_TILT=0 flat fallback -> the old -0.004. BT_SHADOW_BIAS overrides.
KB (locomotion.md): the four-part arrangement recorded as co-dependent, with
the gait-desync-symptom diagnostic hint retained.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
0dfbb97dcc |
Fix torso-pitch aim ray: gyro-stabilized boresight from the mech torso
The aim/pick ray was the active RENDER EYE's view direction, which made a
mech unable to aim at a distant enemy:
(a) it inherited the mech BODY pitch -- on a slope the body tilts ~8 deg to
conform to terrain, so the boresight pointed that far DOWN and the pick
ray hit the ground ~50-100m short of the target (groundPicks, never
mechPicks); and
(b) in CHASE view (the default) it was the elevated behind-the-shoulder
camera, so the ray started over the mech's shoulder and flew high.
Fix (L4VIDRND DPLEyeRenderable::Execute, the BTSetAimCamera publish):
- LEVEL the boresight: drop the view-direction pitch and rebuild an upright
basis (world +Y up) -- the guns fire along the mech's gyro-stabilized
HORIZONTAL heading, not the terrain-pitched body. Reticle X still carries
the torso twist (BTTwistToReticleX); reticle Y any elevation.
- ANCHOR the ray origin at the mech's TORSO (myEntity->localToWorld
translation + ~5) instead of the render eye, so it is correct in BOTH the
chase (default) and cockpit views.
Verified end-to-end: solo, enemy 120m ahead, BOTH chase and cockpit view ->
[target] "MECH under boresight ... mechPicks=59 groundPicks=0" -> beam ->
*** DESTROYED. Also proved PickRayHit resolves a REPLICANT when aimed at it
(hit=1 at 757m, 2-node run).
mech4: BT_GOTO="enemy" test aid now chases the nearest peer of ANY instance
(solo dummy OR replicant), not just replicants.
Still open (locomotion, not aim/MP): BT_GOTO/BT_AUTODRIVE stall short of the
enemy, so an AUTOMATED 2-node kill via the real beam isn't demonstrated yet
(the FORCE_DMG hook still is). A human can now aim correctly in the default
view. Solo un-regressed; build clean; KB validator clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
04a2049ce2 |
MP task #47: DISPROVE the Entity-layout root cause; correct the record
The prior commit ( |
||
|
|
c78662a0f1 |
MP cross-pod damage ROOT-CAUSED: EntityID localID corrupted by hostID on the wire (task #47)
Traced the dispatched-message delivery end to end with BT_MP_NET / BT_MP_FORCE_DMG. Everything works except one wire step: - A's Entity::Dispatch reroutes the replicant's TakeDamage (application->SendMessage(ownerID=3, EntityManager, msg)); POST-Dispatch the message carries entityID=3:22 (host:local) -- the replicant's own ID, matching B's master (GetEntityID()==3:22). VERIFIED sent. - On B the message ARRIVES, GetEntityPointer finds an entity, Posts it, the event drains (ProcessEventTask = ProcessOneEvent(0)), Event::Process runs, Receive finds+calls a handler. VERIFIED the full deliver chain. - BUG: B receives entityID=3:19, NOT 3:22 -- the localID dropped by exactly the hostID (3) between A's send and B's receive. So GetEntityPointer(3:19) returns the WRONG entity (classID 48, not the mech 0xBB9), whose base handler ignores the unaimed hit -> 0 damage. Auto-replicated UPDATE records (msgID 18) arrive with the correct 3:22 and find the mech, so the corruption is specific to the dispatched- message wire path/direction. Next: the host-relative EntityID (de)serialization on the dispatched-msg path (RoutePacket / packet EntityID encoding) -- diff vs the update path which translates correctly. Diagnostics retained (all BT_MP_NET-gated, off in solo -- verified: solo 22 hits, 0 probe noise). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fa88f74c68 |
HUD aspect correction: square reticle units at any window shape (task #44)
User: "it doesn't scale with our screen resolution?" -- correct. The HUD draws into the fixed 800x600 backbuffer and the present stretches it into the client area: on non-4:3 windows everything distorted (circles -> ellipses, the bottom tape widened vs the ladder, positions drifted outward). - BTGetPresentAspect (L4VIDEO, from gWindowAspect; 4:3 fallback). - dpl2d x-unit = (bbW/2)/presentAspect (== bbH/2 at 4:3 -- unchanged there), circles pre-squished in bb space so the stretch restores round. - The reticle<->NDC conversions (BTGetAimRay / BTProjectToReticle / BTProjectHotBox / BTTwistToReticleX) use the PRESENT aspect; the mouse map (BTClientToReticle) is now pure client-relative. - Placement itself is authentic and stays put: the binary clusters the instruments around the boresight (ladder x=0.35 half-heights, spans +-0.25) -- NOT at the display edges. - Crash en route: the BTGetPresentAspect extern declared INSIDE dpl2d's anonymous namespace = a different, unresolved symbol -> /FORCE garbage call (landed in MissileLauncher::DefaultData). Moved to global scope; gotcha noted in the comment. Verified 4:3 regression: aimed lock + zone hits + no crash; hud-geom MapX/MapY unchanged at 4:3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a8a56c57c9 |
Targeting correction: reticle = TORSO BORESIGHT, not a free mouse cursor (task #39)
Colleague flagged that targeting is torso-locked with no fine cursor. Correct: the pod stick's yaw drove the TORSO TWIST (MechControlsMapper -> HUD::SetFreeAimSlew(stick_x) @cockpit+0x28C, gated on torso-horizontal-enabled; hud.hpp:167) and reticlePosition @HUD+0x1FC is COMPUTED by HudSimulation from the mech pose quaternion + target geometry, zeroed to centre with no target (part_013.c:5680) -- never a free-floating cursor. The engine Reticle struct is general (shared with Red Planet); BT drives it from the torso boresight. Fix: - Removed the mouse-cursor slew (a mis-sourced stand-in from the RP-shared struct). The crosshair is now the torso boresight: BTTwistToReticleX(torsoTwist) = tan(twist) projected through the live per-axis projection. Dead-centre on the fixed-torso BLH (TorsoHorizontalEnabled=0); you aim by steering the whole mech. - BT_AIM="x y" retained as the headless test harness. Verified: face-to-face spawn -> HOT lock + aimed zone hits (36); BT_FORCE_TURN circling -> 31 no-target vs 1 HOT (steering off-target drops the lock, as it must). KB swept (combat-damage / gauges-hud / open-questions); checkctx CLEAN. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
18d49491b7 |
Reticle Execute @004cdcf0 RECOVERED: every HUD instrument now live (task #37)
The one un-exported gap in the reticle chain, read via capstone (tools/disas2.py; the annotated disasm preserved at reference/decomp/reticle_execute_004cdcf0.disasm.txt). Draw() is now a transcription of the real per-frame logic, and the HUD attr-table names (hud.hpp ids 4/5/6/8/A/B/C/D) are CONFIRMED by their Execute usage: - Range ladder: BAR from ladder-top to the caret + caret translate. - The bottom 21-tick tape is the TORSO-TWIST indicator (NOT heading): deflection = -/+(span/2) x (RotationOfTorsoHorizontal / twist limit), attrs 4/5/6. Fixed-torso BLH reads centred -- authentic static. - The circle-with-stem is the COMPASS (attr 0xD, rad->deg rotation) at (botX, botY - 3*tickMajor - 0.03), with the THREAT trail (attr 0xC) in its rotated frame: 0.05-unit attack-direction marks, fresh < 2s red, expiring at 6s. Port feed: player TakeDamage pushes the impact direction (dormant vs the passive test dummy). - Pips (composed into subB6): hidden when destroyed (attr 1 == 1), LIT when the fire cycle is LOADED (attr 0x1c == 2; port source rechargeLevel >= 1) else the dark charging ring; filtered by the weapon-GROUP bits (weaponMode & elementMask&0xF). Range plays NO part (the stored TargetWithinRange slots are never read by Execute). - Lock ring: subB9 at frame centre SPINNING 4 deg/frame while locked ([0x9d] is the spin matrix, not a heading list). - Target HOTBOX: a rectangle hugging the projected extents (x+-4 around the top-centre hotbox point, +1/-11.5 vertical; baked K=2.8145 -- the port projects through the live per-axis projection), switching to the edge arrows past +-1.6 or behind (BTProjectHotBox, L4VIDEO). - Reticle state Off/On + PrimaryHudOn full-HUD/simple-X switch, aim translate on slew move, 3D marker + PNAME player-name mesh identified (chain still deferred). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cf9f56044e |
Aim coordinates: route all conversions through the true NDC (fixes the
sideways lock offset) The windowed present stretches the fixed 800x600 backbuffer into the client area while the projection's aspect follows the CLIENT (resize rebuild) -- so the dpl2d/reticle frame, the client frame and NDC only coincide for a backbuffer-shaped window. The mouse mapping and the square-frame ray shortcut (cx = rx*tanHalfFov) were exact at screen centre and drifted outward with a side-flipped sign: aiming left of the enemy locked as if right of it (user report). - BTSetAimProjection now publishes BOTH proj scales (P11 carries the aspect) + the backbuffer size (the dpl2d frame). NOTE: GetViewport at the loop top holds the PREVIOUS frame's last pass viewport (gauge passes shrink it) -> use the renderer's own GetWidth/GetHeight. - BTGetAimRay / BTProjectToReticle convert reticle <-> NDC per axis (ndc_x = rx * vh/vw; camera x = ndc_x/P11) -- exact for any window shape and FOV. - New BTClientToReticle undoes the present stretch for the mouse (client px -> viewport px -> reticle coords); mech4 uses it instead of the raw client-height mapping. - 1Hz aim telemetry in the [target] log (hits/noRay/noPick): verified 100% pick hit rate once the projection is live (the pre-visibility mission-load seconds report noRay -- that was the "flicker"). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d78bde066d |
Authentic target acquisition LIVE: reticle slew + pick-ray lock + aimed zone damage (task #36)
The engine Reticle model (MUNGA/RETICLE.h [T0]) reconstructed end to end: - Mech::targetReticle is a real Reticle member bound to the TargetReticle attribute (0x1d), per the RP VTV analog (VTV.h targetReticle). - Crosshair slew: mouse -> client rect -> reticle coords (the pod stick free-aim channel's dev-box stand-in); BT_AIM="x y" pins it headless. LMB fires lasers / RMB missiles (alongside SPACE/CTRL). - Pick ray: the ACTIVE eye publishes pos + LookAtRH basis (BTSetAimCamera, L4VIDRND view-write site) + the render loop publishes proj._22; BTGetAimRay builds the world ray, Mech::PickRayHit slab-tests it against the collision template's ExtentBox via the engine's BoundingBox::HitBy (local frame; clips the Line at entry) -> world hull point. - Designation: the mech under the crosshair designates (sticky; re-hover refreshes; cleared when the target leaves the roster at burial); the entity target slots 0x37c/0x388/0x38c feed the whole weapon path. - Aimed fire: while HOT the impact point is the PICKED hull point -> the STEP-6 cylinder lookup resolves the zone under the crosshair (verified: center-aim -> head-band zone 13 dominant). Off-crosshair the sticky designation converges on center mass. - HUD: the aim group draws at the slewed position ([0x9a] translate, contained by push/pop); the designator ring tracks the target's projected point (subB9 hot / subB8 designated, BTProjectToReticle); edge arrows when off-screen/behind. - AUTHENTIC gating: no fire arc exists in the binary (FireWeapon fires whenever HasActiveTarget, part_013.c:7758) -> BT_FIRE_ARC is now an explicit OPT-IN presentation clamp; the hardwired gEnemyMech lock and the projectile path's gEnemyMech fallback are removed. - Fixed en route: every renderable rebuild stomped mCamera back to the chase eye (start-inside silently lost the cockpit camera; the aim feed exposed it). BTL4VideoRenderer::mViewInside persists the chosen view. Verified headless: BT_AIM="0 0" -> HOT lock, pick hits the hull face at exact range, aimed zones resolve; BT_AIM="0.8 0.3" -> no lock, zero damage, zero missile launches; kill chain completes to wreck + smoke. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
48b17750e5 |
HUD reticle + weapon pips LIVE: dpl2d 2D display-list port (task #35)
- dpl2d API fully recovered from the binary recorders (@487f34-488630): opcode model (points/lines/polyline/circle/color/width/matrix/push-pop), CallList = INLINE include (state persists to caller), centered coordinate frame (unit = half viewport height). game/reconstructed/dpl2d.cpp rework. - BTReticleRenderable ctor @004cc40c transcribed with the authentic calibration (originX .35, originY .25, scaleY .5, 0..1200m right range ladder, bottom heading tape, FUN_004cd938 tick ladders, lock rings, turn arrows); range caret slides from the live target range fed by the mech4 targeting step (BTSetHudTargetRange). - Weapon pips: the binary gate is IsDerivedFrom(0x511830 = MechWeapon::ClassDerivations) [T1: part_014.c:5386 hard-aborts on missing weapon attrs; part_012 counts + roster ORs capabilityFlags@+0x334] so ALL 7 BLH weapons register (3 lasers + 2 PPCs + 2 MissileLaunchers). Pip A (lit, authored PipColor) on TargetWithinRange, else dark ring B. - AddWeapon @004cdac0 store map corrected to the verified order (part_014.c:4827-4837); both state attrs are literally named "SimulationState" (strings @51d526/51d577) -> weapon simulationState. - Mech roster this[0x1ef] renamed poweredSubsystems -> weaponRoster (0x511830 is MechWeapon, not PoweredSubsystem=0x50f4bc); derivation-tag table added to context/decomp-reference.md. - Draw hook BTDrawReticle after the 3D scene, cockpit view only. Binary Execute @004cdcf0 is an un-exported gap -> Draw dynamics [T3], tracked in context/open-questions.md with the blx_cop canopy + PNAME pip meshes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
56f02b2176 |
Cockpit view live: V toggle, camera gate, real EyepointRotation, inside skeleton
- V toggles the authentic cockpit eyepoint <-> the chase camera. Both eyes coexist; DPLEyeRenderable::Execute now writes the VIEW only when it IS mCamera (unconditional writes let the last-executed eye stomp the toggle). - EyepointRotation is a real zeroed EulerAngles member on Mech (was bound to the shared junk attrPad -- the eye composes this attribute into the view EVERY frame, so the cockpit camera was rotated by garbage: the canted horizon, then the black screen). The eye-slew systems write it later. - The cockpit eye mounts at the eyepoint's REST position with a clean upright forward basis on the tree root (the live joint chain fed it the site tilt + torso pose; authentic pitch/yaw is the deferred gyro eye chain). - The inside view swaps the player to the INSIDE skeleton mesh set (SkeletonType_A): 19 body segments hide, exactly one mesh remains -- blx_cop.bgf, the authentic cockpit canopy shell around the eyepoint. It currently renders as a black enclosure (the black-screen report), so it is HIDDEN pending its interior/punch-material rendering (BT_INSIDE_COCKPIT=1 shows it for that work). Damage gstates respected in both directions; RemakeEntity keys off the DISPLAYED skeleton. - Dev: BT_START_INSIDE=1 starts in the cockpit view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
31626a3b97 |
Beams at the models' NATURAL width, per-weapon tube models (de-cartooned)
The "cartoonish" beams: the port drew the ermlaser tube INFLATED -- a 3.0x glow + 0.9x core two-layer hack, 13x the authored radius. Parsed the beam models: ERMLASER/MLASER 0.22u radius, SLASER 0.11, LLASER 0.32, PPC 0.62 -- thin pencil beams with an authored per-class size progression; the PPC bolt is genuinely ~3x fatter with its own 62-vert shape. Now: ONE draw per beam at natural model scale (width multiplier 1.0), using the weapon's OWN tube model (ermlaser for lasers, ppc.bgf for PPCs; the s/m/llaser tubes load for other mechs' loadouts). BTPushBeamKind carries the model selector; BTPushBeam keeps its signature (kind 0). Tint = 40+215x the authored PipColor; the thin natural tubes stay under additive saturation so the scrolling grit reads without a hand-dimmed core layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9b5004c39d |
Missiles: authentic dsrm smoke trail + torso aim (placeholder tracer removed)
The "white lines toward the feet" report: the LAUNCHES are authentic (the mech's real MissileLauncher/ProjectileWeapon roster subsystems firing on their own recharge cadence -- that's the every-Nth-shot rhythm), but the visual was the bring-up 3-segment beam tracer, and the aim point was the victim's ORIGIN (ground level between the feet). - Trail: per-frame puffs of the AUTHENTIC dsrm effect (psfx 0, "the lrm smoke trail") along the flight path, frame-oriented so the smoke streams behind the round (its .PFX velocities are +Z = backward); the round itself is a short hot streak. New BTPfxTrailPuff spawns slot particles directly at a moving point (no emitter instance). - Aim: the resolved target point lifts to torso height (the beams already converged there) -- impacts now cyl-resolve to body zones instead of diving at the feet/legs. Follow-up noted: the round MESHES (BULLET/LRMS.BGF ship) for a true model-per-projectile look. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5236d4263a |
Wreck sink (quadratic burial) + ldbr debris field -- the 1996 script completed
Answers "does the wreck fade away?": YES, by sinking. FUN_00456410 (the 1996 sink renderable) computes offsetY = rate * t^2; the hulk's authored rate is -0.025 -> the ~7-unit hulk is fully underground ~17s after the kill. The script also pairs the standing hulk with the LDBR strewn-debris field (12x13u flat scatter), parented together and sinking together. Also verified from the mesh data: BLHDBR (1537 verts -- more than the intact torso) IS the authored Blackhawk wreck: the classic standing-leg-in-rubble sculpt. The "just a leg standing there" report is the authentic art. (THRDBR -- the mesh the 1996 script hardcoded -- parses to ZERO vertices; more evidence the hardcode was an unfinished dev shortcut.) Implementation: SwapToWreck adds the ldbr piece; TickWreck applies the quadratic sink per frame (driven from the dead mech's UpdateDeathState), hides both pieces at burial and reports it so the wreck-smoke re-arm stops with the wreck. DPLStaticChildRenderable::SetOffsetTranslation added (Execute re-reads OrientationMatrix per frame -- same in-place idiom as SetDrawObj). Lifecycle verified live: kill -> 'blhdbr.bgf' + ldbr debris -> smoke re-arm @10s -> wreck buried @~17s -> smoke stops. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c0fa6bf21a |
Death = the burning WRECK-HULK swap (effect 104) -- the authentic chain, reconstructed
The user was right: a dead mech turns into a pile of wreckage. The authentic
1995 chain, recovered end-to-end:
kill -> the victim's per-mech DEATH ModelList ('blhdead'/'lokdead'/'owndead'/
'thrdead', .RES ids 22-25) -> its authored effects: 104 (the WRECK script) +
1007 (dnboom big explosion) + 1001 (ddthsmk rubble smoke plume) + a damage-
smoke burst (3/4/5/15).
Effect 104 = ExplosionScripts case 4 (part_008.c:2663, LIVE in the 1996 binary;
the "disabled" warning is case 6): loads the destroyed hulk + flamesml/flamebig
flame meshes with sweep flicker. Every mech ships its hulk (BLHDBR/MADDBR/
LOKDBR/... + GENDBR generic); the 1996 script hardcoded thrdbr.bgf (dev
shortcut) -- we use the victim's own.
Reconstructed as BTL4VideoRenderer::SwapToWreck: hide every segment mesh, hang
"<prefix>dbr.bgf" on the tree root (pending-swap if death precedes tree build);
routed from the engine's ExplosionClassID dispatch (effect 104 ->
BTSwapMechToWreck(explosion->GetEntityHit())). The kill now fires the
authentic 'blhdead' resource (manual 7+1 pfx calls removed -- the list carries
1007/1001 itself). Verified live:
[death] firing authentic death list 'blhdead' id=22
** effect_number = 104
[BTrender] wreck swap: victim -> 'blhdbr.bgf'
Follow-ups noted: mesh flames + hulk settle (cosmetic), DeathSplash.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
b44c0f98f6 |
PFX continuous-rate emission (data-verified) + persistent wreck smoke
The one-burst emitter batching was wrong: in EVERY shipped .PFX, maxIssue/rate == release_period (DAFC 25/100~=0.2, DNBOOM 35/150~=0.2, DDAM2 35/16~=2, DDTHSMK 30/3=10) -- release_period is the EMISSION WINDOW and rate is particles/second emitted continuously across it. The death smoke plume is authored as a 3/sec trickle for 10 seconds (+6s particle life), not one same-frame puff. Emitters now integrate rate*dt until maximum_issue. Wreck smoke (port addition, [T3]): a destroyed mech re-arms the death/rubble smoke plume (psfx 1, DDTHSMK) every 10s -- its own authored window -- so the dead hulk visibly keeps smoking instead of standing there looking alive (the freeze-death is invisible on an already-stationary target). New member Mech::wreckSmokeTimer; UpdateDeathState takes dt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a3d67cc639 |
Combat visible + killable: Wword root-cause fix, .PFX effect layer, RemakeEntity swap
The 'can't kill the enemy / no visible damage' cluster, root-caused and fixed faithfully: - STEP-6 unaimed path was INERT: the cylinder table was 'cached' at Wword(0x111) -- the recon ABSORBER bank (stores nothing, reads 0) -- so every unaimed hit silently no-op'd. Promoted to the named member Mech::damageLookupTable (binary this[0x111], was mislabeled ammoExpended). New gotcha class recorded (reconstruction-gotchas §2) + sweep; 2 dead multiplayer branches logged. - Fire path migrated off the stale vital-zone aim onto the completed STEP-6 unaimed dispatch (zone=-1 + beam entry point -> cylinder resolves the exterior zone). No more invisible 1-shot kills; death via the authentic cascade (~14 center-mass hits). Wreck stays TARGETED on kill (beams stop on it); scoring latches off. - SendSubsystemDamage AV fixed: unbound critical-subsystem plug guard (43 unbound plugs/mech logged as an open question -- the binding itself is a gap). - RemakeEntity (render damage swap): the 1996 render state machine's missing Remake state, reconstructed as an in-place SetDrawObj mesh swap keyed by each segment's damage-zone graphic state (tree dtor doesn't cascade -> never rebuild). Destroyed arms/guns visibly wreck (the only variants the RES registers). - BT .PFX particle layer (L4VIDEO.cpp): the 1995 explosion/damage effect layer, unported since 2007 (DPLIndependantEffect/ReadPSFX/ExplosionScripts all stubs). Parses the authentic VIDEO/*.PFX definitions via the [pfx_day] psfxN mapping; premultiplied blending renders BOTH families from the same data (additive-style fire + occluding smoke -- DDAM2 is 30% grey, DDTHSMK ramps negative: impossible additively); depth-sorted billboards with a radial-masked grit sprite; impact-frame orientation (.PFX offsets are authored mech-local, -Z = out of the struck armor toward the shooter) for weapon hits AND damage bands (via lastInflictingID, now maintained -- was declared but never written). Both effect-number encodings route (raw dpl <100 + WinTesla 1000+slot carried by the band resources). Death fires the authentic dnboom (7) + ddthsmk smoke plume (1). - Effects anchor at the impact point / damaged zone's segment, not the mech origin (no more fire at the feet). - Dev force-input gates BT_AUTOFIRE / BT_AUTODRIVE for headless fire-chain verification; BT_PFX_ADD=1 flips the particle blend for A/B. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4a4ec6855c |
gauge-complete P4e: SectorDisplay reconstructed + registered -> radar SECTOR X/Z read-out LIVE
The "sectorDisplay" cockpit primitive (Secondary overlay, the radar SECTOR X/Z coordinate read-out) was PROSE-ONLY in btl4gau3 (placeholder Make, no ctor/ methodDescription/registration) -> the config line was parse-SKIPPED and never built. Reconstructed byte-verified from the disassembly (ctor @4c9e10, Execute @4ca07c, methodDescription PE-parse): SectorDisplay : GraphicGauge, sizeof 0xC4. Its Execute reads the linked mech's world position and shows two 100-unit sector numerics: numericA = Round(-localOrigin.z * 0.01) + 500 numericB = Round( localOrigin.x * 0.01) + 500 (rounding = round-to-nearest == FUN_004dcd94, corrected from the reviewer's wrong "truncate" claim; 0.01 const PE-verified; -Z/+X axis + fchs confirmed from asm). Overridden slots: LinkToEntity(9) caches the subject, BecameActive(3, non-inactivating), Execute(16) -> satisfies the container-Execute rule. Layout overflow-locked (static_assert sizeof<=0xC4). Make/ctor/dtor mirror the registered PilotList sibling; the config image name is copied (nameCopy) since Execute reads it per-frame. Registered in BTL4MethodDescription[]. VERIFIED LIVE (BT_SECTOR_LOG): Make port=1 pos=(125,579) image=helv15.pcc gridCached=1; Execute -Z=960.4 X=361.6 -> sectorA=510 sectorB=504 (Round(9.6)+500=510, Round(3.6)+500=504 -- authentic 100-unit sectors from live mech position). Gauge composite renders full, no crash. The skip list is now exactly the two remaining widgets (prepEngr x12, messageBoard). Also: a permanent BT_GAUGE_SKIP_LOG diagnostic (GAUGREND.cpp, gated) that logs each unregistered gauge primitive the dev-parse skips -- the tool that pinned this down (earlier "not built" runs were killed before the lazy gauge-renderer init). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
070af409f7 |
gauge wave P1a: publish heat/power/weapon attributes + radar zoom
The gauge-databinding-map workflow found most cockpit gauges resolve NULL because the reconstructed subsystems publish only a fraction of the attributes the config binds. First publishing batch (attribute tables are read-only static data; ids kept a dense prefix from each parent's NextAttributeID): - HeatSink table dense-append: DegradationTemperature/FailureTemperature (the condenser temp-bar warn/max endpoints -- were NULL, so the two-part bars could not scale), NormalizedPressure/DegradationPressure/CoolantMassLeakRate, and the HeatSink link. Condenser/Reservoir inherit this -> all 6 condenser temp bars now resolve current/warn/max (verified: BT_GAUGE_ATTR_LOG all OK). - PoweredSubsystem::GetAttributeIndex() (new) publishes InputVoltage->voltageSource -- the cluster power-branch gate (the power-lamp/generator-voltage/state-lamp sub-branch is skipped when it resolves NULL). Flows to Sensor/Myomers/weapons. - MechWeapon::GetAttributeIndex() (new) publishes OutputVoltage/PercentDone-> rechargeLevel; Emitter/PPC/ProjectileWeapon/MissileLauncher/GaussRifle DefaultData re-pointed at it (they carried an EMPTY default-constructed index -> resolved NOTHING). Verified: the ER MED LASER / PPC / STREAK weapon clusters now render live recharge dials (were blank TEMP/STATUS). - Mech::SetTargetRange un-stubbed (radarRange = range) -> the radar map scale + overlay range readout track the mapper's zoom (was frozen at 1000). - GAUGREND ParseAttribute: env-gated per-binding resolution trace (BT_GAUGE_ATTR_LOG) -- durable diagnostic infra for the wave. Verified DBASE+dev gauges: no startup/gauge-construction crash (dense chain intact), combat un-regressed (TARGET DESTROYED), clusters build with InputVoltage resolving. Remaining config-binding NULLs: HeatSink/AmbientTemperature (aggregate bank, P3) + Searchlight/LightOn (P1b). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0bceb9a638 |
gauges/map: populate the radar entity grid + draw mech contact blips
Root-caused why the radar showed 0 contacts: the gauge renderer's moving/static entity grids (queried by GetMoving/StaticEntitiesWithinBounds) were NEVER populated -- the 1995 game fed them from ExecuteImplementation's InterestingEntity iterator, which the WinTesla port dropped. Fix (engine): add GaugeRenderer::RebuildEntityGrid() -- Clear + repopulate movingEntities from the world's DynamicMaster + DynamicReplicant entities (the vehicles/mechs; AllEntity would flood the grid with ~300 props/effects/terrain). Called from the map's Execute phase 0 (under the gauge's guarded Execute, so a fault disables only the map). Verified: contacts are now found, combat un- regressed (TARGET DESTROYED), heap-clean under BT_HEAPCHECK. Fix (map): DrawMoving now draws a cross blip for mech-class contacts (0xBB9) when the authentic pip raster set is absent (the BT pip table is stubbed in this engine build). KEY PROJECTION FIX: worldToView is a camera (view->world) matrix and Point3D::Multiply applies only rotation+scale (NOT translation), so projecting the absolute entity position left the viewpoint un-subtracted and the blip landed off-screen (verified: blip @(580,-1154)). Project the RELATIVE position (delta = entity - viewpoint, already computed for the range check) instead -> the blip projects viewpoint-relative + heading-rotated correctly (@(164,221)). radarRange default 500->1000 (1km zoom) so contacts within ~500m show on the radar (SetTargetRange, the real player zoom, is still stubbed). BT_MAP_LOG traces the blip coords. STATE: the grid population + viewpoint-relative blip projection are correct and combat-safe. A reliable on-screen contact demo still needs the display-scale vs spawn-distance reconciliation (the BT_SPAWN_ENEMY dummy sits at a varying, large distance -- 377-828m -- beyond the display radius across runs) + the authentic pip symbol/name/video-object infrastructure (still stubbed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fb8c1d6ace |
gauges: colorMapperMultiArmor (worst-of-8-zones) + fix the interpreterTable overflow [widget recon 4]
Reconstructs colorMapperMultiArmor -- tints one schematic colour (a whole torso section) by the WORST of up to 8 damage zones: - MultiArmorConnection (@004c346c/@004c34f4): scans 8 zones via entity->damageZones[idx], keeps the max damageLevel, count==0 ? 100 : Round(worst*100). - ColorMapperMultiArmor::Make/ctor (@004c3c48/@004c3d60): resolves 8 named zones to indices via Entity::GetDamageZoneIndex (13-param methodDescription). ALSO fixes a latent HEAP CORRUPTION that registering this widget exposed (and that would have blocked every further widget): GaugeInterpreter's bytecode buffer is a fixed interpreterTableSize=46864 (GAUGREND.h) tuned for RP's config, and its Insert() bounds guard is a Verify() that compiles out at DEBUG_LEVEL 0. As each BT gauge widget is registered, more of BT's larger L4GAUGE.CFG resolves into bytecode (vs being parse-skipped); colorMapperMultiArmor's 13 params x hundreds of calls pushed the total past 46864 -> silent overflow past the char[] -> heap smash detected later in mission bitmap loading (not the gauge code). Fix: interpreterTableSize -> 262144 (sized for BT's full config). Verified: parses, builds, all zones resolve, no /FORCE unresolved, combat un-regressed (TARGET DESTROYED, 0 crashes), stable. Details: docs/GAUGE_COMPOSITE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b526e92cad |
gauges: all 6 cockpit MFD surfaces in a SEPARATE dev window (Milestone C)
Generalizes the single-'sec'-inset (Milestone B) into a full 6-surface compositor presented in its own top-level window -- the default under BT_DEV_GAUGES (BT_DEV_GAUGES_DOCK=1 docks it into the main window instead). A 960x384 window tiles all six pod instrument screens: Heat (COOLANT/BALANCE/ RES + condenser gauges), Comm (KILLS/DEATHS/SELECT TARGET), Mfd1/2/3 (DISPLAY/ PROGRAM/NEAREST frames), and the color radar (SCALE grid + dials + ARMOR DAMAGE). Main 800x600 3D view is un-occluded; default DEV un-regressed (TARGET DESTROYED after 8 hits, 0 crashes). Design (mapped by the mfd-multisurface-map workflow): - All 6 surfaces are bit-plane MASKS over the ONE shared SVGA16/pixelBuffer (sec=palette low byte; Heat=0x4000 UL, Mfd2=0x0400 UC, Comm=0x8000 UR, Mfd1=0x0100 LL, Mfd3=0x1000 LR), so the compositor reaches the SVGA16 once and extracts each by mask. No port-name reconcile needed on the dev path -- fetch the BT names directly; the RP aux* names only matter to the pod's own SVGA16::Update demux (a deferred pod-only fallback). - SVGA16::DrawDevSurface: two kernels -- palette-LUT (sec) + mono bit-plane-> tint ((word&mask)?tint:0). BTDrawGaugeSurfaces iterates a 6-entry table. - Separate window = one CreateAdditionalSwapChain on the existing device (no 2nd D3D device). BTGaugeWindowRenderAndPresent (after the main EndScene): SetRenderTarget -> SetDepthStencilSurface(NULL) -> Clear -> BeginScene -> 6 tiles -> EndScene -> restore -> swap->Present. - KEY BUG fixed: the main 800x600 depth surface stays bound when rendering to the 960x384 gauge backbuffer; a bound depth smaller than the RT is invalid -> all draws silently fail (clear color, no geometry). Unbind depth (Z is off), restore after. Pod SVGA16::Update/BuildWindows path byte-unchanged; all gated BT_DEV_GAUGES. Content is still the authored cockpit frames + base-table gauges -- the live MFD widgets need the BTL4MethodDescription reconstruction (this window is now the live viewer for that work). Details: docs/GAUGE_COMPOSITE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a256813395 |
gauges: secondary/radar MFD composites live on a dev box (Milestone B)
The pod's secondary/radar cockpit surface now renders as an inset in the 800x600 dev window under BT_DEV_GAUGES: radar/tactical grid, SPEED/HEADING/ MAGNETIC/PROP dials, and the color-coded ARMOR DAMAGE mech schematic -- real gauge content (nzSec=27247), composited from the shared CPU pixelBuffer. Composite pass (engine): SVGA16::DrawDevInset palette-expands the secondary plane into a MANAGED texture on the MAIN device and draws an XYZRHW inset quad; BTDrawGaugeInset() reaches the gauge renderer's 'sec' port and is called from DPLRenderer::ExecuteImplementation as the last draw before EndScene. Three reconstruction bugs fixed to get real content: 1. BTL4Application::MakeGaugeRenderer signature mismatch (REAL fix): the reconstructed no-arg override only HID the 2007-engine's widened 3-arg virtual MakeGaugeRenderer(int*,int*,int*), so the engine built a base L4GaugeRenderer whose ctor never parsed gauge/l4gauge.cfg -> empty symbol table -> "undefined label 'bhk1Init'" -> no ports/gauges. Matched the 3-arg signature so it truly overrides (args ignored; BT is fullscreen). Same bug class as the BTL4GaugeRenderer(false,NULL,NULL,NULL) ctor fix. 2. Parse hung on undefined primitives (gated dev accommodation): the BT gauge primitive table (BTL4MethodDescription) is still a stub, so an unknown primitive -> ReportParsingError -> Fail() -> a MODAL dialog freezing the parse. Under BT_DEV_GAUGES, skip the unknown primitive's params so labels register and the base "configure" primitive builds the ports. 3. Gauge widgets AV on NULL data bindings (gated): NumericDisplayScalar's NULL value_pointer -> GaugeConnectionDirectOf ctor deref (bind NULL->static zero); RankAndScore::Execute derefs unreconstructed game state (Gauge::GuardedExecute SEH wrapper -> Disable(True) on first fault). All guards gated on BT_DEV_GAUGES: default DEV un-regressed (TARGET DESTROYED, 0 crashes) and the pod path is byte-unchanged (strict Fail, no guards). Remaining (docs/GAUGE_COMPOSITE.md): the real gauge WIDGET reconstruction (BTL4MethodDescription method table + gauge->game-state data bindings), then Step 2 (RP<->BT MFD port-name reconcile) + Step 3 (2-window layout). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c13e72d0ba |
platform profile + gauge dev-composite (Milestone A: gauge renderer woken)
PLATFORM PROFILE (-platform pod|dev / BT_PLATFORM / run.cmd pod; default dev): an env-preset selector, NOT a code fork -- it picks which environment the existing video/gauge/input code reads. dev = single 800x600 window + keyboard (the working build); pod = RIO input + (on real hardware) the multi-surface gauges/MFDs, mirroring content/SETENV.BAT (the pod path -- FindBestAdapterIndices / SVGA16 -- is left untouched). The multi-surface needs the pod's 2 video cards, so -platform pod does NOT auto-enable L4GAUGE on a dev box (stays bootable, single-window). Also fixes a real engine bug: SVGA16::BuildWindows PostQuitMessage'd on a CreateDevice failure but fell through to a null-device deref (segfault) -- now breaks (inert on the pod). GAUGE DEV-COMPOSITE, Milestone A (option B; opt-in BT_DEV_GAUGES, default OFF): wake the (dormant) gauge renderer so its CPU-rastered pixelBuffer can later be composited into the dev window, WITHOUT touching the pod SVGA16 output path. SVGA16 does no per-surface D3D in this mode (a DevGaugeComposite() gate forces the no-surface path + short-circuits Update/Refresh). Waking the never-exercised gauge subsystem exposed 4 latent reconstruction bugs, each guarded: SVGA16::Update / Refresh (empty mSurfaces[]), LBE4ControlsManager::MakeLinkedLamp (NULL lamp manager), L4GaugeRenderer::NotifyOfNewInterestingEntity (garbage warehouse chain). It now boots STABLY; the gauge reconstruction is incomplete (shadowed lamp-manager + warehouse members) -- Milestone B (the composite pass) will reveal whether the content is real. See docs/GAUGE_COMPOSITE.md. Verified: default DEV un-regressed (combat DESTROYED, 0 crashes); pod path untouched; BT_DEV_GAUGES boots stable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7b7d465e5e |
Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.
Layout:
engine/ MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
models) + image codec; the minimal rp/ headers the audio HAL needs
game/ reconstructed BT logic + surviving-original BT source + fwd shims
+ WinMain launcher
content/ full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
docs/ format specs + reconstruction ledgers
reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
tools/ MP console emulator + map/resource scanners
One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|