d4f105f42d124bb2684cab596cf64b011e8c63a0
397
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d4f105f42d |
BT410 5.3.69: the cockpit eye sits in the canopy -- site handling in the walk
RecurseSKLFile now walks "site=" children. Sites get no draw component -- which is why the walk's 26 nodes matched the real capture all along: sites were never DCS nodes -- and siteeyepoint spawns the cockpit camera with the donor's exact construction (bt411 btl4vid.cpp:462, decomp FUN_004579a8): offset = the site's own local rest transform, parent = the site's PARENT joint's DCS, not the hull root. World orientation, torso twist and gait all arrive through chain composition. The root-DCS eye remains only as the zero-construction fallback for skeletons with no siteeyepoint. Measured on the live pod with the RIO streaming: [eye] cockpit eye on 'jointeye', 26 nodes / 19 objects / 1 eye, and the bridge camera sits at Y = hull + 7.7 -- the exact chain sum jointlocal 5.29 + hip 0.37 + torso 0.31 + jointeye 1.69. canopy_eye1.png is the pilot's view, the mech's own nose wedge visible below the sightline. Own-mech cage handling is the next layer. Also: eye_count threading through ReadSKLFile/RecurseSKLFile, and the [skl] summary now reports it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8231bee58c |
BT410 5.3.68: THE LIVE-RIO FAULT IS FIXED -- it and the TM crash were one defect
The [map] audit named it in one run: subsystem 17 = Torso, attribute ids 12/13 unresolved. The donor's decompiled torso.hpp carries the authentic enum with binary offsets -- ids 3..15, including StickPosition(9), TorsoUp/Down/ Left/Right(10-13), TorsoCenter(14), MotionState(15). Our table stopped at seven entries with MotionState at id 9, on the strength of a comment claiming the rest were messages. The streamed control mappings bind BY ID as direct WRITE destinations, so the truncation produced two different crashes from one cause: in TM mode the button ids 12/13 resolved NULL (the boot write-fault the moment the joystick polled), and in RIO mode the analog id 9 resolved to our motionState StateIndicator -- every analog packet from a live vRIO wrote raw floats over a watcher-socketed object, and the corrupted chains walked into unmapped memory ~30-40s later. That was the 'intermittent' pod fault at the fixed DPMI-host address. vRIO down = no analog = no corruption, which is why the norio conf launched: every observation from the whole hunt drops out of this mechanism. Fix: the full 13-entry donor table; five int command members carved from the dynamicsState reserve (class size unchanged); ctor zeros them. Verified, two agreeing runs each way: TM mode launches with a clean audit and the Thrustmaster joystick driving the torso (stickY=0.907 -> torsoElev 0.349); RIO mode with vRIO STREAMING launches, zero faults, weapons cycling. pod_render_rec is no longer poisoned and the norio workaround is obsolete. The audit-before-the-archive-call pattern (BT_MAP_LOG walks the same streamed table CreateStreamedMappings consumes, naming what will not resolve) turned a two-day intermittent-corruption hunt into a one-run lookup. The streamed RES tables are binding contracts on our attribute enums. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f30acc6a94 |
BT410 5.3.67: pose_chase was upside down -- harness bug, wire exonerated
The operator caught it: the 5.3.65 image showed the mech hanging from its own ground-shadow plate (-y is up in the bridge world). The follow-up pinned down what was and wasn't wrong, and the reconstruction survives untouched. Right, by three independent wire-level proofs: MAD.SKL offsets are a coherent Y-up model (hip +5.29, knees descending, torso ascending -- anatomical only read +Y-up); the BGF vertex extents agree (foot meshes extend -1.0 below their ankle to the sole, torso +3.8 up to the missile pods); and the SHIPPED pod's wire carries the same signs (hip +6.21, knees negative, vehicle root a pure yaw with det +1). Our wire and shipped's are convention-identical -- no flip exists between game and board in either game. Wrong: the ad-hoc chase camera. It borrowed the aerial up-hint from the calibrated live-bridge world (where -y is up) and applied it to a raw scene-space render of Y-up wire data; the symmetric checkerboard ground disguised the inversion. The shipped capture rendered upside-down through the same harness, which is what cleared the wire. Both conventions now documented in the roadmap: wire/scene space is Y-up in both games; the live bridge presents -y-up via its calibrated mirror family (FP_RIGHT_SIGN / CAGE_TWIST_SIGN, COCKPIT-CAGE-NOTES.md). Tooling: pose_probe.py replaces the throwaway harness (updir=+1 scene space default, -1 for the bridge sense). pose_chase_upfixed.png is the corrected milestone image: the MadCat right side up -- chicken-walker legs, purple knee actuators, feet planted, shadow under the feet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1233a1d5f6 |
BT410 5.3.66: RIO-fault eliminations, and TM mode's own NULL-deref surfaced
Three clean eliminations on the live-RIO fault, all on the ack-fixed emulator: not the ack heuristic (reproduces after the fix), not the debug logging (pod_render_quiet faults identically with no BT_* env), and not the failed test-mode handshake (the quiet run's init PASSED and it faulted anyway). The Thrustmaster isolator -- L4CONTROLS=THRUSTMASTER so the game never opens the RIO while vRIO keeps streaming into serial1 -- was blocked by its own discovery: TM mode dies at boot in ControlsInstanceDirectOf<int>::Update+0x6, mov eax,[eax+0x1c] with EAX=0, at guest 00485E1A. That address resolves cleanly in our map and the loader names BTL4REC.EXE CODE 0x75E1A directly: a real defect in our controls wiring for the non-RIO path, and its own reconstruction brick. Standing facts for the hunt: our exe + live vRIO = fault in ~30-40s; shipped exe + the same stream on the same emulator = fine (two baselines). The ISR and LBE4ControlsManager are archive code; ours in the per-event path are the mapper (MECHMPPR) and the event/receiver plumbing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e846717283 |
BT410 5.3.65: THE MECH STANDS -- wire matrix convention proven from engine source
pose_chase.png: the MadCat assembled from its 19 parts, standing in the arena at its live articulated position. Feet, legs, torso, weapon pods, canopy plate. Not a collapsed stack at the origin, which is what every prior frame actually showed. The convention is no longer inferred -- it is read from the engine. RootRenderable's ctor writes an entity pose into a DCS with *(Matrix4x4*)dpl_GetDCSMatrix(myDCS) = localToWorld, so Matrix4x4::operator=(const AffineMatrix&) IS the wire layout: row-major, rotation in rows 0-2, zeros in column 3, translation in ROW 3 (entries 12/13/14). The old 3/7/11 choice rested on two errors, both corrected in the code comments: AffineMatrix does keep translation at 3/7/11, but because it is COLUMN-major -- citing it as precedent for a row-major layout read the storage and ignored the indexer (AFFNMTRX.HPP:99). And 'the last row made the maths blow up' came from the contaminated bisect; those crashes were the RIO fault. Rotation now composes through the engine's own Matrix4x4::operator=(const EulerAngles&) from the page's pitch/yaw/roll, and the node write uses the RootRenderable idiom (dpl_GetDCSMatrix + assign). Also banked: the chase-frame fifobridge variant -- camera framed on the articulated root instead of the arena bounds -- as the offline proof harness for any future pose question. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d143f833ab |
BT410 5.3.64: THE COCKPIT EYE IS LIVE -- root+eye composition, and the emulator
deadlock it exposed The camera gap root-caused and fixed through three layers: LAYER 1 (ours): "chain the engine after the skeleton" was a non-fix. The engine's MakeEntityRenderables only accepts Object/Rubble resources, so chaining it with a Skeleton printed "wrong video resource type" and built NOTHING -- the fifodump proved it: zero vr_flush_dcs_artic records. The real fix mirrors the engine's Mover composition (L4VIDEO.CPP:4795-4860) inside our mech case: a Dynamic RootRenderable (whose ctor seeds its DCS from localToWorld and whose Execute re-flushes on change -- the ONLY source of per-frame wire articulation), the skeleton hung UNDER its DCS via ReadSKLFile's new parent_dcs parameter, and for the inside view a DPLEyeRenderable on that root with the published EyepointRotation. Also explains the mystery monolith in the first frame: the skeleton was parked at the world origin with the camera inside its shins. LAYER 2 (1995 library, read from our own linked symbols): dpl_DrawSceneComplete = velocirender_frameack(0), and frameack's unsolicited-reply path prints "dpl error - unsolicited input during frame ack", adds the 1995 authors' own puzzled "flush artic??" when the stray action is 0x1f, and calls exit(9). Documented before it ever fires. LAYER 3 (emulator, the actual deadlock): with per-frame articulation flowing for the first time, the game stopped drawing at frame 232 -- one 0x1f burst per frame forever, no receives, no error. The VPX device feeds the frame ack only after 6 CONSECUTIVE empty polls and reset that counter on EVERY outputData write; per-frame articulation writes made the count unreachable, so dpl_DrawSceneComplete never went true. Fix: the reset is gated on !frame_outstanding -- once a draw is outstanding the only receive the game will do next is the frame ack. The iserver-drain concern the reset guarded is boot-time only, when no frame is outstanding. Verified: two agreeing runs of ours on the fixed emulator (611 draws / 222 artic batches in run 2 -- the 232 wall is gone), camera travelling with the mech (cam -329.7,0,60.5 -> -362.3,0,54.5 across 8s), anim_abs 0 -> 1 on the bridge, view now INSIDE the cockpit cage. Shipped binary re-run on the fixed emulator: launches and runs, no regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
af010c18c2 |
BT410 5.3.63: the mech gets both skeleton and engine renderables
MakeEntityRenderables no longer stops after the skeleton. It builds ours AND
chains DPLRenderer, which is what creates the RootRenderable and the
DPLEyeRenderable the camera follows. Verified over three runs:
[mer] 208 class=3001 view=1 res=1
[vid] type=1 file='mad.skl'
[skl] video\mad.skl -> 26 nodes, 19 objects
[chain] into DPLRenderer, skeleton=yes, stack at 0x12dde7
No fault, mission launches, sim runs. Worth noting separately: the engine's
eyepoint construction -- prime suspect for the page fault through most of a
session -- runs perfectly here, with the stack at 0x12dde7, right beside the
main loop's frame. Both are consistent with the corrected finding that the
fault is a live-RIO artifact at a fixed DPMI host address.
The camera still does not move: two frames six seconds apart differ by zero
pixels while [sim] shows the mech travelling from (487,20,288) to (493,20,301),
and the bridge reads cam (0.0, 10.0, 0.0) -- world origin plus eye height. So
the eye renderable was necessary but not sufficient.
Eliminated by reading our own code rather than guessing: the viewpoint entity
IS the mech (BTL4APP.CPP:354), the video renderer IS linked to it
(APP.CPP:1337), and SetupCull would Fail() loudly if the mech lacked
"siteeyepoint" or EyepointRotation -- neither Fail appears. That leaves the
path from SetupCull's worldToEyeMatrix to what the bridge reads off the wire,
which is bridge territory rather than reconstruction territory.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
3c42766f48 |
BT410 5.3.62: correction -- the fault address was never in our code
Every conclusion that named EulerAngles::operator=(const LinearMatrix&) as the
fault site was wrong, including the disassembly built on top of it. Three
independent proofs:
1. ROTATION.CPP is ours, so the function was instrumented directly -- print
this, &matrix and a local's address on the first twelve calls plus any wild
pointer. The run faulted with ZERO [euler] lines: never called.
2. After the probe was added, 0x66D9 disassembles to a two-byte conditional
jump (jnl) that touches no memory. The dump reports ErrCode 0004, a data
READ.
3. EIP 0x66D9 and cr2 0x7000FA64 are identical to the byte across every build
this session, including builds where the code at that offset changed
completely. A fault in our CODE segment moves when the code moves. This
one does not -- it lives in the DPMI host, which loads low and never gets
relinked.
The map lookup was a coincidence: our _TEXT is section-relative from 0, so its
offsets overlap the host's low addresses, and the map will name a function for
any small number. Before resolving a fault address against the map, confirm it
belongs to our segment -- cheapest check is whether it moves on relink.
What is actually true, by same-binary A/B:
vRIO DOWN, serial1 still a namedpipe -> mission LAUNCHES
vRIO UP, same conf, same binary -> FAULT
The trigger is a LIVE RIO stream, not an unplugged cable. The emulator reports
continuous serial1 RX overruns, the fault lands wherever the app happened to be
(terrain one run, the mech's inside view another), and the shipped binary
survives the identical conditions. A fault at a fixed host address, at
arbitrary points in the app, requiring an interrupt-driven stream, is an
interrupt re-entrancy signature rather than a wild pointer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
d5ae9c4851 |
BT410 5.3.61: the camera never moves -- our mech case skips the engine's eyepoint
Two frames captured seconds apart are pixel-identical while [sim] shows the mech travelling from (180.4, 10, -358.7) to (-604.8, 0, -352.4), with the bridge title reading cam (0.0, 10.0, 0.0) throughout. The scene renders, the eyepoint is never driven. Our own MakeEntityRenderables explains it. When ReadSKLFile finds a Skeleton entry it sets handled = True and the chain to DPLRenderer::MakeEntityRenderables is skipped -- but that is the call which builds the RootRenderable and the DPLEyeRenderable the camera follows (L4VIDEO.CPP:4548-4566). We hand the board a skeleton with no root and no eye. That also closes the loop on the fault: the eyepoint construction we skip is the same code that faults when it runs. The mech case must end up doing BOTH, so the EulerAngles::operator=(const LinearMatrix&) fault has to be understood before the mech can be seen from its own cockpit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9c959e6891 |
BT410 5.3.60: first rendered frame from a running reconstructed mission
emulator/render-bridge/first_mission_frame.png -- arena city, textured buildings, horizon, 28fps, captured from the GL bridge while our build ran a live mission with the mech walking. The skeleton went in with it: [skl] video\mad.skl -> 26 nodes, 19 objects. Corrects yesterday's RIO framing. I called serial1 an unplugged cable. Wrong twice: VRio.App is running on this rig (the dev-rig default since 7/17), and the emulator log recorded real byte counts on that port -- RX overruns of 471, 503, 528 -- which an unconnected pipe cannot produce. The port was LIVE and streaming during every crashing run. So the defect is not that we mishandle a disconnected port, it is that we mishandle a live RIO stream, which is worse: every production pod has a real RIO. The shipped binary on the same rig logs 'lost RIO analog request', keeps sending characters, and runs the mission; ours logs 'RIO never came back from test mode!' and dies partway through the load. The overrun counts say the guest is not draining the port fast enough. L4RIO.CPP's handshake is authentic archive code, but MechRIOMapper (BT/MECHMPPR.CPP) is ours -- that is the thread. pod_render_norio.conf stays the way to get a running mission today, but it is a workaround: it disables the cockpit's primary input device. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
475ae3106e |
BT410 5.3.59: A MISSION RUNS -- the RIO serial port was blocking every load
pod_render_norio.conf is pod_render_rec.conf with one line changed, serial1=disabled instead of the vrio named pipe. Same binary, same egg. With it, our build launches and the mech walks: [launch] state=2 minPriorityEmpty=1 ticks=85316 [queues] p0=- p1=- p2=BUSY p3=- p4=- BTL4Application::RunMissionMessageHandler Turning Plasma Score Display On [sim] pos=(180.466,10,-358.703) yaw=-0.275605 spd=14.4 [sim] pos=(251.959,10,-250.498) yaw=-0.89103 spd=14.4 No fault, 290 log lines and counting. This is the first time the reconstruction has reached a running mission on the pod. The hang was never starvation, a deadlock, or a refill loop -- it was VOLUME, the first candidate I listed and then talked myself out of. 530 renderer events queue at priority 0 during load; BackgroundTasks::Execute runs exactly one task per call round-robin over seven tasks, and the 1ms frame budget is always blown here so no extra background passes happen. That is ~9 events per real second, so a load legitimately takes ~60s. The fault arrived at ~40s, before the backlog could clear. Every 'the queue never drains' reading was really 'the process dies before it can'. The disproof was already in hand: the post tally climbed to 530 and went FLAT, which means nothing was refilling. p0=BUSY with nextReady=1 is equally consistent with a large finite backlog still draining, and I read it as refill. Why the RIO port: the pipe has no server attached, which should behave as an unplugged cable. The emulator log shows steady serial1 RX overruns, our build prints 'RIO never came back from test mode!', and the SHIPPED binary prints 'lost RIO analog request' and launches anyway on this identical rig. So an unplugged RIO is survivable and our handling of it is not -- a real defect, not a rig artifact, since a pod with an unplugged RIO cable should still boot. The wild reference at EulerAngles::operator=(const LinearMatrix&)+0x19 is still unexplained, but it is now reproducible on demand by enabling serial1 rather than being a coin flip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d3b332a62a |
BT410 5.3.58: the load gate is blocked by a REFILL loop on priority 0, not starvation
Per-priority occupancy, measured every second until the fault: [queues] p0=BUSY p1=- p2=BUSY p3=- p4=- nextReady=1 That single line kills the two leading theories. p3/p4 empty means nothing above priority 0 is competing, so the pump is free to serve it -- no starvation. nextReady=1 means PeekAtNextEvent always has a READY event, so priority 0 is not holding a timed event whose alarm never comes due -- no deadlock. What remains is refill: priority 0 is replenished as fast as the pump drains it, ~143 events per simulated second. It is also fully deterministic. Two runs of the same binary reported pump=352/1499 at frame 1001 and 495/2643 at frame 2002, identical to the byte. So 'crashes about half the time' was never a race -- it was comparing runs that differed in binary or conf. Only InterestManager::PostRendererEvent posts at priority 0 (it maps every renderer event there while the app is not RunningMission), so naming the message names the flooder. Application::Post now tallies priority-0 posts by message ID and reports the busiest three. Notably, all three producers of NotifyOfNewInterestingEntity are entity-CREATION paths, so if that ID dominates then something is creating entities in a loop -- which would explain the unbounded growth behind the fault as well as the hang. pod_render_noskl.conf now carries the same probes, so one instrumented binary can be run with the skeleton walk on and off. Walk-off runs are the only configuration of ours known to reach 'Turning Plasma Score Display On'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4cf09917ce |
BT410 5.3.57: the crash is deterministic, and the real blocker is the load gate
The previous commit's attribution was wrong and is corrected in the roadmap. WRONG: 'the crash is the skeleton walk's' rested on 0 crashes in 2 runs with the walk disabled -- a 25% coin flip presented as evidence. The oldest preserved dump has no [skl] line and ends on the old 'couldn't figure out how to MakeEntityRenderables' fallback, so it predates the walk and faults identically. WRONG: 'it lands at different points each time'. Every dump carries the same numbers to the byte (cr2 7000FA64, EIP 66D9). What varies is how far the log gets, not where the fault is. Resolving the address through btl4opt.map names it exactly: EulerAngles::operator=(const LinearMatrix&)+0x19, a read through the matrix reference. A binary scan finds all three call sites of that operator pass a stack local, so the 1.79GB pointer still has no static explanation -- that is now its own open item rather than a guess. Two theories killed cleanly by the new BT_STACK_LOG probe and a binary scan: ESP drift (drift=0 over 2002 frames; exactly one callee-cleans function exists in the whole binary) and an undersized stack (our PE and the shipped one have identical 1MB/8K geometry). What the probe found matters more: the application is parked in state=2, which is LoadingMission, not WaitingForLaunch. Priority 0 is where the interest manager queues renderer events during load, the gate needs that priority empty, and it never empties -- so the mission never launches and the renderer holds a blank screen by design. The fault arrives ~30s into that wait. Runs that DO launch never print a single [launch] line. So 'crashes half the time' and 'hangs during load' are one event seen twice. A 15x disagreement between two clocks looked like a reconstruction slip -- BTL4.CPP passes GetTicksPerSecond() where ApplicationManager wants a frame rate. Checked against the shipped binary before touching it: same instruction sequence, same kind of static float pushed. Authentic. Documented so nobody 'fixes' it. Also swept every subsystem DefaultData against its real C++ base. Fifteen chain past their immediate parent, but fourteen skip only classes that add no handlers and no attributes, and no class's attribute-ID base disagrees with its index chain -- so there are no gap slots there. The one real defect: Generator is a HeatSink but chained to Subsystem::MessageHandlers, so it ignored every ToggleCooling message. Fixed (compiles next build). Tooling: podrun.sh stages the build over BTL4REC.EXE, exports the host-side VPX board env -- without which the run dies at the iserver handshake rather than merely rendering nothing -- and archives every run's log, marking it -CRASH when it faulted. Before this the only preserved dump was an accident, on a rig where each run costs four minutes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f54e1a3c19 |
BT410 5.3.56: the intermittent pod crash is the skeleton walk's -- attributed by same-binary A/B
Added BT_NO_SKL, which skips the skeleton build so ONE executable can be run with and without it. That is the only clean way to test an intermittent fault, and it settles attribution: walk enabled ~50% of runs die (page fault at 66D9) at DIFFERENT points walk disabled 0 crashes in 2 runs shipped exe 0 crashes in 2 runs, same rig and conf So it is our new code, not the rig, and the varying fault point points at memory corruption surfacing later rather than a bad instruction in the walk. Hypotheses killed by reading the private library headers: the matrix array is the right size (dpl_MATRIX is float32[4][4]); and neither s_dplobject nor the common dpl_node header retains an object name, weakening the dangling-string theory (a private name cache inside the library is still possible and is the best surviving suspect). Also checked something that would have been much worse than a crash: ~NotationFile REWRITES its file when dirty, and these are the game's shipped .SKL data files. MAD.SKL is untouched, and ReadSKLFile now carries the engine's own Verify(!IsDirty()) guard. Next tests are listed cheapest-first in the roadmap: keep the NotationFile alive, copy the Object= names, count board allocations, and re-check whether MakeEntityRenderables runs twice per mech. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d66c2b0812 |
BT410 5.3.55: the mech skeleton is built -- .SKL walked into a dpl_DCS tree
BTL4VideoRenderer answers MechClassID: walks the video-object chain the way the engine does, hands every L4VideoObject::Skeleton entry to ReadSKLFile, and recurses the .SKL into a dpl_DCS tree with geometry instanced onto it. Verified repeatedly on the live pod: '[skl] video\mad.skl -> 26 nodes, 19 objects', with no 'wrong video resource type' complaint and no load failures. Those counts are exactly what the file declares (25 joint= entries + root, 19 Object= entries). Corrects the earlier success criterion in this file, which said 22 instances by reading the reference capture's 'instance x22' against DZoneCount=22. Damage zones are not geometry -- 28 dzone= tags spread across 19 objects. Translations are written to matrix[3]/[7]/[11], MUNGA's own AffineMatrix layout. It walks cleanly but no frame has been seen WITH the mech yet, so the slot choice is recorded as unconfirmed. Rotation stays identity by design: every base-pose angle in MAD.SKL is 0 or ~1e-3, so translation alone assembles the model and isolates one convention at a time. AND A CORRECTION I have to flag loudly: I earlier concluded from single runs that non-identity translations crashed the pod, 'isolated' it, and 'confirmed' the alternative also crashed. That was all noise. The same binary re-run gives walk / crash / walk / crash -- the known intermittent plane-write defect is now firing on ~half of pod runs and lands at different points each time, which is precisely what made it look deterministic. Never accept a single pod run as evidence on this rig; require two agreeing runs. That defect is now the top of the list: a run must survive both the skeleton build and the launch to render anything, which at ~50% is a coin flip on a four-minute cycle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fac559bc31 |
BT410 5.3.54: skeleton-walk API inventory complete; staged plan with pixel-free checks
Confirmed every call the .SKL walk needs -- the NotationFile accessors and the MakeEntryList/GetFirstEntry/GetNextEntry idiom (copy DPLReadINIPage's objectpath loop), and the dPL side (NewDCS/SetDCSMatrix/AddDCSToDCS/ AddDCSToScene/SetDCSZone/FlushDCS, LoadObject/NewInstance/SetInstanceObject/ AddInstanceToDCS/FlushInstance). On the matrix: a DCS flush body is [remote][type_check][node][64 bytes] = a 4x4 of float32. Decoding a real BT capture shows a near-identity with a single 10.0 term in the last row, suggesting row-major with translation in row 3 -- but the rows print shifted by one word against a true identity, so the decoder offset is suspect and the convention is recorded as NOT yet proven. Flagged rather than guessed. Staged the work so each half is verifiable without looking at pixels: build the tree with identity matrices first and prove the structure by wire counts (26 DCS + 22 instance flushes, which MAD.SKL's JointCount=25/DZoneCount=22 and the dpl3-revive reference capture agree on), then add real transforms and fix the convention by watching which slot moves. Also noted the one API still to check first: the skeleton reaches us as a non-Object L4VideoObject::ResourceType, so branch on that enumerator rather than re-deriving the filename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a6612d5f75 |
BT410 5.3.53: full spec for the mech-skeleton brick, with an exact success criterion
Everything needed to write the .SKL -> dpl_DCS path is now known and written down: the file format (each page = one node with a local transform, an optional .bgf, damage-zone tags and its child joint pages), the dPL call set (NewDCS / SetDCSMatrix / AddDCSToDCS / LoadObject / NewInstance / AddInstanceToDCS / FlushDCS), and the algorithm matching RPL4VID.HPP's ReadSKLFile + RecurseSKLFile, including where DPLJointToDCSTranslator picks up the joint->DCS mapping afterwards. One unknown is flagged honestly: no surviving source calls dpl_SetDCSMatrix (the RP game file that did is missing, like BT's), so the matrix convention must be derived from the dpl3-revive protocol spec and DPLTYPES.H. The success criterion is exact and needs no pixels: the dpl3-revive reference capture of a real pod decodes as 26 DCS flushes and 22 instance flushes, and MAD.SKL declares JointCount=25 (+root = 26) with DZoneCount=22. A correct walk of that one file should reproduce those counts on the wire. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
15d509c8a8 |
BT410 5.3.52: frame rate measured properly -- the missing mech skeleton is the real gap
The pod updates every few seconds. Measured by sampling a head window every
2s (emulator/render-bridge/headrate.py): OURS changed in 5 of 12 samples,
SHIPPED in 1 of 12. So the reconstruction is not slower than the shipped
binary -- the emulated board is just expensive. Caveat recorded with it:
ours was being driven by the throttle hooks while the shipped exe ignores
them and sat parked, so treat those as same-order, not a win.
What did cost us 3x was mine: BT_MECH_LOG/BT_LAUNCH_LOG write per-frame lines
and DEBUG_STREAM=cout is redirected to COM3, so every one goes through an
emulated serial port. Turning them off took the wire from 480 to ~1480
bytes/sec. pod_render_quiet.conf is the conf to use for timing work.
And a correction to my own earlier framing: wire bytes/sec is NOT a frame
rate. Shipped pushes ~8900 B/s against our ~1480 and the difference is
CONTENT, not speed -- shipped submits the mech and we do not:
OURS: L4VIDEO.cpp wrong video resource type for object mad.skl
SHIPPED: (no such line)
The mech's model resource is a SKELETON. The engine's default
MakeEntityRenderables accepts only Object/Rubble and rejects anything else
with exactly that message, because skeletons are the GAME renderer's job. So
our arena renders but the MECH IS ABSENT, and with it the cockpit interior --
one unimplemented path explaining both the missing model and the lower wire
volume.
Next brick is now precisely scoped: answer MechClassID by reading the .SKL
notation pages into a dpl_DCS hierarchy and hanging the per-node .BGF
geometry off it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
09192367c0 |
BT410 5.3.51: THE 3-D WORLD RENDERS -- reconstruction to emulated board to a picture
emulator/render-bridge/first-3d-frame.png: the arena from the pod -- sky, horizon, ground, the arena structures along the skyline -- at ~31fps on the VelociRender bridge. The whole chain now works in our build: MakeVideoRenderer -> BTL4VideoRenderer over DPLRenderer -> board boot -> Renderer::LoadMission -> DPLReadEnvironment for the art paths -> 40/40 arena objects loaded -> mission launch -> per-frame submission over the wire. The mistake worth remembering: the run was never stalled after InitializePlayerLink. I called that from a 150-second sample and it was just too short. CheckLoadMessageHandler reposts every second and will not advance until the min-priority event queue drains, and with the video renderer in the mission that takes minutes. A BT_LAUNCH_LOG trace showed the queue draining and then both RunMissionMessageHandler calls, the plasma display, and the first sensor tick -- matching the shipped binary line for line. The renderer deliberately holds a blank screen until RunningMission (L4VIDEO.CPP:5100), so 'black' was the app still loading, not a rendering bug. Zero unbuildable entities, zero geometry failures. The frame is static only because the mech is parked with no input, and the camera in the bridge title is the bridge's own viewer, not the game's eyepoint. Also banked the launch-gate trace (BT_LAUNCH_LOG) behind an env var. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
060078e4ae |
BT410 5.3.50: geometry loads -- the bring-up no-op was suppressing the base
DPLRenderer::LoadMissionImplementation (L4VIDEO.CPP:6007) is not empty: it
calls DPLReadEnvironment (which opens L4DPLCFG/btdpl.ini and sets the dPL
object/material/texmap paths from the objectpath= entries) and then
LoadNameBitmaps. The 'bring-up no-op' installed earlier therefore did not do
nothing -- it REPLACED that, so the loader never learned where the art lives
and every dpl_LoadObject returned NULL.
I justified that no-op by pointing at VideoRenderer's bare Tell and
GaugeRenderer's identical one; neither is our base. Check the ACTUAL base
before overriding in this engine and chain it unless there is a reason not
to. DPLReadEnvironment is private to DPLRenderer, so chaining is the only
way a game renderer can reach it at all.
before: 40x 'couldn't load object', 'NULL instance', run ends,
wire ~24 bytes/sec
after: 0 failures, run continues, wire ~12 KB/s (1.17MB climbing),
bridge live at 88fps
Proved it was ours and not the rig by running the SHIPPED exe under the same
conf: it loaded every object. That A/B is cheap and is the right first move
whenever the pod misbehaves.
Still black: the bridge camera sits at its default (0,10,0), so the wire
carries state and object loads rather than a populated scene. The mech and
arena entities still need MakeEntityRenderables bodies.
Rig improvements, both from the user: nosound is fine on the SLOW clock (it
is the FAST SOS clock that needs the AWE32), which saves ~4 min and ~300MB of
audio taps per run; and serial3=file + '> COM3' gives a live unbuffered log,
so a run that does NOT crash is finally readable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
ed7843b89f |
BT410 5.3.49: first renderable answer -- BTPlayer, and the pod stops complaining
BTL4VideoRenderer now overrides MakeEntityRenderables. Class 3035 resolves to BTPlayerClassID (the BT enum block starts at 3000 in VDATA.HPP), and a player carries no graphics -- exactly what the engine already does for its own PlayerClassID with an empty case. Everything else chains to DPLRenderer, so the override can only add answers. On the pod: the 'couldn't figure out how to MakeEntityRenderables' complaint is gone and the run no longer exits, it keeps running. It still renders nothing -- the wire fifodump grows at ~24 bytes/sec, keep-alive rather than a frame stream -- which is expected, since only the player has been answered and the mech and arena still have no renderables. Banked a practical problem worth fixing before the next session: a pod run that does NOT crash produces no readable log, because the conf redirects the game's stdout and DOS buffers it. Every readable log this session came from a run that crashed and had its buffer flushed by the fault handler. The RC.TXT marker (a separate command) survives that, and the com3/serial3=file trick from the emulator notes would give live unbuffered output -- worth wiring in, otherwise progress is invisible precisely when things are going well. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
32023a918f |
BT410 5.3.48: watcher ladder complete; the engine names the next brick
With every authored watcher resolving, the pod run reaches the mission, draws the FULL COCKPIT with the board booted and audio running, and exits GAME-RC=0 with no Fail. It builds no 3-D scene, and the engine says why: Entity 1:1 class3035 couldn't figure out how to MakeEntityRenderables L4VIDEO.cpp couldn't load object sky.bgf (then the whole arena) NULL instance VideoRenderer::MakeEntityRenderables (VIDREND.CPP:231) is the BOTTOM of a virtual chain -- its comment says so outright -- and BTL4VideoRenderer does not override it, so every entity falls through, no scene graph is built, and the geometry that would hang off it never loads. CORRECTS an earlier note in the roadmap: I recorded the first full-rig run as proving '.BGF loading was never a reconstruction problem, only a missing bridge', because the complaint count was zero. It was zero because the run Failed at a watcher long before reaching geometry. Now that it gets there, the loads are attempted and fail -- for want of renderables, not the bridge. Next brick is the real btl4vid body, shape pinned by the surviving sibling header RPL4VID.HPP: override MakeEntityRenderables, plus ReadSKLFile / RecurseSKLFile to walk the .SKL skeleton pages into a dpl_DCS hierarchy and load the .BGF geometry per node. Renderable content from the BT411 donor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4168f1ad4d |
BT410 5.3.47: the whole authored watcher set is published -- cockpit renders in the pod
Ladder rungs, each run-verified on the pod rig: ReportLeak (HeatSink), GeneratorOn (Generator -- member existed, publication missing), ConfigureActivePress (MechSubsystem -- likewise), AmmoState + FireCountdownStarted + the rest of the AmmoBin table. THE PINNED RANGE IS NOW THE AUTHENTIC SHAPE. Publishing ReportLeak on HeatSink and ConfigureActivePress on MechSubsystem shifts the chain down two, and MechWeapon's bridge to its binary-pinned PercentDone (0x12) collapses from THREE guessed pads to ONE -- which is exactly the arithmetic the shipped string pool predicts (MechSubsystem 1, HeatableSubsystem 3, HeatSink 6, PoweredSubsystem 5). Three pads of guesswork replaced by a real attribute and a real base-class row. HeatableSubsystem and Torso rebase onto MechSubsystem::AttributeIndex; MechControlsMapper does not (it derives from Subsystem directly). Types were chosen deliberately this time, per the AudioWatcher families the engine instantiates (Motion / Hinge / Scalar / StateIndicator): every *State name resolves to a StateIndicator, ReportLeak is a Scalar. RESULT: the pod run now gets past every authored watcher and DRAWS THE FULL COCKPIT -- sensor cluster, myomers, cooling, the weapon panels, kills/deaths -- with the board booted and audio running. It then exits without flushing its redirected stdout, so the exit reason is not yet known; next step is a run with the redirect removed so the console is readable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
afb9e3f5d9 |
BT410 5.3.46: the Reservoir crash solved -- *State names must be StateIndicators
The linker map named the faulting function: the crash address minus the CODE base (0x410000) looked up in btl4opt.map's Publics-by-Value landed inside AudioStateWatcher::AudioStateWatcher +0x2D. AudioStateWatcher is AudioWatcherOf<StateIndicator> and its ctor immediately runs Cast_Object(StateIndicator*, attributePointer)->AddAudioWatcher(this). So every authored *State name must be published as a StateIndicator -- AlarmIndicator counts, it derives from one -- and pointing one at a plain int sends that member call through garbage. That explains both earlier failures: the AlarmIndicator attempt was the right type but was tested with other bugs still in the batch, and the plain-int attempt was simply the wrong type and crashed further along. Published as state objects: Reservoir/ReservoirState -> reservoirAlarm, Generator/GeneratorState -> stateAlarm, plus new StateIndicator members for Condenser/CondenserState and Torso/MotionState. StateIndicator has no Initialize(); the default ctor suffices because the watcher only needs the object to exist. Verified on the pod rig: no crash, ladder advanced to ReportLeak. Technique worth keeping: on an extender fault, subtract 0x410000 from the dumped address and look it up in btl4opt.map -- it names the engine function, and for this family of work that names the watcher class and hence the required member type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0d97d8c38b |
BT410 5.3.45: six more watcher names published; the Reservoir one CRASHES (bisected)
Published and verified green on the pod rig: Condenser/CondenserState, Generator/GeneratorState, Emitter/LaserOn, ControlsMapper/TargetRangeExponent, plus the Torso and Myomers tables from the previous pass. Reservoir/ReservoirState CRASHES the pod on the DOS extender, and a bisect pins it to exactly that change: revert it and the run returns to a clean Fail, re-apply it alone and the crash returns. It is reverted; the tree is green and the ladder is blocked there. The important lesson is general: AttributeWatcherOf<T> does currentValue = *(T*)attributePointer AT CONSTRUCTION, so a published name is read the moment its watcher is built. My earlier staging note claimed the provisional types could not matter because nothing drives the values yet -- that is wrong, and this is the counterexample. Ruled out by measurement and recorded so they are not retried: the AlarmIndicator-vs-int type (a plain int got further, 232 -> 749 bytes of log, but still crashed), static-init order (reservr.obj sorts last, after heat), an id gap (contiguous at HeatSink::NextAttributeID), and the gauge rig (same binary runs clean there -- only the pod/arena context faults). Crash signature for whoever picks it up: 0044B4AD, mov eax,[edx+0x18] then call [eax+4], EAX=0x15, fault at 0x19 -- a small integer called through as an object, i.e. the AttributeIndexSet::Build uninitialised-slot pattern. Condenser is the control: same base class, plain int, no crash. Also fixed on the way: staged members must be appended at the END of a class (offset-sensitive readers exist -- condenserNumber is reached as master+0x1d4) and never added to a resource struct, which is a wire format. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3ecd88aaef |
BT410 5.3.44: read the whole watcher list out of the resource; Torso MotionState
BTL4.RES stores each watcher as a (SUBSYSTEM, ATTRIBUTE) string pair, so the complete list can be extracted directly instead of discovering one name per 6-minute run. Banked the extraction method and the full normalised table. Sixth rung climbed: Torso/MotionState (staged member, appended at the end of Torso's own range so no downstream id moves). LocalVelocity and LocalAcceleration turn out to need nothing -- the engine's Mover already publishes them. Also banked, and deliberately NOT acted on: ReportLeak and ConfigureActivePress are base-class attributes inside the pinned range, and chasing them turned up what looks like the authentic attribute layout. The shipped pool gives MechSubsystem 1, HeatableSubsystem 3, HeatSink 6, PoweredSubsystem 5 -- which counted from Subsystem::NextAttributeID=2 lands MechWeapon's real ids on the binary-pinned 0x12 with EXACTLY ONE pad, where our tree needs three. That arithmetic is strong evidence for the original layout, and reconciling to it would replace three guessed pads with the real table. But it moves ids underneath a cockpit currently verified at 98.9% pixel-identical, so it wants a deliberate session with the A/B rig open rather than a bulk edit at the end of a long one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
14da9489c2 |
BT410 5.3.43: full pod rig -- geometry loads, ladder advances to the audio watchers
Under the documented rig (launch_pod.ps1 with the GL bridge up) the 'couldn't load object' count went to ZERO. .BGF loading was never a reconstruction problem, only a missing bridge. What blocks now is a series of authored AttributeWatchers: BTL4.RES binds them BY NAME and the engine Fails outright on any that does not resolve, so each is a name our subsystems must publish. Five rungs climbed, each run-verified: UnstablePercentage, SpeedEffect (Myomers table), AnimationState + CollisionState/CollisionNormal + ReduceButton (Mech), and the full Torso table (all six authentic names mapped onto existing members). The method that makes this cheap is banked: the shipped binary's string pool carries each class's attribute names CONTIGUOUSLY in ID ORDER after the class name, and in every case so far our member declarations sit in the same order -- which confirms the layout and lets ids be pinned rather than guessed. Intersecting those names with BTL4.RES gives the exact work list. A THIRD miswired SharedData found on the way: Myomers carried Subsystem's tables where it derives from PoweredSubsystem, the same defect as MissileLauncher (5.3.33). Worth a sweep across every subsystem. Staged member TYPES are provisional and documented as such: AttributeWatcherOf reads *(T*)attributePointer and the instantiation comes from the resource, which the string pool does not reveal. Nothing drives them yet, so settle the types with the models that write them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
91b043778e |
BT410 5.3.42: the 3-D pipeline runs to the geometry load -- two rungs climbed
RUNG 1: BTL4VideoRenderer::LoadMissionImplementation, Fail -> bring-up no-op. Legal, not a cheat: the engine's own VideoRenderer version (VIDREND.CPP:259) is a bare Tell and our working GaugeRenderer ships the same, so the renderer comes up and runs its frame loop with an empty scene -- exactly what we want to measure before writing content. The authentic shape for the real body is pinned by the surviving sibling header CODE/RP/RP_L4/RPL4VID.HPP (walk the mission entities -> MakeEntityRenderables -> ReadSKLFile/RecurseSKLFile). RUNG 2: Mech::EyepointRotation published. The engine then Failed at SetupCull, which fetches that attribute BY NAME off the viewpoint entity and composes it with the siteeyepoint segment to build worldToEyeMatrix. The mech already HAD EulerAngles eyepointRotation with a getter -- only the publication was missing. One enum id, one table row. The run now reaches the full mech build, constructs the renderer, boots the board (~907K VPX wire transactions), loads the mission, runs the per-frame cull and enters geometry loading with ZERO Fails and zero exceptions. It stops at 'couldn't load object buttee.bgf' / 'NULL instance' -- and that is not a defect: the models are present and BTDPL.INI points at them, but .BGF loading goes through the board to the external GL render bridge that the VPX HLE tees over VPX_FIFOSOCK, and that bridge was not running. 'NULL instance' comes from the prebuilt LIBDPL.LIB refusing to instance an object that never loaded. Next rung is the documented full rig (render-bridge/launch_pod.ps1). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1b204de6d3 |
BT410: the 3-D path runs to mission load -- btl4vid's next brick is named
Run against the emulated Division card, our build reaches the mission scene
load and stops at the one method we stubbed:
btl4vid.cpp(42): BTL4VideoRenderer::LoadMissionImplementation
-- btl4vid.cpp not yet reconstructed
That proves MakeVideoRenderer, the BTL4VideoRenderer ctor over the engine's
DPLRenderer, the board boot (transputer + i860 firmware, ~858K wire
transactions through the VPX HLE) and Renderer::LoadMission all work in our
build. btl4vid is not a from-scratch climb; the engine half was always
linked and what is missing is the mission-load hook and its renderables.
Banked the reproduction rig (emulator/vidtest.conf + the host VPX env --
VPX_RESPOND alone is not enough, the iserver handshake dies without
VPX_RENDER), the authentic contract from CODE/RP/MUNGA/RENDERER.CPP:263, and
a note that the BT411 donor restructured this hook so the contract comes from
the 1995 engine and only the renderables come from the donor.
Also corrected today's earlier note: the transient 3-D frame seen during a
gauge run was NOT the dPL path. MakeVideoRenderer returns NULL without
DPLARG and no gauge conf sets it, so no video renderer exists in those runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
50341b4a66 |
BT410 5.3.41: the plane-write hunt -- two more hypotheses dead, defect bounded
PlayerStatus (dirty-gated, blits an 8-bit pixmap -- but the widget map for a DIRTY run contains no playerStatus at all; it belongs to cameraInit and never exists in a mech cockpit) and heat-driven redraw (a BT_FORCE_FIRE run came back clean). Seven hypotheses killed by measurement so far. It is a heisenbug: dirty on ~3 runs in 25, and every run since instrumenting has been clean. The pattern tracks STARTUP LOG VOLUME -- the three dirty runs all had the lighter conf, and a chatty trace slows init enough to suppress it. The blit trace is therefore narrowed to fire only for pixmaps landing on a non-palette port, which is near-silent when nothing is wrong. That narrowed trace caught real traffic: 17x17 PixelMap8 blits onto Mfd1/2/3 -- 8-bit pixmaps on single-bit planes. They render correctly, so the library thresholds them; not the culprit but the right bug class. Also banked a hunting recipe, because two detector traps cost runs: Eng1 == 0 means the cockpit is not up (not dirty), and an early capture during mission start caught a full 3-D cockpit frame with the gauges at pod positions (Eng1 62927) that did not recur over the next minute. BTDPL.INI is in the mount and DPLRenderer is linked, so the dPL path may emit a frame during launch even with L4VIDEO=OFF -- first evidence the 3-D path produces anything, and explicitly NOT a claim that btl4vid works. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
90daf5e9b7 |
BT410: bank the plane-write hypothesis ledger and the instrument to use next
Four hypotheses tried and killed by measurement, recorded so the next session does not repeat them: misrouted eng port (traced -- resolves correctly), OneOfSeveral's DrawPixelMap8 branch (only OneOfSeveralPixInt uses it, and all four instances are on the 6-bit colour port where it is correct), an 8-bit value overflowing a 6-bit field (the bits are far too high), and a displaced copy from elsewhere in the buffer (the rows appear nowhere else). Observed structure: constant low byte, varying high byte -- one field written correctly while another is garbage. The next move is an instrument, not a fifth hypothesis: trace at the VIEW layer and log every draw whose absolute position lands in the artifact rect, with port, colour and call site. The rect is tiny so the log is short. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c8350408fc |
BT410 5.3.40: correction -- the 'exact' heads are exact only on lucky runs
Same binary (md5 identical), different run: Eng1/Eng2/Comm read 17981/17981/15656 on some runs and 19011/19011/16909 on others. The shipped binary is STABLE across three runs, so this is ours. Eleven consecutive clean runs is what let it hide, and the dirty numbers are exactly the pre-gate numbers from 5.3.32 -- the same artifact recurring. Read off the raw words rather than inferred: in the artifact rect the shipped binary writes ONE masked plane bit per pixel (0x4000 = Heat, 0x002D = sec), while we write arbitrary 16-bit words (0x8BD6 / 0x8AD6 / 0x8AC0) that light sec+overlay+Mfd1+Eng1+Eng2+Comm at once. Raw values going into a plane-packed buffer -- which is why one bad draw appears as extra pixels on six heads and as 483 MISSING on Heat at the same time. Intermittent because these widgets are change-driven: on runs where the coolant value never moves after the first paint, the bad draw never happens. The six fixes of 5.3.33-39 stand (each moved specific attributable pixels). What must be restated is the claim that four heads are exact -- true only when this defect does not fire. The rig now needs a run-variance check before any per-head number is believed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b6426388de |
BT410: correct what the sec head is -- the colour radar, not the 3-D scene
I had it recorded as 'the 3-D scene head, this is btl4vid'. It is not. Port 0 carries map / headingPointer / digitalClock / numericSpeed / messageBoard / the armour colour-mappers / the four GeneratorClusters, and its plate shows the radar scope, the SPEED / HEADING / MISSION TIME dials and the ARMOR DAMAGE diagram -- the pod's colour screen, the one the VDB splits out alongside the five mono MFDs. So sec is ordinary gauge work and is now the largest single head delta. btl4vid is a separate deliverable this rig cannot measure at all: the 3-D main view never enters the gauge framebuffer -- it goes down the dPL path to the render board, and these confs run L4VIDEO=OFF, so it is not even being produced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
540c41e497 |
BT410: correct the leak-gauge note -- both of my guesses about it were wrong
The CFG parameter order is fine (Make already maps third/frames by name, with a comment saying so), and we do not paint nothing: cropping the plane shows both binaries draw the hatch, ours about three columns narrower. So it is a level/frame-width detail, and most likely the same root as the coolant numerals -- our coolant value differing by a step. Folded the two into one thread and pointed it at the value rather than the widget. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ce199103a9 |
BT410: session landing -- the cockpit at 98.9% identical, four heads exact
Final per-head state, the binding audit (31 CFG bindings resolve, zero NULL), and the open list in priority order: btl4vid for the sec head, then the leak-gauge hatch (with the CFG parameter-order question that probably explains it), the coolant numerals, and the MFD remainders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fc0cafe867 |
BT410 5.3.39: the Comm page goes exact -- the own row and its name raster
Two things, both read off the shipped framebuffer.
SLOT 0 IS THE OWN ROW. Its layout entry is the odd one out -- {180,225} at
layout mode 0, KILLS/DEATHS at +0x95/+0xe8 instead of the strip boxes'
+0x48 -- and the shipped Comm page fills it with the LOCAL pilot while the
seven surrounding boxes stay empty in a solo mission. So slot 0 takes the
mission player directly and only slots 1..7 come from the pod roster. That
also explains the artifact this session opened with: the ungated walk was
feeding the mission player into the STRIP slots, which is why gating the
roster removed wrong pixels but left the centre row missing.
THE OWN ROW TAKES THE LARGE NAME RASTER. Mission carries both; we asked for
the small one, so 'Aeolus' drew visibly undersized and low in the box. The
strip slots keep the small raster -- their boxes are half the height.
Comm missing 2099 -> 733 -> 0 extra 0 -> 152 -> 0
The Comm head is now EXACT: 15656 pixels against the shipped binary's 15656.
Four heads exact (Eng1/2/3, Comm), the three MFDs within ~150px, and the
whole cockpit at 98.9% identical / 99% coverage -- from 89.7% / 92% when the
session started.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
377aec92b0 |
BT410: the MFD wave landing report (5.3.33-38)
Where the cockpit stands per head, the five defects the rig found and the order they fell, and the measurement lesson: a large overlapping error makes a small one measure as already-optimal, which is exactly what I recorded for the title banner before the strip art was fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
44aa7a349c |
BT410 5.3.38: SegmentArc270 never applied its 270 degrees
The class computed segmentSpan = 0.75 in its ctor and nothing ever read it: the engine SegmentArc base has no span member, and SegmentArc270 declared no Execute, so the recharge dial lit all 20 segments at PercentDone 1.0 where the shipped cockpit lights 15 and leaves the quarter gap. That was the last MFD delta -- four extra spokes at the upper-left of every weapon disc. A derived Execute is the only place the 0.75 can reach the draw, which is the same role SegmentArcRatio's Execute plays with its own copy. It scales the raw fraction, draws, then restores: the connection only rewrites currentValue when its source changes, so scaling in place would compound frame after frame and walk the dial to zero. head missing extra Mfd1 140 (=) 679 -> 7 Mfd2 97 (=) 462 -> 14 Mfd3 31 (=) 461 -> 13 Cockpit: 98.2% identical / 97% coverage. The three MFD heads and the three engineering heads are now within a few dozen pixels of the shipped binary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
945812fef1 |
BT410 5.3.37: the panel title banner was off by exactly one row
y+0xb3 -> y+0xb4. Earlier this session I measured 0xb3 as the optimum and wrote that the banner was at most 1-2px off -- that reading was confounded: the strip-art misplacement (5.3.35) was ten times larger and overlapped the same bands, so it dominated the metric and hid the one-row error. With the strip art correct the title fringe isolated cleanly at 553/553 missing/extra per head, and +1 collapsed it. head missing extra Mfd1 1107 -> 140 1646 -> 679 Mfd2 1616 -> 97 1981 -> 462 Mfd3 584 -> 31 1014 -> 461 Cockpit: 97.9% identical / 97% coverage, from 89.7%/92% at the start of the session. Method note for the worksheet: fix the largest error on a head FIRST, then re-measure the small ones -- a big overlapping defect makes a small one measure as noise, or as already-optimal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6a3cf06ece |
BT410 5.3.36: the cooling-loop lamp goes live (OFF -> the loop digit)
CoolingLoopConnection::Update wrote a constant 0 -- the documented inert stub -- because HeatSink::coolantAvailable, HeatSink::linkedSinks and Condenser::condenserNumber are protected with no accessor, so every MFD panel showed OFF where the shipped cockpit shows the loop number. heat.hpp now publishes GetCoolantAvailable(), ResolveCoolingMaster() and GetCondenserNumber(), and the connection follows the binary: a sink with coolant reports the loop number of the Condenser it is plumbed to, otherwise frame 0. Same shape as PowerSourceConnection beside it, which was already live and already matched. The box now reads 4 / A against the shipped binary's 4 / A, pixel for pixel. head missing extra Mfd1 1331 -> 1107 1776 -> 1646 Mfd2 1908 -> 1616 2196 -> 1981 Mfd3 730 -> 584 1119 -> 1014 Cockpit: 96.6% identical / 96% coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
287695fd3b |
BT410 5.3.35: the MFD strip art belongs in the middle of the quadrant
image_names[auxScreenPlacement] (qblh0..qblh6) is the mech diagram with THIS subsystem's segment lit, and SubsystemCluster drew it at the panel origin. Shipped puts it in the quadrant's middle. Solved from the framebuffer rather than guessed: our extra pixels clustered at panel x 0..70 against shipped's missing at x 140..230, and a cross-correlation of the two sets peaked at dx=+144 dy=-54 independently on all three panels of the head. +144 is 0x90 exactly; y is bottom-up in this space, so up the screen is +0x36. head missing extra Mfd1 4908 -> 1331 3960 -> 1776 Mfd2 6870 -> 1908 5280 -> 2196 Mfd3 3091 -> 730 2565 -> 1119 ~10.9K missing and ~6.7K extra pixels recovered. The whole cockpit is now 96.4% pixel-identical / 95% coverage against the shipped binary, from 89.7%/92% at the start of the session -- and the MFD weapon panels are visually indistinguishable apart from the state box, which still reads OFF where shipped shows a digit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
30e4b5885d |
BT410 5.3.34: the weapon ammo readout goes live (0024)
BallisticWeaponCluster's two round counters pointed at UnboundIntegerSource because ProjectileWeapon::ammoBinLink is protected -- the documented inert gap in the ctor ledger. ProjectileWeapon now exposes GetAmmoCount() (the resolved bin's rounds, -1 with no bin) and the cluster copies it into a live cell each Execute, the same pattern it already uses for jammed and reloadSeconds. The binary pointed its NumericDisplayInteger straight at the resolved bin's count. The STREAK 6 panel now reads 0024 against the shipped binary's 0024, in the same font, stencilled into the fire-ready disc the same way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
341a503641 |
BT410 5.3.33: MissileLauncher was wired to the wrong shared-data tables
MissileLauncher::DefaultData passed Subsystem::MessageHandlers and Subsystem::AttributeIndex where every sibling weapon passes MechWeapon's -- and a launcher publishes no tables of its own, so it must chain MechWeapon's exactly as ProjectileWeapon (its own base) does. Wired to Subsystem's, EVERY weapon attribute on a missile launcher resolved to nothing and the gauge read the unbound zero cell: PercentDone, TriggerState, WeaponState, DistanceToTarget, RearFiring. The cockpit is what found it. The shipped binary lights a fire-ready disc on the STREAK 6 panel (WeaponCluster gates it on PercentDone >= 0.99) and ours never did -- while the launcher itself reported state=Loaded recoil=0 level=1 bin=24. A healthy weapon behind a dead binding. head missing before -> after extra Mfd1 8807 -> 4991 4063 -> 4003 Mfd3 6935 -> 3174 2659 -> 2608 Mfd2 6882 -> 6870 (unchanged -- it hosts no missile panel) ~7.6K missing pixels recovered. The disc, its rays and the stencilled ammo cells now draw; the digits themselves stay blank pending the ammo feed, which is the documented inert gap in the BallisticWeaponCluster ledger. Traces added, all env-gated on BT_VIS_LOG and kept as tooling: [warn] (cluster percentDone/warn state) and [proj-state] (launcher state, recoil, recharge rate, bin count) -- the pair that separated "broken weapon" from "broken binding" in one run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
883315a647 |
BT410 5.3.32b: the weapon-panel work list, measured
With the engineering heads exact, the entire remaining cockpit difference is the three MFD quadrant heads -- the SubsystemCluster panels, which the widget map shows are built by vehicleSubSystems rather than by the CFG, so they are our code end to end. Banked with evidence: the ammo weapon's disc+rays are absent while both energy weapons draw theirs; the ammo digits render as empty cells at exactly the right position; the mech silhouette is misplaced low-left; the state box reads OFF where shipped shows a digit. Also settled by experiment that the title banner is NOT misplaced: 0xb3 is the measured optimum (0xb6 and 0xb0 both score worse), and the equal missing/extra counts are overlap fringes, not an offset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
740ef2b0a7 |
BT410 Phase 5.3.32: the per-head A/B -- three engineering heads now exact
The gauge framebuffer is PLANE-PACKED: L4GAUGE.CFG gives each port a bit
mask, not a rectangle, so all six heads share the same x/y and are separated
only by bit -- the packing the VDB splits into the pod's physical screens.
Comparing RGB was therefore measuring nothing useful; the "magenta artifact"
just meant the wrong heads were lit at that pixel.
New instruments (emulator/render-bridge/gauge-ab/, with a README):
planes.py per-head scoreboard -- shipped vs ours, missing vs extra, per
port mask, recovering the 16-bit word from DOSBox's RGB565
BT_VIS_LOG a complete cockpit widget map from the single dispatch every
gauge widget is built through (MethodDescription::Execute),
printing name + port + authored position
ab.sh stages the fresh build over BTL4REC.EXE and kills any running
DOSBox before launching -- see below
The fix: the Comm page's pilot roster is the POD roster (the viewpoint mech's
ControlsMapper pilot array, mapper attributes 15/16), not the mission's
player list. It is zero in a solo mission, so the shipped page draws empty;
ours resolved the local player and painted its icon in colour 0xff. The gate
belongs in the row source, not PilotList::Execute -- Execute's empty branch
still has to run, because erasing is what the shipped page draws.
head shipped ours missing extra (was)
Eng1 17981 17981 0 0 (0 / 1030)
Eng2 17981 17981 0 0
Eng3 17981 17981 0 0
Comm 15656 13557 2099 0 (2099 / 1253)
Heat 21374 20913 566 105 (566 / 990)
Title-band magenta 404 -> 0. The 2099 Comm pixels we are missing were
missing before the gate, so it is a strict improvement.
Method note, which cost more than the bug: the rig's confs run BTL4REC.EXE
out of the mount while the build writes build410/btl4opt.exe, and nothing
connected the two. Three measurements ran an hour-old binary, so a change
that "did nothing" three times had never been tested -- and the correct first
hypothesis was discarded on that evidence. ab.sh now re-stages every launch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
c6cafadb28 |
BT410 Phase 5.3.31: traced the stray blocks -- PilotList, not PlayerStatus
Three rounds of source inspection produced three wrong suspects; one
instrumented run settled it:
[pl] PilotList slot=0 at (180,225) <- the only drawer
(no [ps] lines at all)
So the magenta blocks over the top-left title are PilotList's slot-0 entry
(the local pilot's name art + mech icon) on the Comm port -- content that
SHOULD draw; what is wrong is the entry's coordinate convention inside that
port. The port geometry itself is proven right: the Comm background
(btcomm.pcx, the KILLS/DEATHS columns) lands pixel-identical in both exes.
Second, separate finding: PlayerStatus never draws at all. The cfg builds
8 of them (sec port, 4x2, players 1..8) but our roster resolve is 0-based
against 1-based cfg player numbers, so every panel stays unbound and its
Execute returns early. Fixing that will add a whole scoreboard page, so it
belongs in an A/B session against the shipped reference rather than a blind
edit -- both leads are written up in GAUGE-BLOCK.NOTES.md.
Also recorded: the capture rig's occlusion failure mode (CopyFromScreen
grabs whatever is in front -- one run scored 77%/47% purely because an
editor covered half the window) and the PrintWindow fix for unattended runs.
Verified build, zero faults; last valid A/B measurement stands at 94.7%
identical / 92% coverage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
10e1a70055 |
BT410 Phase 5.3.30: the pilot name resolves -- and the magenta boxes diagnosed
The name art was never missing. The egg carries it as notation pages of hex rows ([BitMap::Small::Aeolus]); the engine BitMap has a ctor that reads exactly that format; and the AUTHENTIC Mission ctor already loads them into smallNameBitmapChain (MISSION.CPP:525 -- implemented, called, working). The break was ours: btl4gau3's LookupPlayerNameBitmap was a bring-up stub returning NULL. It now uses the authentic API (GetCurrentMission()-> GetSmallNameBitmap(playerBitmapIndex)) and resolves live -- traced index=1 to a real BitMap, with the fallback-box branch provably never firing. Coverage 91% -> 92%. DIAGNOSIS CORRECTED for the stray magenta blocks over the top-left title: they are NOT a missing-name placeholder. They are the REAL name bitmap drawn at the wrong slot coordinates -- PilotList lays its 8 roster slots out from the PE-recovered DAT_0051af88 (x,y,mode) table, and ours puts them top-left instead of the shipped exe's centre row. Recovering that table is the next cockpit thread; the egg-hex -> live-BitMap pipeline is verified end to end, so what remains there is layout, not art or lookup. (A duplicate loader written before finding the authentic one was removed -- CODE/RP/MUNGA/MISSION.CPP already does this job.) Gauge fight 11/11, smoke and novice clean, zero faults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
42ff5c3856 |
BT410 Phase 5.3.29: the panel titles -- 94.8% identical to the shipped cockpit
Reading the art settled where the titles live: qsensors/qmyomers/qermlas/ qppc/qstrk6.pcc are 299x26 TITLE BANNERS, while the qblh0..7.pcc strings the cfg hands vehicleSubSystems are 77x120 MECH ICONS (already lit by the 5.3.28 placement fix). So the authored auxScreenLabel IS the panel title -- it just belongs on the MFD panel, not only the ENG page. SubsystemCluster now builds a titleBanner child from the cached label. Placement was MEASURED against the shipped framebuffer through the A/B rig (BackgroundBitmap places by left/BOTTOM): a bring-up BT_TITLE_DY knob, then a green-pixel correlation over the title band, then baked at x+0x09, y+0xb3 from the panel origin. "SENSOR CLUSTER", "MYOMERS", "TYPE II 65 TONS" and both "ERMED LASER RANGE 500M" banners now render where the shipped binary puts them. Score progression vs the shipped cockpit on identical GAUGE content: 5.3.26 birth 90.1% identical / 82% coverage 5.3.28 authored screens 93.2% / 85% 5.3.29 titles tuned 94.8% / 91% Also: grab.ps1 now focuses the target window before capturing -- the screen-copy grabbed whatever was in front, which cost two junk captures (a white frame and an unrelated 3-D app window). Gauge fight 11/11 rounds and smoke both clean, zero faults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
24ea06574c |
BT410 Phase 5.3.28: the visual A/B pass -- 93% pixel-identical to the shipped cockpit
Ran the reconstructed exe and the shipped ALPHA_1 binary against the SAME GAUGE content, same mount, gauges on, and diffed the 640x480x16 framebuffers. OUR EXE DRAWS THE COCKPIT: 90.1% pixel-identical on the first run, and every difference traced to a single root cause. The authored aux-screen block (screen number / background placement / label) lives in the subsystem resource -- which is freed with the Mech ctor's stream buffer (the 5.3.24 use-after-free landmine). Both this tree and the BT411 port had stubbed it, which is why BT411's displays show the same artifacting. PoweredSubsystem now caches the block at ctor time with accessors, and btl4gau2 uses the AUTHORED screen instead of a roster-order stand-in and builds the placement strip art. Panels snapped onto their authored screens -- weapon dials now land on the same panels as the shipped exe, mech icons appear on the sensor/myomer panels, generator letters correct -- taking the match to 93.2% identical / 85% coverage. Remaining differences are donor-inherited stubs: the panel title art (now proven to come from the q-strip statusImage path, not auxScreenLabel -- the label blit is wired and guarded regardless), the pilot name bitmap, and some lamp frames. The A/B rig is banked at emulator/render-bridge/gauge-ab/ (both confs + the window-grab script) so the comparison is repeatable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b615f319db |
RIO: decode the pod-bus I/O architecture end to end
New IO-ARCHITECTURE.md, derived from the v4.2 image, the U7 GAL decode and schematic sheets 1/3: - $A010 control-latch bit map (CCK / D_OUT / WR_SB / RD_SB / SEL0-2) - the 50-byte script format the firmware replays, and the ROM table map - full port/address population: 9 buttons boards + 2 keypads across all 8 ports; the 0x00-0x6F logical map and its 0x48-0x4F gap - the keypad engine ($CC53/$CC7E): 4-row matrix scan, row-patched RAM scripts, the $DC14 key-code table, message type $8B - lamp readback ($21C2) and the 72-byte lamp-fault mask at $DFA8 -- fault reports are type $03 with the lamp index in $2519 - scan cycle budget: 1.91 ms/pass, 17.3 ms/scan, ~58 Hz; demux is 60% - expansion: one spare buttons board, and nothing further without a protocol revision Corrections to existing docs: - $CC53 was cited as the encoder sweep; it is the keypad-1 scanner. The sweep is $C8CC-$C9A7, driven from $C0CB. - U31 is on sheet 3, not sheet 1, and is completely uncommitted: no address, data, strobe or pod-bus signal reaches it. Not an expansion hook -- populating it does nothing without new wiring. - No spare HCTL-2016 footprints exist. The decode has 3 free selects on U9, but the PCB carries exactly 5 positions and sheet 1 draws 5. More analog axes need hardware, not firmware. Also carries the previously-uncommitted standard-rate (16550) feasibility analysis and baudscan.py, plus a note that the FastRIO "FTDI-class adapter" rule really means arbitrary-rate generation: CP2102N qualifies, classic CP2102 snaps 31250 to 38400. Bench-measured 2026-07-26; on-cockpit latency check still pending. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |