4a38fba2c920899dd260ff7df211d7aa0c060bb0
137
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2edde29671 |
Fix 1 (Gitea #12): disasm-exact ProjectileWeaponSimulation @004bbd04 + the dirty-bit sweep
Replace the RivetGun-modeled ProjectileWeaponSimulation body with the fully recovered binary machine (capstone disasm, every branch address-cited): - gate 1 @4bbd36: simulationState@0x40==1 (destroyed) || heatAlarm==FailureHeat || owner disabled -> recoil=rechargeRate + alarm 7 (was: simulationFlags==1) - gate 2 @4bbd71: bin alarm 2/3 or bin destroyed -> alarm 7 re-pinned per frame - Loaded @4bbec2: DENIED shots (viewFireEnable off / no owner target) blip SetLevel(4);SetLevel(2) and STAY LOADED, no ammo pull (the old FireWeapon early-returns under an unconditionally-cycling caller faked a full firing pip cycle -- the "missiles cycle but never launch" incident mechanic); the ammo pull is bin->FeedAmmo @4bbee6 in the CALLER; both updateModel|=1 marks (@4bbf05/@4bbf4c = ForceUpdate) bracket FireWeapon; recoil set @4bbf51 - Loading @4bbdd2: recoil bleeds ONLY here at electrical Ready and CLAMPS at 0 (it ran to -114 before); bin Loaded -> weapon Loaded - 7 @4bbe4d: binary-faithful roach motel (re-asserted unconditionally; recovery = ResetToInitialState only) - slot 17 @004b9c9c reconstructed as MechWeapon::ComputeOutputVoltage (rechargeLevel=(rechargeRate-recoil)/rechargeRate, Emitter overrides with @4ba738) and called from Loading/7 -- the launcher recharge dial ANIMATES (the old "authentically static" claim was wrong) FireWeapon bodies stripped to heat+spawn only per @004bcc60 (no view gate, no ConsumeRound, no recoil); ConsumeRound retired (not a binary method). Gotcha #20 sweep (all sites disasm-verified): simulationFlags|=0x1 == engine DelayWatchersFlag (audio watchers muted forever) removed everywhere -- projweap/mislanch fire marks, ProjectileWeapon::ResetToInitialState (@4bbb47 = updateModel|=1); Emitter::SetDirty retired and split per binary site into ForceUpdate() (@4bafaa/@4ba55d) vs ExecuteOnUpdate() (@4ba99a/@4ba943); Emitter fault gate GetFlags()==1 -> simulationState==1 (@4baab9). AmmoBin::GetAmmoState() accessor added (named-member read of ammoAlarm@0x1A8). BT_LOOK_TEST=<frame> scripted verify added (mech4.cpp): holds the rear-view button 300f on/300f off to drive viewFireEnable headlessly. Verified solo ARENA1 (bhk1, autofire): 54-60 [projectile] PUSH + impact smoke; look-back denial window = trigger pulses, state stays 2, zero launches, resumes on release; enemy (disabled) mech launchers pinned 7 + full recoil; STREAK-6 recharge dial sweeps across BT_SHOT frames; lasers fire throughout; zero gauge faults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
63b168cb92 |
SeekVoltageGraph: full reconstruction -- the eng-page POWER graph + top-box eraser (Gitea #11)
The #10 audit's one WRONG: the port Execute was a bring-up no-op, so the emitter/myomer engineering pages never erased their top data box -> stale sibling-page ghosts on the shared Eng bit-plane (SYSTEM 10 PPC showing the Streak ammo box, etc). Full faithful widget landed: - btl4gau2: ctor/BecameActive/Execute/clear/ticks/cursor recovered from the capstone disasm of @004c6798/@004c6920/@004c6934/@004c6be4/@004c6c30/ @004c6c6c (Ghidra dropped every x87 arg). Plot: v=0..12000 step 1200, x=Round(response(v)*230), y=Round(v*(1/12000)*187) (ld80 @004c6bd0/@004c6d74 = exactly 1/12000); change-test samples the response at 12000V vs the 9999 activation sentinel; XOR op for tick/cursor move-by-redraw; destroyed branch centres edestryd.pcc and revives via own vtbl+0xC (BecameActive, slot 3 of PTR_0051a1fc -- vtable-dump verified). - The vtbl+0x3C sampler identified from the binary vtables: Emitter slot 15 @004bb42c = sqrt(P(v)/2.0e8), P @004bb3f4 = damageFraction*v^2*0.5* energyCoefficient; Myomers slot 15 @004b8f94 = sqrt(AvailableOutput(v)*3.6/ 350). FUN_004dd138 == sqrt (part_015.c:4026): myomers' fabs reading corrected, GetSpeedReading renamed SeekVoltageResponse. Port dispatch via complete-type bridges BTSeekVoltageSample/BTMyomersSeekSample + BTSubsystemDestroyed (databinding rule). - Emitter's AUTHENTIC attribute table recovered (binary @0x511dd4, ids 0x1D-0x25) and published: Laser*/Seek*/OutputVoltage@0x414 (currentLevel, RAW volts -- the live-cursor feed). Field renames per the table: 0x3F8 minSeekVoltageIndex / 0x3FC maxSeekVoltageIndex (static_assert-locked). The MechWeapon 0x1D OutputVoltage PORT ALIAS retired (binary table ends 0x1C; Find walks lowest-id-first, the alias shadowed the authentic row). PPC::DefaultData -> Emitter::GetAttributeIndex(). - mech4: BT_PRESET_HOLD=<n> (freeze the #9 preset cycler after n pulses) for steady-state pixel verification; BT_SEEK_LOG diag. Verified live (BLH, autofire): both #10 repro pairs held ghost-free for minutes (SYS09->SYS10 PPC, SYS02->SYS04 ERMed, SYS05->SYS06 Myomers); curves draw with live cursors; one replot per activation; quad panels/J-K-L/sec panel un-regressed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6783619069 |
Gitea #9: the upper-MFD PRESET pages (3 MFDs x 5) live -- SetPresetMode
table re-decoded to the ModeMFD bits + desktop J/K/L page cycle
The preset system was unwired by ONE defect in the message layer: the
SetPresetMode @004d1b24 table @0051dbf0 had been transcribed from the
section dump as BIG-endian dwords ({0x1e,0x01000000} instead of
{0x1e,0x01}), so a preset press set a garbage high bit -- and for group 1
items 3-4 / group 2 items 0-2 stomped the LIVE NonMapping / Intercom /
ModeSecondary* bits -- while the real page bits never moved. Ground
truth (section_dump.txt:72901-72908, little-endian + BTL4MODE.HPP [T0]):
set = ModeMFD{1,2,3}{Quad,Eng1-4} = 1<<(group*5+item), bits 0-14, fully
disjoint from the #6 secondary trio (bits 18-20); group 2 is MFD3, NOT a
duplicate of the secondary views.
What the 15 presets show (l4gauge.cfg): group = the MFD (Mfd1 lower left
/ Mfd2 upper center / Mfd3 lower right), item 0 = the btquad.pcx Quad
overview (up to 4 vehicleSubSystems cluster panels), items 1-4 = the
full-screen engineering-detail pages (bteng.pcx + prepEngr screens
group*4+1..4 + the cluster eng children: GENERATOR SELECT A-D, POWER
graph, COOLING loop, DAMAGE, ammo). Empty screens are authored per mech
(Blackhawk: 3/11/12 empty).
Authentic dispatch (streamed "L4" .CTL EventMappings, BT_CTRLMAP_LOG
dump): each MFD owns the 8-button RIO bank around it, MODE-MASK-gated --
Mfd1 = 0x08-0x0F, Mfd2 = 0x20-0x27, Mfd3 = 0x00-0x07. Quad page ->
direct-select buttons for the POPULATED eng pages (mapper msgs
Aux1Eng1-4 0x4-0x7 / Aux2* 0x9-0xC / Aux3* 0xE-0x11); eng page -> one
back-to-Quad button (0x3/0x8/0xD) + per-subsystem controls. These
records already install and fire on desktop (btinput passes the live
manager mask), so the NUMPAD profile's 0x20-0x27 keys page MFD2
authentically.
Port wiring (the #6 pattern): keys J/K/L -> actions Mfd1/2/3Cycle ->
gBTPresetCycle -> L4MechControlsMapper::CyclePresetModeNow(group) -- a
documented desktop shim (24 mode-dependent pod buttons don't fit a
keyboard) that cycles Quad -> populated Eng pages -> Quad, visiting
exactly the pod-reachable set; the body is the authentic SetPresetMode.
Dev-composite: BTDrawGaugeSurfaces now draws the Eng1-3 planes at their
sibling cells and skips any mono plane whose channel is BlankColor,
honoring the mode-driven reconfigure (RemapGraphicsPort) -- each dev
cell shows the ACTIVE page like the pod monitor. This supersedes and
removes the 2026-07-12 GAUGREND "frozen-dial" scaffold (forced all 15
page bits active under BT_DEV_GAUGES; it pinned the shared Eng plane on
the highest screen and ate the page flips).
Pixel-verified (BT_PRESET_TEST + BT_DEV_GAUGES_DOCK + BT_SHOT,
Blackhawk): all three MFDs page Quad -> SYSTEM NN eng details -> back to
Quad in lockstep with the [mode] preset mask log; group 0 skips the
authored-empty screen 3, group 2 skips 11/12. Un-regressed: N display
cycle (0x450421->0x490421->0x510421, page bits intact), M control mode,
CONTROLS.MAP 52 bindings parse clean.
Diags: BT_MODE_LOG ([mode] preset), BT_PRESET_TEST=<frame>.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
1d6339b226 |
Gitea #6: secondary MFD Damage/Critical/Heat cycling -- reconstruct the
NotifyOfDisplayModeChange override (vtbl+0x4C @4d1ae4) + wire desktop 'N'
The secondary screen's schematic selector was mislabeled: @004d1ae4 (the
bits-18..20 ModeSecondary* mask swap) was reconstructed as a non-virtual
"SetControlMode" that nothing called, so the desktop stayed pinned on the
Damage view. The binary's L4 vtable @0051e440 pins the truth:
+0x48 = @004d1acc <- CycleControlModeMessageHandler (FUN_004afbe0):
forwards to the base RET no-op @004b048c. A BAS/MID/ADV
control-mode change never touches the secondary view
(empirically confirmed: BT_MODECYCLE_TEST cycles the CONTROL
MODE lamp, mask bits 18-20 unchanged, schematic stays ARMOR).
+0x4C = @004d1ae4 <- CycleDisplayModeMessageHandler (FUN_004afcac):
THE Damage/Critical/Heat selector, indexed by displayMode
(table @0051dbe4 = ModeSecondaryDamage/Critical/Heat).
Authentic pod inputs (streamed type-6 .CTL EventMappings, dumped via the
new BT_CTRLMAP_LOG EVENT records): secondary-panel button 0x15 -> msg
0x15 CycleDisplayMode (manual p13, the "'Mech status Info center" bottom
left of the secondary screen), button 0x18 -> msg 0x14 CycleControlMode
(manual p6, top right), 0x10/0x11 -> ZoomIn/Out. The DOS keyboard
fallbacks (Keypress 0x13d/0x13e = extended F3/F4) are dead under the
WinTesla VK map, hence the desktop pin.
Port wiring (the M/ModeCycle pattern): key N / pad RightThumb -> action
DisplayCycle -> gBTDisplayCycle -> CycleDisplayModeNow() -- the same body
the pod console button message drives. Both .MAP profiles + the
compiled-in default updated.
Verified live (docked gauges + BT_SHOT, BT_VIEWCYCLE_TEST): the sec
panel cycles ARMOR DAMAGE silhouette -> CRITICAL DAMAGE subsystem list
-> HEAT DAMAGE colored list, mask 0x450421 -> 0x490421 -> 0x510421; M
control-mode cycling un-regressed (BAS/MID/ADV lamp cycles, view pinned).
Diags: BT_MODE_LOG, BT_VIEWCYCLE_TEST=<frame>, BT_MODECYCLE_TEST=<frame>,
BT_CTRLMAP_LOG now dumps EVENT records. KB: gauges-hud secondary-view
section rewritten, CLASSMAP +0x48/+0x4C slots, decomp-reference env
gates, GAUGE_COMPOSITE phase-4 entry resolved.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
2ae9bd43ae |
Coolant Flush end-to-end (Gitea #7): InjectCoolant id-4 handler + drain + FLUSH.PFX cloud + 'H' key
The playtest report (sound plays, no gauge drop, no cloud) traced to the
Reservoir's InjectCoolant chain being dead in three places:
- The handler was never REGISTERED: the binary's Reservoir handler table
@0x50e680 has one entry {4, "InjectCoolant", @4aee70}; added
Reservoir::GetMessageHandlers + the press/release handler (press starts
the flush gated on coolantLevel@0x12C > 0 -- the old body misread +0x12C
as currentTemperature; release stops it; novice lockout via FUN_004ac9c8).
- Reservoir::InjectCoolant (@4aefa4, 1019 bytes) was an empty stub -- the
drain the coolant gauge reads. Reconstructed in full: work list =
condenser/weapon/heatable chains (+0x7cc/+0x7bc/+0x7ac, roster-walk
emulation with the binary's duplicate-visit weighting; HeatSink-filtered
[T2 guarded]) + the linked master sink; per sink with flowScale != 0 move
squirtMass x flowScale x dt (clamped to the tank / sink capacity) and
credit pendingHeat with the negative carried-heat delta capped at
sinkMass x reservoir startingTemperature -- the set%-biased flush of the
manual (p24), riding the existing heat model.
- Two latent ctor decode bugs surfaced and fixed: the master gate @4af408
(and @4aeb40 HeatWatcher, swept) reads the OWNER MECH's simulationFlags
(*(param_2+0x28)), not the resource's subsystemFlags (the misread left
the CoolantSimulation Performance unregistered); and the capacity scale
FILDs the bank's INTEGER HeatSinkCount ((float10)*(int*)(link+0x1d0)) --
the float reinterpret gave ~1e-44 -> a permanently empty tank.
Capacity = 0.05 x heatSinkCount x streamed CoolantCapacity (BLH: 6.0).
Visual: the binary's mode-1 coolant-effect renderable (FUN_00456a68, built
for classID 0xBC0 on "ReservoirState", part_014.c:5439; tick @part_007.c:
8780) starts psfx 19 = FLUSH.PFX ("Coolant flush", BTDPL.INI) when the
state changes to 1 -- BTSpawnFlushCloud (mech4.cpp) spawns it on the same
alarm edge as an attached emitter at torso height.
Input: new CONTROLS.MAP action "Flush" on 'H' (HELD; press+release both
dispatch, the held-button payload). Diags: BT_FLUSH_LOG, BT_FLUSH_TEST.
Verified live (FOGDAY): [flush] Reservoir level 6 -> 0.13 -> 0 over ~0.6 s
held (= the manual's 3-4 punches to empty), the coolant vertBar source
Reservoir/CoolantMass drains, and the bluish condensation cloud rises from
the mech (scratchpad/flush_cloud.png vs flush_before.png).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
d9ceddb12a |
Aim ray follows the torso elevation (the boresight leveling erased it)
Follow-through on the pitch fix: the view pitched but shots still flew level (user report). Cause: the task-#48 AIM BORESIGHT leveling in the eye's aim-camera publish (L4VIDRND) strips ALL pitch from the pick-ray direction -- correct for the TERRAIN body pitch it was built against, but it also erased the pilot's deliberate R/F elevation. Fix: mech4's per-frame eye compose publishes the torso elevation (gBTEyeElev); the publisher re-applies it onto the LEVELED forward (fwd = (cos e * level, sin e)) before building the basis -- terrain pitch stays stripped, the deliberate aim survives into BTGetAimRay. Sign matches the pixel-calibrated view compose (+ = up). Harness evidence: pinned-down elevation visibly pitches the chase eye into the terrain (the ray basis moves); a full headless aimed-kill could not be driven (the autofire gate needs a designated target and the random spawns wedged short of the truck row) -- aim-at-truck kill needs the human pass. Awaiting human verification (issue #8). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b85afed925 |
Torso-elevation aim LIVE: R/F now pitches the view + aim ray (pitch was inert)
User + tester reports: pitch does not work. Root cause: the Torso sim integrated the R/F / stick-Y axis into currentElevation (authored limits and rates all correct) but NOTHING consumed it -- eyepointRotation's only writer was the look-state switch, so neither the cockpit view nor the aim ray (camera basis) ever pitched. Shots always flew level; low targets (trucks) were unhittable. Fix (faithful: the pod's stick-Y pitches the EYE; the torso geometry does not tilt): per-frame compose in the HUD tick -- eyepointRotation = EulerAngles(lookPitch + torsoElevation, lookYaw, 0); BTCommitLookState now stores its look component in gBTLookPitch/Yaw; new complete-type bridge BTGetTorsoElevation (torso.cpp, mirrors BTGetTorsoTwist). DPLEyeRenderable consumes it every frame, so the view and the boresight aim pitch together (crosshair stays screen-centred). Sign PIXEL-CALIBRATED via new diag BT_FORCE_ELEV=<-1..1> (pins the axis headless): screenshots confirm axis +1 (key R) = aim UP, -1 (F) = down. X still recenters. Awaiting human verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6f6a39b8c9 |
Issue #3 (b)+(c): weapon damage reaches cultural icons -> full trkdead explosion + authored burn fire
(b) The 'tiny explosion' on the ram kill is AUTHORED: crunch res 31 = 'stephit'
(one video object, effect 1008 = ddam5 damage smoke) -- a step/ram squash is
small by design. The FULL explosion res 32 = 'trkdead' = psfx 15 dtrkboom
(fiery omni burst) + psfx 16 dtrkburn (the burning-wreck fire), reached by
WEAPON kills -- which the 1995 binary dispatches [T1]:
- MechWeapon::SendDamageMessage @004b9728 (part_013.c:6765) gates only
"target NOT derived from Mech@0x50bdb4 OR aimed zone set": a non-Mech
boresight target takes the zone=-1 damage UNCONDITIONALLY;
- Missile contact @004be078 dispatches at the struck solid's OWNER entity
with no class test at all.
Port wiring: Mech::WorldStructurePick returns the struck solid's owning entity
(BoxedSolid::GetOwningSimulation; TERRAIN.cpp:107/246 build every static solid
with its Terrain/CulturalIcon/Door entity as owner); the mech4 pick block
designates IT instead of the gBTTerrainEntity sentinel (sentinel = fallback);
the projectile contact path grew the non-mech damage-zoned else-branch
(@004be078 mirror, direct Dispatch). Plain terrain ignores the damage
(ENTITY.cpp:885 zone==-1 guard); an icon's handler maps -1 -> 0 and dies.
Truck armor is WeaponDamagePoints=1 (TRK.DMG): one laser = one dead truck.
(c) The burning fire is dtrkburn.pfx, authored INTO the death package (every
icon family carries 1016: trkdead/bigdead/meddead/msldead/twrdead). There is
NO looping BurningState fire in the binary: the damage-zone effect watcher
ctor @0042a984 has exactly one call site (the Mech ctor, part_012.c:10405), so
icon ExplosionTables are inert, and CulturalIcon has no Performance.
Documented as authentic -- nothing invented.
Verified live (BT_SHOT pixel capture): 26 laser kills, [cult] TakeDamage
type=3 -> DYING res=32 -> DPLIndependantEffect 1015+1016 at the icon origin;
frames show the orange dtrkboom fireball, dtrkburn flames on the fresh wreck,
and the rubble aftermath; ram harness re-run still fires crunch res 31.
Diags: BT_FIRE_AT_ICON (designate nearest ahead icon; live-icon census in
CULTURAL.cpp), BT_FX_TEST="1015,1016".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
122fb7bccb |
Fix the backtick crash (a /FORCE-hidden linkage bug) + backtick = view toggle
The crash (any typed key in a glass session; found via the backtick report): the merge-reconciliation stand-down declared extern BTRIODevicePresent at BLOCK scope inside the extern-C BTInputSuppressKey -- the declaration inherited C linkage, _BTRIODevicePresent went UNRESOLVED, and /FORCE bound the call to garbage (cdb: wild call into Sensor::DefaultData from LBE4ControlsManager::Execute's suppression check). The tolerated-LNK2019 batch hid the new unresolved external -- the exact CLAUDE.md /FORCE trap. Fix: the extern moved to file scope (C++ linkage); link verified clean of _BTRIODevicePresent. Backtick feature (per Cyd: backtick = 1st/3rd person): PadRIO edge-detects VK_OEM_3 in its poll (focus-guarded, message-path-free) and the mech4 view block consumes it beside the V action (which stands down with the binding engine in glass mode). Verified live: two real presses -> [view] COCKPIT eyepoint -> [view] external chase, game alive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f889e24ce0 |
Merge origin/master: the D1 relay/operator line + input remap meet the glass layer
33 master commits in (relay TCP/UDP + PySide6 operator console, CONTROLS.MAP +XInput binding engine, camera seats, torso pitch aim, sign fixes, the 1995 manual, version stamping, 18-mech certification). Conflicts: .gitignore + CLAUDE.md router rows (combined). SEMANTIC RECONCILIATION (the one real overlap): masters btinput binding engine (ungated, CONTROLS.MAP) and the glass PadRIO (gated, bindings.txt) would both read the keyboard/pad in a glass+PAD session. btinput now joins the stand-down convention: BTInputPoll yields (and BTInputSuppressKey claims NOTHING, so authentic hotkeys flow) when an operational cockpit device owns the input path -- BTRIODevicePresent, BT_KEY_BRIDGE force-override honored, forced harness exempt. One input system per mode: btinput on pod/dev desktops, PadRIO on glass. The mechmppr/mech4 bridge merges composed clean (masters negate-once sign fix inside our device-gated bridge). The D1 relay keeps its own raw sockets by design (an alternative LAN wire; Steam and relay are separate modes). Verified post-merge: all 3 configs build; glass boots with [input] binding engine standing down + PadRIO owning input (30 ticks); pod forced-walk speedDemand=61.501 with btinput ACTIVE; 2-node loopback MP full 31/31 mission, 76/76 ticks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ff5260ce98 |
Coolant-loop cross-check vs the 1995 manual: structure strongly faithful
Dumped each mech's subsystem->coolant-loop (BT_SPEC_LOG in mech4: walks the roster once, resolves each subsystem's linked-condenser number via BTCoolingLoopFrame) and diffed all 6 manual mechs' COOLANT LOOPS tables. RESULT -- the structure survives the 4.0->4.10 gap remarkably well: - BACKBONE identical on ALL 6: Generator A/B/C/D on loops 1/2/3/5, Sensors(our Avionics) on loop 2, Myomers on loop 5, LRMs on 1&3, autocannon on 4, big energy weapon on 6 -- exact match - weapon LOADOUT identical on 5 of 6 (Thor/Vulture/MadCat/Owens/ Blackhawk); Loki is the one full rework (4.0 PPCx2/AFC100/SRM6 -> 4.10 AFC50x2/ER Medium x2/SRM4) - the consistent 4.0->4.10 change is a small-laser REDISTRIBUTION: the 2 ER Small Lasers moved off the sensor/heavy loops onto the energy loops 4&6 (Thor/MadCat/Vulture); Owens near-perfect (loops 1&3 exact) - Loop 0 = correctly UNCOOLED infrastructure (condensers, reservoir, gyro, torso, HUD, ammo bins, ...) -- not a coolant loop CONCLUSION: the coolant-loop reconstruction is faithful; every diff is 4.0->4.10 balance tuning, not a bug. BT_SPEC_LOG retained. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1271d3bc76 |
Torso elevation (pitch aim) wired: the pod stick's Y axis lives
The mechs could always tilt their aim up/down -- Torso models the full vertical axis (currentElevation, rate, VerticalLimitTop/Bottom, recenter) and EVERY 1995 control mode routes stickPosition.y into Torso::SetAnalogElevationAxis -- but the desktop bridge hard-zeroed stick Y, so the axis was dead on a keyboard rig. The one pod control the remap left unwired. - btinput: JoystickY axis -> elevTarget/elevActive/elevAbsolute - mech4 shim: sElev integrator (same walk/spring model as the twist; X recenters pitch too via gBTElevRecenter) - mechmppr bridge: feeds stickPosition.y every bridged frame (the old unconditional zero removed); both mode branches covered - CONTROLS.MAP (+ numpad profile + compiled default): R/F = aim up/down, pad LeftStickY = elevation (was unused) - torso.hpp: CurrentElevation()/ElevationVelocity() accessors (diag) - [mppr] trace gains stickY (note: the trace reads AFTER the next frame's device push re-zeroes the stick -- input flows regardless) Verified live: R held -> torso elevation climbs at the authored rate and clamps at 0.349066 rad = exactly 20.0 deg (the Blackhawk's VerticalLimitTop); release holds the aim. The eyepoint correctly stays level -- pitch aims the GUNS and reads on the HUD's vertical elevation tape, as in the pod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
17176058cc |
First 4-pod session VERIFIED + the EntityID-cast diagnostic trap
4 pods, relay-assigned seats, 4-pilot MP4.EGG (eggmodel-generated incl. callsign bitmaps): 4 distinct seats, full ladder, every node replicates all 3 foreign mechs (host-qualified [repl] sets are exact complements), 4 distinct spawn positions (no dropzone stacking), UDP at 4-pod fan-out (relay tx=3x rx, zero drops), all pods alive through the session. Per-host [net-rx] census symmetric: 36 NewDynamicEntity makes (mech + subsystem roster) from every host to every node. DIAGNOSTIC TRAP that cost a false bug-hunt: EntityID::operator int() returns localID ONLY, so the [repl] log's (long)GetEntityID() collapsed different hosts' mechs onto one number and read as 'missing replicants'. The log now streams the EntityID object (host:local via its operator<<). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
980c9cd7e5 |
Input remap: CONTROLS.MAP binding engine + XInput gamepad support
The pod's controls reach the game through the RIO serial board; on a
desktop that hardware is a keyboard shim of hardcoded GetAsyncKeyState
reads. New binding engine (game/reconstructed/btinput.cpp) maps PC keys
and an XInput pad onto the authentic channels through a user-editable
file (content/CONTROLS.MAP, community-suggested grammar, corrected):
key/pad -> button <addr> real buttonGroup emissions, mirroring the
RIO convention exactly (press a+1 w/ mode
mask saved, release -a-1 w/ saved mask;
L4CTRL.cpp:2470-2520) -- aux/preset banks,
hotbox, panic, reverse thrust all reachable
key/pad -> axis Throttle rate (lever), pedals/JoystickX
deflect (turn/twist) into the existing
virtual-controls integrators (feel, gait
detent, spring-centering all unchanged)
keypad pilot|external <n> keyboardGroup key values ('0'-'F')
pckey <char> any authentic 1995 typed hotkey
action <name> port dev controls (view, all-stop, ...)
Keys claimed by a binding are SUPPRESSED from the legacy WM_CHAR/KEYUP
feed -- ends the historic double-dispatch ('w' drove AND selected pilot
0; F5's key-up value 0x74 aliased to the 't' hotkey; letter key-ups fed
the developer fake-event dispatcher). Unbound keys keep their authentic
meaning. The dual-use 'V' is split: V = view toggle, B = look behind.
Default profile = WASD classic (compiled-in twin; delete the file to
restore). CONTROLS_NUMPAD.MAP ships the corrected community layout
(keypads are NOT buttons 0x50-0x6F -- that space doesn't exist; no
clickable cockpit exists so everything needs a binding; keyboard fire
buttons added; 0x36/0x37 are hotbox not 'config'; missiles moved off
Ctrl). XInput loads dynamically (1_4 -> 9_1_0), disconnected-pad
probing rate-limited; pad sticks write the axes as absolute positions
(the spring is physical), triggers/buttons per the file.
Verified live: bindings load (43), W and NumPad8 drive (speedDemand 0 ->
61.5), X all-stop (spd -> 0), aux-bank emission (D1 -> [input] 0x2f
PRESS/release under the numpad profile), suppression both directions
(posted 'r' reaches [keych], posted 'w' swallowed), full-keyspace
WM_CHAR+WM_KEYUP fuzz x3 survived with sim advancing, XInput graceful
with no pad, 2-node relay session un-regressed (full ladder + UDP).
Pad-in-hand testing still needs a physical controller.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
9f35a8034a |
Controls: the keyboard bridges STAND DOWN for a live device (step 2c)
New engine query BTRIODevicePresent() (L4CTRL.cpp, ungated -- pod-correct for the serial RIO too): device exists AND IsOperational(). Both dev input bridges -- the mech4.cpp mapper-attr writes and the mechmppr.cpp BT_KEY_BRIDGE block -- now gate their WRITES on it: BT_KEY_BRIDGE unset = auto (bridge only when no device), 0 = force off (the documented pod setting, honored), else force on. The BT_FORCE_THROTTLE headless harness always rides the bridge. The mapper demand READ (turn = turnDemand) always runs. Verified live (glass build, PAD): OS-injected LSHIFT hold slews the PadRIO throttle 0 -> 0.33 -> 1.0 with pre==thr every frame -- the value arrives via the ENGINE push through the streamed .CTL binding, not the bridge -- and speedDemand=61.501 comes out of the authentic InterpretControls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ebdfa40d95 |
HUD (task #68 follow-up): authentic reticle pip GROUPS -- front vs rear
The binary reticle registration resolves each weapon's RearFiring attribute and registers its pip in group 1 (front) or 2 (rear) (part_014.c:5429-5434) [T1]; the reticle draws the group selected by the mech's reticleElementMask low bits (= binary mech+0x390, driven by the weapon-update view switch part_013.c:5588-5595: forward |1&~2, look-back |2&~1). Port: BTBuildReticle passes the real group (was hardcoded 1 on the disproven "no BLH weapon is rear" belief); BTCommitLookState drives the mech's reticleElementMask low bits per view (mech4's HUD tick already publishes them as gBTHudGroupMask, and the reticle Draw already filters on it). Resolves the user-reported "only 2 ERM dots but the mech has 3": ERMLaser_1 (front) and ERMLaser_2 (rear) both author pip position 1 -- with every pip drawn in one group the two red dots sat exactly on top of each other. Now: forward view = 5 pips (2 PPC + ERM_1 + 2 SRM), look-back = the 2 rear lasers' own group layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
38febae36b |
Rear-fire + look views (task #68): the pod's rear arsenal reconstructed
The user's tip ("some mechs actually do fire backward") checks out end to
end. Binary ground truth [T1]:
- weapon+0x334 (attr 0x1B "RearFiring"): the ctor tests the MOUNT SEGMENT's
page name for the marker 'b' (@0x511aa2, part_013.c:6913-6930) -- the back
gun ports sitelbgunport/siterbgunport. The old reading ("EXT in the
weapon model name") was a double misread (wrong string, wrong name).
- mech+0x410 (attr id 50 "RearFiring"; the old `stateFlags` label): the ctor
ORs every weapon's flag (part_012.c:10220) -- "carries a rear arsenal".
- The mapper's five-state LOOK machine (part_013.c:396-459): on a state
change it re-aims the eyepoint (mech+0x360 = EyepointRotation -- consumed
by DPLEyeRenderable, already live in the port) and re-arms each weapon's
viewFireEnable(+0x3E0): FORWARD view = the non-rear weapons, LOOK-BACK
(yaw pi + lookBackAngle pitch) = the REAR-mounted ones, side/down = none.
+0x3E0 is the same flag the emitter's Loaded->Firing gate reads (the old
`useConfiguredPip` label) -- pips and fire permission both follow the view.
Port: rearFiring derived from the mount segment name (BTWeaponMountIsRear);
mech rearFiring ORed in the roster pass (the old SubProxy::IsDerivedFrom
stub returned 0 -- that loop never ran; now bridged through
BTWeaponIsRearFiring, which also fixes the weaponRoster fill); the look
commit is LIVE (BTCommitLookState: eyepoint EulerAngles from the authored
per-mech look angles -- now real members, were Wword scratch parks -- +
per-weapon view enables); MissileLauncher/ProjectileWeapon FireWeapon gate
on viewFireEnable like the emitter; RearFiring attrs (mech + weapon) bind
real members. Keyboard: HOLD 'V' = the pod's rear-view button.
Live-verified on the default blackhawk: ERMLaser_2 -> siterbgunport rear=1,
ERMLaser_3 -> sitelbgunport rear=1, PPCs/SRMs/torso mounts forward -- the
blackhawk authors TWO REAR LASERS (owens also has back ports). [rearfire]
trace under BT_PROJ_LOG.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
ab6ea83e3b |
Missiles (task #67): launch through the WEAPON MOUNT frame -- the
backward-firing rack User report (madcat): missiles sometimes leave the mech BACKWARD; lasers always fine. Root cause: only missiles fly the authored MuzzleVelocity vector, and BTPushProjectile rotated it through the mech BODY basis (localToWorld) -- but the madcat's racks ride the TORSO. The muzzle POINT was segment-resolved (tracked the twist) while the launch DIRECTION followed the LEGS: twist the torso far enough and rounds left the back. Lasers/ACs aim straight at the designated pick (no launch vector) -- unaffected, exactly as observed. Binary ground truth [T1]: the fire builder FUN_004bcc60 spawns the missile with the FULL muzzle-segment frame (the real GetMuzzlePoint fills an AffineMatrix into the descriptor, :8762-8764), composes the authored MV with the z-NEGATION (:8759-8761 -- confirming the 2026-07-12 telemetry finding) onto the mech's own localVelocity (FUN_004b9cbc = owner+0x1c4), and the dumb seeker re-aims 100u ahead of the MISSILE's own frame each frame (0x4be9a0 disasm) -- the mount orientation IS the launch direction. Port: BTPushProjectile takes muzzle_seg and rotates the launch vector through the mount segment's world frame (segment-to-entity x localToWorld, the same matrix the muzzle point already used), keeping the z-negation convention, and inherits the shooter's world velocity; body-basis fallback for callers without a segment. All four call sites pass GetSegmentIndex(). Bonus decode banked for the full Missile-entity revival (KB-worthy): the three authentic performances -- Seeker 0x4be9a0 (aim = target-frame offset / 100u-ahead dumb + loft/lead 0x4beae4), Thruster 0x4be474 (quaternion-slerp BODY TURN toward the aim at turnRate deg/s), MoveAndCollide 0x4bef78 (velocity aerodynamically aligned to thrust via signed-square per-axis gains, ballistic droop as fuel burns, PROXIMITY FUSE on seeker rangeToTarget, max-range + altitude-floor retire). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c51d64a6b8 |
Gyro tail + audio (task #66): the INSTABILITY MODEL -- mech+0x3F0 reconstructed
Byte-decoded the un-exported master-perf block @0x4aad3d-0x4aaf14 (capstone;
the same gap that held the F5 footstep code) [T1]:
instab = (min(|AccelerationLastFrame| / maxUnstableAcceleration, 1)
x unstableAccelerationEffect)^2
instab += clamp((demand - legCycleSpeed)/demand, <=1) # "gun the engine"
x unstableGunTheEngineEffect # (demand clamped to
# [gimpStride(neg), runMax2])
if (legAnimationState == 4 /*turn-in-place*/) instab += unstableStopedTurnEffect
clamp to 1 -> mech+0x3F0 -> gyro->swayBias (gyro+0x3A8)
Every piece resolves to already-reconstructed structure: the model-record
tuning block rec+0x80..0x94 (ctor copy @0x4a2593 -- the six authored
Unstable* fields, previously parked in the Wword scratch bank, now real
members); AccelerationLastFrame@0x82c = the snapshot of the ring-derived
localAcceleration (copied at the perf tail @0x4ab142); CurrentSpeed@0x348 =
legCycleSpeed; the trn state 4 gate; and the gyro feed lands in the
EXISTING swayBias member + GyroscopeSimulation consumer (task #56) --
GyroFrameJointWrite's "0.0f model TBD" argument is now the live value.
UnstablePercentage (binary id 52 @0x3F0) binds the real member: the LAST
dead audio attribute is live -- the authored instability alarm (start
thresh 0.01 + volume = the fraction) sounds under hard maneuvers, and the
cockpit ambient sway now scales with reckless driving.
Live verification (30s, 0.6-throttle run): instab 0.38 during the walk->run
chase (cyc 22.2 vs demand 33.6), settling to 0.002-0.12 per-stride ripple at
steady run; attribute binds with live float values; [instab] trace under
BT_GYRO_TRACE. unstableSuperStopEffect/unstableHighVelocityEffect writers
remain unlocated (plausibly the airborne perf variant) -- noted on members.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
a11a697824 |
Audio: footsteps arrive on the FIRST stride -- the 10-20 s warm-up bug
User report: footfalls silent for the first 10-20 s of every mission (all
mechs), then solid. Root cause was three interlocking layers, each measured
with timestamped traces:
1. The authored footstep volume chain (LocalAcceleration [0,10]->ctl100 +
LocalVelocity [0,0.6]->ctl101 through authored N=30/N=15
AudioControlSmoothers, fill 0) hangs off the SOURCE's watcher chain
(scale watches smoother watches mixer watches source), and an idle
source's chain executes only at Start attempts -- one smoother sample
per stride.
2. Each hop is frame-gated (AudioComponent::ExecuteWatchers,
DefaultAudioFrameDelay), so any burst collapses to one execution.
3. The transient drop gate (vol < 0.3, AUDREND) rejected every Start while
the smoother average crawled up 1/30th per attempt -> ~25 dropped strides
before the first audible step, then per-frame execution while playing
kept it warm forever ("solid after that").
Fixes (engine-level, each documented in place):
- AudioScaleOf<T>::Execute now sends EVERY poll (scales are continuous
value-feeders; the base bitwise change-gate -- Motion::operator== is
memcmp -- froze on our deterministic gait math, where the original's
noisy physics floats never bit-repeated. Triggers/matchers keep the
change gate: their semantics are edge-based).
- Component/AudioComponent::PrimeWatchers(passes): recursive, GATE-FREE
watcher pump; AUDREND runs 30 passes on every transient Start request so
the authored smoothers evaluate at their true steady state before the
drop gate reads the volume.
- localAcceleration derives via the binary's exact structure: 15-sample
ring buffers of the raw position derivative + dt (ctor part_012.c:9836,
derive :15169-15195), in the PerformAndWatch tail so it runs every frame.
- AttributeWatcherOf::GrabCurrentValue private -> protected (the scale
override calls it).
Verified (30 s walk from cold start): drops 25 -> 3 (the survivors are
authentic quiet-stride gating: first gentle strides at vol ~0.28 vs the 0.3
gate), footfalls deliver from the first stride, 43 delivered with live
per-stride gain variation. Diag traces added: [accwatch]/[fsscale]/
[smooth]/[smoothcfg]/[motionscalecfg] + timestamps on DROP/volset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
0e2401fb52 |
Audio Phase 4d (AUDIO_FIDELITY F5): footStep is the authored CONTACT LEVEL
The binary's per-frame perf (disasm @0x4a9e80-0x4a9eb6 leg / @0x4aba86- 0x4abab9 body) computes footStep@0x394 = (jointlocal.y <= *footStepThreshold) with the threshold pointer = SequenceController+0x20 = &ANI hdr[2] -- the header word the engine's own AnimationInstance captures (JMOVER.cpp:1415) and our SelectSequence skipped. States 0/1 retain the value. Port: SelectSequence captures hdr[2]; PerformAndWatch evaluates the contact level per frame from the cached jointlocal root joint (leg channel drives the pose); the 150 ms clip-transition pulse + decay are RETIRED (steps fired at clip boundaries with a fixed width; clips whose root crosses twice or never counted wrong). Live verification (30s walk): the authored threshold decodes real (-0.232 root height); rootY oscillates across it per stride (-0.26 contact / -0.19 swing) with clean 0->1->0 transitions per leg state (5/6/7); 34 footfall deliveries at stride rate with per-stride varying gains. Also closes F23(2): the 17 authored AnimationState trigger states fire empirically -- the EngineShiftFwd/Rev heard during the gait dead-band hunt WERE states 10/11/14/15; the runtime clip numbering is correct. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9dcb4752de |
Audio Phase 4c (AUDIO_FIDELITY F17/F19): impact scaling + the authored footstep feed
F17 CollisionSpeed (binary id 24 @0x4B4): real member captured as |worldLinearVelocity| when the contact accumulator arms (0->1) -- the authored AttackVolume [0.9,1] / Brightness [0.7,1] scales over impact speed [0,25] now make harder hits sound louder and brighter. ReduceButton (id 46 @0x340): real watchable member (the keyboard rig never presses it). UnstablePercentage stays deferred: its sway/overspeed model @0x3F0 is the known gyro-ledger gap (live writer unexported); binding without the model would be a stand-in. F19 footstep feed (the invention is dead, long live the authored chain): new [motionscalecfg/motiontrigcfg] traces recovered the authored configs -- EVERY motion watcher extracts |linearMotion| (motionValue=3); the footstep volume mixer is fed by LocalAcceleration [0,10] -> ctl100 (per-stride kick) + LocalVelocity [0,0.6] -> ctl101 (0.4 base while moving). The port never wrote Mover::localAcceleration, so ctl100 read 0 and the old mech2.cpp step-intensity broadcast (patch-sniffing, invented curve) fought the live authored scale. Now: localAcceleration.linear = d(published velocity)/dt -- EXACTLY the binary's derivation ((avgVel - prev)/avgDt into +0x1e4, part_012.c:15186-15195) -- and the broadcast is REMOVED. Regression (30s, walk throttle): stable; footfalls deliver through the wholly-authored chain with per-stride VARYING gains (0.61/0.72/0.62 -- real step dynamics, impossible under the old constant-curve invention). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cc2b109cc8 |
Audio Phase 4a (AUDIO_FIDELITY F6/F7): coolant-leak warning + missile alarm
F6 ReportLeak (the largest dead authored block -- 38 of 54 match watchers):
recovered the binary HeatSink attribute table (16-byte {id,name,off+1} rows
@0x50e438..0x50e4c8, ids 3..12) [T1] -- it confirms EVERY existing heat
binding (CurrentTemperature@0x114 .. CoolantMassLeakRate@0x130, HeatSink@
0x164) and adds the one row we never published: ReportLeak (id 12) ->
+0x138 = coolantActive, the INT leak hysteresis flag UpdateCoolant
(@004adbf8) drives 1/0 around draw 0.003/0.0025. PoweredSubsystem derives
from HeatSink, so the single row serves all 19 authored leak watchers
through the chained index, exactly like the binary. MechWeapon's pinned-id
pad absorbed the +1 chain shift (0x0E -> 0x0F, tripwire fired as designed).
F7 IncomingLock/DistanceToMissile (missile alarm): binary Mech table walked
in full (ids 21..56 [T1] -- also settles FootStep@0x394, CollisionSpeed@
0x4B4, UnstablePercentage@0x3F0, ReduceButton@0x340 for the next findings).
IncomingLock id 54 @0x3fc, DistanceToMissile id 56 @0x400; the old
"maxSpeed @0x400 = FLT_MAX" member was a MISREAD of the far default and is
retired (its 1000.0f "override" was RadarRange id 47 @0x404, already
published). Real members + accumulators: Missile::MoveAndCollide reports
target + range each tick (BTReportIncomingMissile bridge); PerformAndWatch
latches per frame. The authored beeper (match 1/0) + range->TEMPO scale
(100..800 -> 600..10, accelerating as the missile closes) read them
unchanged. Drive is intent-level [T3]; init 0/FLT_MAX matches the binary
reset (part_012.c:9446-9447).
Regression (30s): stable; ReportLeak binds real on every subsystem (0 pad
redirects); DistanceToMissile binds with FLT_MAX live; attrnull 41 -> 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
aa004eb7b8 |
Gait: keyboard detent snaps the lever out of the walk/run DEAD BAND + KB
Byte-decoded finding [T1]: the gait SM has no stable state for a demand in
(walkStrideLength@0x534, reverseSpeedMax@0x538). Cap semantics settled from
LoadLocomotionClips @0x4a80d4: 0x538 is the walk->run TRANSITION CLIP's exit
speed (the run ENGAGE threshold -- not a reverse max) and 0x34c (run-cycle avg
root speed) is the mapper's continuous demand multiplier (FUN_004afd10). The
finished-callbacks up-shift when tgt > 0x534 but enter/sustain run only when
tgt >= 0x538, so a demand parked between them (Blackhawk: 22.02-30.87 =
throttle 36-50%) hunts walk -> shift-up -> shift-down forever, firing the
authored EngineShiftFwd/Rev sounds each swing -- the user's repro, faithfully
reproduced by all-authentic logic + data. Likely masked on the pod by
MECHANICAL throttle-quadrant detents ("5 speeds" lore; the software path is
notch-free) [T4] -- added to get-from-Nick.
Accommodation (keyboard = our stand-in lever): at key REST, snap the lever to
the nearer dead-band edge (walk cap, or run engage + margin since the cont
check is >=). Sweeping THROUGH the band while held stays continuous -- an
authentic moving lever; the single shift it fires is the authentic shift.
BT_GAIT_TRACE logs [gaitdetent] snaps.
KB: locomotion.md gait-dead-band section, pod-hardware.md mechanical-notch
hypothesis, open-questions.md Nick item.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
537749cae3 |
KB: audio fidelity Phase 1+2 settled facts -> context/wintesla-port.md
wPreset patch numbering, SF2 v1.0 pitch generators + EMU8000 44100 base rate + the WAV bake formula, inverted SBK attenuation, full-zone/key-select/loop- region/release-fade architecture, distance/gain/doppler model, the audio-clock origin (FUN_0044e19c = ApplicationManager::GetFrameRate; frames were clock ticks), and the ConfigureActivePress base-attribute resolution. Annotated the mech4.cpp velocity smoothing as AUTHENTIC [T1] (binary Mech::Execute AverageOf filters, part_012.c:15169-15179). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c68307ad53 |
Audio: kill the per-footfall static -- smooth the PUBLISHED velocity (task #50)
User pinpointed it: the per-stride 'static' was NOT the footstep sample -- it was the ambient engine hum being interrupted in time with the footfalls. Diagnosis (live tape): EnginePower01 was caught in a rapid Play->Stop cycle (80x/session). The idle-hum is gated by authored LocalVelocity triggers (hum ON in speed [1,20], OFF above 20) -- and the port's localVelocity is a RAW per-frame position derivative that pulses with every stride (gait acceleration + ground-snap bob). At walking speed |v| straddles the 20 band edge, so the trigger Start/Stop-flapped the hum on every foot plant; at run |v| >> 20 keeps it off (matching 'goes away at higher speeds'). Fix: ~0.25s exponential filter on the PUBLISHED localVelocity.linearMotion (fwd + vertical) -- kills the stride ripple, tracks real speed changes within a couple strides. The physics worldLinearVelocity (StaticBounce, collision damage pricing) is untouched; update records + the impact gate get the smoother value (both benefit). VERIFIED: EnginePower 1 play / 0 stops for the whole run (was 80 stop-cycles); FootFall cycles normally per stride; gear shifts/windup lifecycle intact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1d019e8109 |
Audio: footstep pulse VERIFIED end-to-end to the matcher; volume chain traced (task #50)
Footstep chain progress (each step empirically verified): - The FootStep watcher is an AudioMatchOf<Logical> (AudioLogicalTrigger extends AudioMatchOf, NOT AudioTriggerOf): authored config match=1 -> Start(2.0) on a DirectPatchSource. [trigcfg]/[matchcfg] ctor dumps added. - The pulse was pinned at 1: the decay lived in IntegrateMotion, which the current gait path NEVER CALLS. Moved to Mech::PerformAndWatch (provably per-frame) with a time-based 150ms window sized to the ~10Hz watcher poll. The matcher now fires per stride (14/run, was 1). - Every footstep Start is DROPPED at the renderer: the source's volumeScale is 0 (five VolumeAudioHandler sends of exactly 0 = inert/one-shot primes). The volume arrives through the authored control chain (AudioControlMultiplier inputs ctl 100+; one 0 input pins the product). Chain scales identified on this entity: LocalVelocity (live), LocalAcceleration (live), Myomers. SpeedEffect (=1.0 healthy; one authored scale maps [0,1]->[1,0] INVERTED), UnstablePercentage (INERT PAD -- always 0!), HeatSink.CurrentTemperature. PRIME SUSPECT: an UnstablePercentage-fed multiplier input primes 0 once and never updates (the pad never changes), pinning footstep volume at 0. - SF2 numbering fact recorded: 38 presets in AUDIO1 / 5 in AUDIO2 have wPreset != file index (gap at preset 77); my table keys by wPreset. The 184x ProgramButton01 (bank1 patch83) storm at ~7/s needs an identity check against index-numbering (it may be the wrong sample for that patch id). Diagnostics added (BT_AUDIO_SPATIAL/BT_ATTRBIND_LOG): [trigcfg]/[matchcfg] ctor dumps, [matchfire] with component ptr+class, [volset] VolumeAudioHandler, [mult0] multiplier zero-products, scale sends with authored boundaries, [fswatch] dedicated footstep watcher tracer (g_btFootStepAddr), SetupPatch ENTRY with bank/patch/state, Simulation::DebugAudioWatcherCount. NEXT: back UnstablePercentage with a real (live) member -- it is the mech's stability 0..1 (the stabilityAlarm/gyro family) -- or confirm via a one-run chain dump which multiplier input pins the FS source; then the footstep transient should clear the 0.3 drop gate at walking speed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ba3040ea09 |
Audio: footstep-chain diagnostics -- every link verified except the final trigger fire (task #50)
Instrumentation (all BT_AUDIO_LOG/BT_AUDIO_SPATIAL-gated): mech watcher-poll probe (delayed flag + socket count via new Simulation::DebugAudioWatcherCount), per-sim audio-socket size in ExecuteWatchers, trigger notifications (val>0), watcher poll-rate/change probes, footstep pulse address trace. VERIFIED working: FootStep binds to the real member (pulse addr == bound ptr, same run); pulses fire per stride (40/run); the watcher registers on the mech (audioWatchers=20 after audio-object creation); the mech's poll runs (delayed=0, simFlags=0x1110); Logical == int (STYLE.H:132) matches the member type. The idle<->moving engine crossfade (AudioMotionScale on LocalVelocity through the restored watcher poll) is CONFIRMED audible in play. REMAINING: the FootFall transient still doesn't reach SetupPatch -- next probe is the trigger's streamed threshold/inverse config and the speed-scaled transient volume at the renderer drop gate (LowAudioVolumeThreshold=0.3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
744ef8cc16 |
Audio: restore the dropped ExecuteWatchers poll -- polled audio watchers were ALL dead (task #50)
User: "when I start moving all the sounds fade away, no footsteps." Diagnosis chain (all empirical, BT_AUDIO_SPATIAL/BT_ATTRBIND_LOG traces): - The audio head DOES track the mech (listener-relative positioning verified while driving) -- not a spatial bug. - The idle sounds correctly STOP on leaving the standing state; the MOVING sounds never started because every POLLED audio watcher was dead: the reconstructed Mech::PerformAndWatch replaced the engine performance but dropped the ExecuteWatchers() step from the engine tail (Simulation:: PerformAndWatch = Perform -> ExecuteWatchers -> WriteSimulationUpdate). Only PUSHED StateIndicator watchers (SetState->Execute) ever fired -- which is exactly why state sounds worked but footsteps/motion-scaled audio didn't. FIX: poll ExecuteWatchers() (AreWatchersDelayed-gated) before the update write. Immediately unlocks the polled family: MissileLoaded01/LaserLoaded01 ready dings, ProgramButton01, motion scales (PlayNote 15 -> 30 per run). - FootStep (0x1e) backed REAL: was the inert attrPad (an AudioLogicalTrigger polls it -- threshold-crossing pulse per foot plant). New footStep member, pulsed by SetBodyAnimation on entering any locomotion clip 1..0x17 (runtime stride alternation MEASURED as body states 12<->13 -- the enum-name numbering does not match runtime clip ids), decayed by IntegrateMotion (~1/3 s). - Footstep volume is speed-scaled (AudioMotionScale on LocalVelocity): at standstill the transient start computes volume 0 and the renderer drops it (LowAudioVolumeThreshold) -- so footfalls are audible at real walking speed. Diagnostics kept (BT_AUDIO_SPATIAL): spatial dist/vol per source, CLIPPED/DROP/ START at the request gate, vol=0 factor breakdown, scale->0 sends, trigger notifications, watcher poll-rate probe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7b1e469ffa |
Audio: in-game TRIGGERING works -- real StateIndicator attrs + audio-watcher databinding (task #50)
Enabling audio unlocked L4AudioRenderer::CreateEntityAudioObjects, which binds AudioStateWatchers to entity/subsystem state attrs BY NAME (attribute-pointer databinding) and calls AddAudioWatcher on them. The reconstruction pointed the Mech's state attrs at the scalar read-pad `attrPad` (fine for gauges that READ a value, fatal for a state watcher that calls a METHOD) -> AV in SChainOf::Add on 0xCDCDCDCD. Root-caused via cdb + a BT_ATTRBIND_LOG trace in AttributeWatcher. Mech-level state audio now REAL + driven (verified: walking fires SetupPatch + PlayNote/alSourcePlay, patches 78/80/116): - AnimationState (0x1f) / ReplicantAnimationState (0x20): real StateIndicators (0x21 states covering MechAnimationState 0..0x20), driven from SetBodyAnimation so footstep/gait/transition sounds fire on animation change. - CollisionState (0x16): real 4-state StateIndicator driven from ProcessCollision via collisionTemporaryState (the deferred @4aa741 per-frame zero + the part_012.c:15406-15413 contact tail, InitialHit accumulation; the 1-vs-2 Slide split awaits the 0x240/0x244 floats still stubbed by fieldAt()). - @0x44c CORRECTED: "ammoState (0/1 leaking/2 dry)" was a mislabel -> it is collisionTemporaryState (never read as ammo). Bring-up guards [T3, temporary, self-clearing] so audio-on is STABLE while the subsystem state models are still stubs (Generators/Condensers/Myomers/Torso/ Avionics/Reservoir/ControlsMapper -- ~15 subsystems, ~40 attrs: GeneratorState, CondenserState, ReportLeak, ...): a NULL subsystem attr redirects to an inert pad (was a fatal Fail); an AudioStateWatcher on an unconstructed StateIndicator (null/ 0xCDCDCDCD) skips instead of AV. Both pass automatically once the subsystem is reconstructed. Those subsystem sounds stay silent until then (their audio wave). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3224009dc1 |
MP: peer accel/decel "snap" MEASURED ~0.64u (not ~2.9u) -- effectively resolved (task #50)
Went after the flagged ~2.9u peer-motion snap. Added a BT_SNAPLOG diagnostic
(mech4.cpp: logs the re-anchor drift eMag = |authority - dead-reckon| absorbed per
frame) and ran a controlled accel/decel soak: node B BT_AUTODRIVE + BT_DRIVE_SWEEP0
sweeping the throttle through STOP, node A observing, affinity-pinned.
Result: peer drift maxes at ~0.64u (median 0.54u, 18 events >0.5u over 48s), gently
absorbed at k~0.24/frame -- SUB-UNIT, at the noise floor. The ~2.9u figure was stale:
it predated the peer body-channel swap (
|
||
|
|
aab7a8a137 |
Combat: FIX weapon effectiveRange -- it degraded with heatLoad, not host-zone damage (task #50)
MechWeapon::UpdateTargeting computed effectiveRange = (1 - heatLoad) * weaponRange, reading the weapon's own inherited HeatableSubsystem heatLoad. The authentic decomp (@004b9bdc:6983) reads *(weapon+0xE0)+0x158 = Subsystem::damageZone->damageLevel -- i.e. effectiveRange = (1 - HOST-ZONE DAMAGE) * weaponRange. Same @0xE0-DamageZone-vs- heat misattribution already corrected in HeatSink::UpdateCoolant (heat.cpp:803). Impact: for a charge/discharge weapon (ER laser) the weapon's OWN heatLoad swings 0..1 every fire cycle, so effectiveRange collapsed toward 0 and the weapon was perpetually "out of range" -> Emitter::FireWeapon's `if (dist <= effectiveRange)` gate skipped SendDamageMessage -> NO damage submission and hence NO impact explosion. The beam still rendered (beamFlag/beamEndpoint set before the gate), so the shot LOOKED like a hit but did nothing -- the user-reported "lackluster/absent laser hits, esp. the ER medium, on mechs AND buildings". PPCs mostly worked only because their heatLoad happened to sit low/stable. Fix: read the QUALIFIED this->Subsystem::damageZone->damageLevel (the MechSubsystem shadow is a shim -- heat.cpp:812) so an UNDAMAGED weapon holds its full, STABLE weaponRange, and range shortens only as the weapon's host zone takes battle damage. Verified (parked in range of a building, autofire): laser effRange 500 STABLE (was fluctuating 0/59/340/424 -> mostly out of range); impact explosions 13 in 22s (11 laser id=16 + 2 PPC), up from ~2. Lasers now consistently damage + spawn FX. Also adds env-gated diagnostics used to root-cause this: [fireW] range trace + per-weapon explID (emitter.cpp), and BT_FIRE_AT_STRUCT (mech4.cpp) which designates the nearest world structure so weapon-vs-structure fire can be tested without the screen aim ray. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
de8f6d02c1 |
MP: world structures (garages/walls) TARGETABLE -- boresight ray-tests the static collision tree (task #50)
The boresight's non-mech pick only sampled the VISUAL heightfield (BTGroundRayHit), which on arena1 is a single flat 'sky'-named ground mesh (and class-42 BuildTables likewise holds only 'sky') -- so shots passed THROUGH the garages/walls and only mechs moved the HUD range axis (user-reported regression: "buildings used to move the range axis; now only mechs do, and you can only fire at a peer"). Real fix: the boresight now ALSO ray-tests the ZONE'S STATIC COLLISION SOLID TREE -- the same geometry that already blocks the mech's walk. Authentic engine mechanism: Mover::FindBoxedSolidHitBy already tests the static world via zone->GetCollisionRoot()->FindBoundingBoxHitBy(line). Factored its "static world" tail into Mover::FindStaticSolidHitBy(Line*) (static solids only, no movers), wrapped by Mech::WorldStructurePick(start,dir,range,&hit) (builds the world-space Line, reads the entry point back via line->FindEnd since HitByBounded clips line->length=enter). Boresight pick order is now: closest MECH (PickRayHit, damage zones + lock) -> closest STRUCTURE (WorldStructurePick; occludes a mech BEHIND it; designates the gBTTerrainEntity sentinel + entry point, so the range caret reads the structure distance and NO lock ring draws, mech4.cpp:4529) -> flat ground (BTGroundRayHit) -> sky (fire-at-nothing). Also un-skips arena1's misnamed-'sky' flat ground so the ground tier works (btvisgnd geometry-aware skip + [mapent]/[rendent] census). Verified headless: a BT_WSWEEP horizontal ray-fan on arena1 tracks position (3/24 hits near the boundary -> 17-21/24 inside the interior garage cluster -> 3/24 past it = DISCRETE interior solids, not an enclosing box), no crash/assert/AV. Interactive aim (BTGetAimRay) can't run headless (no window -> noRay), so the sweep is the headless proof; interactive aim is user-verified. Note: FindBoundingBoxUnder (the ground/containedByNode BoundingBoxTree) is DOWNWARD-only (gravity/ground-snap: *height = FindDistanceBelowBounded), useless for a horizontal boresight; the static SOLID tree's FindBoundingBoxHitBy is the only ray-vs-world query. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ee6e19e86e |
Combat: RESTORE authentic fire-at-nothing + non-mech range axis (task #50 / Discord report)
Determined the authentic behavior from the decomp (HudSimulation + Emitter,
part_013.c:5619-5670 / 7689-7778) and fixed two regressions the players hit:
(A) You could only fire when a MECH was locked (couldn't fire at nothing).
(B) Buildings/structures/ground stopped moving the range axis; only mechs did.
AUTHENTIC (T1): a weapon discharges iff the target slot mech+0x388 != 0
(Emitter::FireWeapon FUN_004bace8:7727), and 0x388 is whatever the BORESIGHT
designates -- mech OR world geometry -- NOT a manual mech lock. The RANGE readout
(HUD+0x1ec) moves for ANY designated target (mech OR structure/ground); the LOCK
ring is mech-only (target has a damage-zone table, HudSim :5619). So the pod
'fired freely at nothing' and buildings moved the range caret.
ROOT CAUSES (both content-triggered, not a targeting-code change):
1. arena1's ONLY class-42 world entity is the 10000x10000 FLAT GROUND plane at
y=0, MISNAMED 'sky' -- SkippedName filtered it out by name, so BuildTables
ingested 0 geometry and gBTTerrainEntity stayed null -> nothing groundable ->
mech-only targeting. (LAST.EGG rewrite ~bb795e2 lost whatever terrain the
working scene had.)
2. A boresight that hit nothing set MECH_TARGET_ENTITY=0 -> no discharge.
FIXES:
- btvisgnd.cpp: geometry-aware skip -- keep a wide, near-flat, near-ground plane
even if name-skipped (it is the arena floor); still skip true domes/backdrops.
Adds mesh y-bounds. arena1 now ingests 1 ground instance (was 0).
- btl4vid.cpp: capture ANY world-geometry entity as the non-mech pick sentinel
(the default render case is world-only), not only Terrain-derived ones.
- mech4.cpp: FIRE-AT-NOTHING -- when nothing pickable is hit, designate a
max-range point (1200, the HUD default 0x44960000) along the boresight using
the world sentinel, so 0x388 != 0 and the weapon discharges (no zone damage /
no lock ring, since the sentinel has no damage-zone table -- mechs only).
Verified autonomous: arena1 ground ingested; [target] shows 'fire-at-nothing
(max-range designate)'; weapon discharges at empty space ([fire] explode
resolved); range axis (sShownRange->BTSetHudTargetRange) slides to the designated
distance. Diagnostic: BT_GROUND_LOG (world-geometry ingest + skip reasons).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
23f1532fb0 |
MP: peer true-mirror cadence -- drive the body clip from actual replicated motion (task #50)
Toward a truer mirror (user: peer 'hesitates/skips on accel/decel, master has
smoother transitions'). The body channel FOLLOWS the master's replicated STATE
(
|
||
|
|
f094d78df1 |
MP: restore peer turn-step on the body channel -- arm turn state 4 from replicated turn (task #50)
The body-channel swap (
|
||
|
|
96a896ae43 |
MP: peer poses from the BODY channel following replicated state -- the authentic single-channel design (task #50)
Answers 'is this different from the original?' -- NO, it RESTORES it. Decomp
(workflows wh1h5gnmc/w1s9ou02o): the 1995 game had ONE live skeletal channel,
the BODY channel FUN_004a5678 via IntegrateMotion; the LEG channel FUN_004a5028
is DEAD CODE (zero call sites, SetLegAnimation never runs). Both master and peer
posed the whole skeleton from the body channel, the PEER FOLLOWING the master's
replicated body-anim state (bodyStateAlarm@0x728, set by the type-3 reader's
SetBodyAnimation, mech.cpp:1913) + replicated bodyTargetSpeed@0x6b4.
Our port RESURRECTED the dead leg SM as the peer's poser (mech4.cpp AdvanceLeg-
Animation) fed a LOCALLY re-derived commanded speed. The leg SM's phase-
independent pre-switch wind-down (mech2.cpp:560-568: force-jump {6,7,8,9}->stand
when legCycleSpeed<=0) made the peer SKIP the master's decel state 8 and snap to
stand while the body coasted -- the 'slid forward after legs stopped' slide, the
reverse 'slippy sliding in place', and the whole re-derivation desync class.
FIX (peer branch, gated BT_PEER_LEGCH=1 to revert): pose+travel from
AdvanceBodyAnimation(dt, mj=1) -- the body channel, which follows the replicated
state and has NO early wind-down, playing the master's exact clips (walk/decel-8/
reverse/stand). ZERO new netcode -- the state is already on the wire and already
decoded into the body channel; we were simply posing from the wrong (resurrected,
dead) channel. Single-player untouched (replicant-gated).
Verified autonomous (through-zero sweep): no crash; body channel advances
(frmAvg 0.3-0.75, states 5/6/7/10 following master); slide-in-stand events
519 -> 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
3792a04661 |
Fix throttle-detent Abs() macro bug + KB analog-throttle + slide diagnostics (task #50)
Uncommitted work from the speed-model + peer-motion investigation: - btl4mppr.cpp: the L4MechControlsMapper full-throttle detent used the unparenthesized Abs() macro (STYLE.H:118) on an expression -- Abs(throttlePos - 1.0f) mis-expands to -(throttlePos + 1.0f), always <= 0.05, so the detent snapped throttle to full EVERY frame. Diff into a temp first (same class as the |
||
|
|
a9ab3db952 |
MP: FIX peer gliding-stops -- snap the residual offset when legs are at rest (task #50)
User-reported 'gliding stops': when the master halts, the peer's legs wind down to standing but the error-absorption kept sliding the body forward to close a small residual offset -- visible precisely because no leg motion masks it. With the feet planted a sub-unit correction is imperceptible, so resolve it in one frame (k=1) when replLegAdv~0 and the offset is small, instead of gliding it in. Large offsets at rest (a real teleport) still ease. Measured (stop-emphasis sweep): maxStep 1.77u->0.98u (now sub-unit), peer drift 2.1u->1.0u mean. The remaining <=1u snaps are the leg-vs-body channel-shape mismatch -- likely at/near the in-spec floor for this test rig (the Python console relay batches packets, unlike the real dedicated pod network). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
38fed81d8a |
MP: tighten peer re-anchor -- error-proportional absorption (residual snap, task #50)
Chasing the ~2.9u residual snap: instrumented BT_MIRDIV and isolated it to the PEER side (master send-mirror error is accurate, mean 0.39u < 0.55u threshold; peer drifted 3-11u from received authority). Root: the master models the peer with the BODY channel but the peer moves with the LEG channel -- slightly different travel/frame -- so the master deadband does not fire exactly when the peer drifts, and the fixed ~1/3s offset decay bled slower than accel/decel drift accumulated. A hard ground-snap (authentic FUN_004ab1c8:14985) popped instead (records not perfectly dense on the one-box relay). FIX: error-proportional absorption -- bleed the offset to updateOrigin at a rate that scales with the error (k 0.15..0.6/frame): small steady offset absorbed gently (no foot-pop), large speed-change drift caught in a few frames. Measured (through-zero sweep): peer drift 11u->2.1u, maxStep 2.9u->1.77u, ratio 1.043->1.036. Remaining <=1.8u snaps are the leg-vs-body channel-shape mismatch; fully closing needs a leg-channel send-mirror (second instance) -- deferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b0137427a6 |
MP: PROMOTE coupled peer motion to DEFAULT -- user + metrics confirm (task #50)
User confirms 'way better'; single-player un-regressed (master walks correctly, the bodyTargetSpeed change only affects the invisible mj=0 body channel). Flip the authentic-coupled path ON by default: - peer position: gait-coupled linear (was velocity dead-reckon); BT_DR_POS=1 reverts - master send-mirror: gait projection of projectedOrigin; BT_NO_MASTER_GAITMIRROR reverts The two-source split (position from velocity + animation from commanded speed) that mismatched during speed changes is retired. Residual: occasional ~2.9u snap from the leg(peer)-vs-body(mirror) channel mismatch -- second-order, tracked for follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c52a1ad79a |
MP: COUPLED peer motion -- authentic gait-driven position + master gait send-mirror (task #50, env-gated)
Finishes the coupled IntegrateMotion path the decomp workflow (wh1h5gnmc, 3 make-or-break claims CONFIRMED by adversarial verify) proved authentic: - Peer LINEAR position is SINGLE-SOURCE gait: IntegrateMotion integrates the body-channel cycleDistance into projectedOrigin.linear@0x260, copied verbatim to localOrigin -- NOT velocity dead-reckon (that is angular/heading ONLY). 0x260 'motionDelta' and 'projectedOrigin' are literally the same field; the 'contradiction' was our reconstruction's two misnamed shadows. - Master and peer run the SAME predictor; the master's SEND-mirror must run it too (binary FUN_004a9b5c @0x4aab9c) so a gait-driven peer stays anchored. Our mirror used the constant-velocity deadReckoner + overwrote bodyTargetSpeed live every frame -> could not model a gait peer (the tug-of-war). - T4 CONFIRMED: both channels pose the full skeleton (same JointedMover); keep the peer LEG channel (our body channel is unbound on the peer). CHANGES (all behind BT_MASTER_GAITMIRROR / BT_ROOT_POS, DEFAULT OFF -- zero change to shipped behavior until visually confirmed): - mech4.cpp: send-mirror advances projectedOrigin by the mj=0 body channel travel (mirrorBodyAdv) rotated by heading + last-sent angular vel, re-seeded to localOrigin on each send, instead of the deadReckoner; and stops the per-frame live bodyTargetSpeed overwrite so the mirror slews toward last-sent. - mech.hpp/mech.cpp: mirrorBodyAdv member. Measured A/B (autonomous through-zero sweep circle, the speed-change regime): velocity two-source (current default): ratio 1.0022 BUT user-visible glitch coupled, NO mirror (dense): ratio 1.86 (tug-of-war) coupled + gait mirror, dense OFF: ratio 1.14, back-steps 0.1% coupled + gait mirror, dense ON: ratio 1.043, back-steps 0.1%, maxStep 2.9u The single-source coupling is proven (backward-stepping 0.1% vs the split's churn). Residual 2.9u occasional snap = leg(peer)-vs-body(mirror) channel mismatch, second-order. Awaiting visual confirmation before default promotion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
702822f9c5 |
MP: root-motion peer-position EXPERIMENT (env-gated BT_ROOT_POS; default unchanged) + KB notes (task #50)
The user's architectural question -- 'should animation and velocity even be
allowed to be uncoupled?' -- is decomp-CONFIRMED correct: the original peer
(FUN_004ab430 -> FUN_004ab1c8) drove POSITION FROM THE CLIP'S ROOT TRAVEL
between records (feet<->ground locked by construction) with a pose-sync
offset decay absorbing record corrections. Our port dead-reckons position
from velocity while the legs run on commanded speed -- the two only agree at
steady state, mismatching exactly during speed changes (the reported glitch).
This commit lands the peer half of the coupled architecture, env-gated:
- BT_ROOT_POS=1: peer position += clip travel rotated by heading (mirror of
the master world-step @3325, == IntegrateMotion tail @004ab1c8); pose
records absorbed via the authentic offset-decay (motionEventVector
mechanism) instead of snapping.
- Measured A/B (through-zero sweep circle, harshest speed-change regime):
velocity-lerp (default): step ratio 1.0022 (clean)
root-motion + sparse records: 11-17u anchor snaps (master's velocity
mirror no longer models a gait-driven peer -> under-sends)
root-motion + dense + offset-decay: evenness OK but ratio 1.86 --
authority tug-of-war (double-authoring, exactly the D5 risk).
CONCLUSION: the coupled peer requires the MASTER side of the original
architecture too (gait-driven send-gate mirror / channel-B IntegrateMotion
projection) -- a coherent rebuild for a fresh session, not a peer-only
patch. Default therefore stays velocity dead-reckon.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
2c6db6a2de |
Diag: speed-change investigation -- authentic analog throttle CONFIRMED, harnesses + gait probes (task #50)
Decomp workflow w0odszxro settled the speed model: the pod throttle was ANALOG- CONTINUOUS (RIO Ranger, 800 ADC counts, 0.05 deadband; sole detent = snap-to-1.0 within 0.05 @004d196c). '5 speeds' is FALSE -- the 1-5 keys are MFD mode pages; the 0..5 stepper is HUD/radar zoom. BONUS: throttleState@0x4a4 is a MISNOMER -- binary census proves it is the fall-contact surface material cache (0..7, init 2=Concrete), written only at knockdown; rename pending. Empirical rule-outs (autonomous drive-sweep harness, this commit): - Record density: with the clock guard + incremental heading in, a per-frame continuous demand sweep measures IDENTICAL to constant throttle on position evenness ([repljit]), render heading ([rendhdg]), and gait cadence ([gaitev]). - Type-3 stomps: ~0 fire in sustained sweeps (only at launch) -- not the driver. - The 0.05-grid keyboard publish experiment is retained env-gated OFF (BT_GRID_LEVER) -- the ADC was ~continuous, so the grid is NOT authentic and gains nothing measurable; default publish stays continuous (authentic). New tooling: BT_DRIVE_SWEEP[0] (forced-drive triangle sweep, optional through- zero), BT_FORCE_STEP (0.05-grid variant), BT_GAITEV (per-frame leg-clip advance + state-flip + demand-change stats), [t3rx] (type-3 stomp trace). STATUS: user still reports visible speed-change glitches in interactive play; all harness metrics saturate at baseline -- next step is probes ON the user's interactive session (their eyes + instruments on the same run). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d78e77bf84 |
MP: FIX walk+turn peer skip -- incremental heading integration + scalar yaw mirror (task #50)
The user-keyboard regime (steering WHILE walking) fired BOTH dense record streams at once and exposed the last divergence: our peer heading used the engine Mover::DeadReckon slerp-toward-projection, whose angular projection reads the SHARED lastUpdate/nextUpdate timebase. The dense type-0 pose stream resets that timebase every frame while walking while RESTORING a stale orientation (the authentic case-0 strip, verified against FUN_004a1232 case 0) -- so the angular target barely advances from a stale base and the slerp DRAGS the heading back every frame. Measured: peer yaw advancing at ~40% rate with half the frames stepping BACKWARD. Pure-spin and pure-walk tests never showed it (single stream) -- why autonomous looked smooth while keyboard play skipped. AUTHENTIC FIX (decomp FUN_004ab1c8 -> FUN_004ab188/FUN_00409f58): the original replicant integrates its heading INCREMENTALLY from the CURRENT pose -- exact rotation of (replicated yaw rate * dt) composed on each frame -- and re-anchors on type-4 receipt. It never slerps toward a projected angular target. - mech4.cpp peer branch: save heading, let DeadReckon own LINEAR only, then integrate heading incrementally (ReconQuatIntegrate); on angSyncLatch (new type-4) re-anchor to updateOrigin. - mech.hpp/mech.cpp: angSyncLatch member (angular analog of poseSyncLatch), armed by ReadUpdateRecord case 4. - SCALAR peer-yaw mirror (angMirrorYaw/Rate/Time, re-based in the type-4 writer): replaces the quaternion projectedOrigin mirror for the ANGLE deadband -- the old one was recomputed each frame by the master's own reckoner from timing it does not control and false-fired in pi-waves (measured maxAng~=pi bursts -> periodic resync floods). - Dense-rot type-4 send REMOVED (was masking the old crude projection; not authentic; churned the shared horizon). Orientation now rides the sparse angle/velocity deadband resyncs exactly as the binary's. Verified live-autonomous: - pure spin: 59/59 perfectly regular peer yaw steps; master resyncs 0/s with mirror drift ~5e-7 (records near-silent, authentic sparse model). - walk+turn circle (the user regime): peer sim yaw monotonic at exactly the master's rate (0.00556/frame @ 0.327 rad/s), no backward steps, no stalls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b7be95b584 |
Diag: BT_TRNRATE probe -- turn-in-place body-rate vs trn-clip cadence (task #50)
Confirmed via BT_TRNRATE + direct disasm of the un-exported perf drive (0x4aa3d3) that turn-in-place rate == walkingTurnRate (speed=0 collapses the lerp) and the trn clip cadence is authentically FIXED -- so the reconstruction is faithful and the residual legs-vs-body slip is authentic (decoupled by design; scaling the clip would deviate from the decomp). Probe left env-gated for future asset-tuning checks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a8eb8a427f |
MP: FIX peer spin hang/divergence -- exact quaternion integration + frequent orientation (task #50)
Answers 'how did the original handle this?' from the decomp (subagent hunt):
the 1995 binary's replicant reckoner (FUN_004ab1c8 -> FUN_00409f58, part_000.c:9359)
integrates heading EXACTLY: build a unit axis-angle rotation quaternion from
angularVelocity*dt ({axis*sin(t/2), cos(t/2)}) and Hamilton-multiply it onto the
heading (FUN_00409d9c) -- exact for any timestep, stays on the unit sphere. It
further carries the full orientation quaternion in the FREQUENT pose record
(FUN_0040a938, 7-float pose), so the dead-reckon gap stays tiny.
Our reconstruction diverged two ways, both fixed:
1. ReconQuatIntegrate (mechrecon.hpp) -- the reconstruction of FUN_00409f58 -- was
STUBBED as , a crude small-angle VECTOR add. Restored to
the real exact axis-angle composition. (A 'no stand-ins' violation: the comment
even wrongly claimed Quaternion::Add == FUN_00409f58.)
2. The engine Mover reckoner (MOVER.cpp AcceleratedDeadReckoner/LinearDeadReckoner)
also did the vector Add on projectedOrigin.angularPosition -> over a long peer
record gap it diverged to ~180deg then snapped (the reported spin HANG/hesitation).
Routed both through a new ExactAngularProject() helper (same exact math).
3. Orientation only rode the sparse type-4 resync; during a PURE spin the linear
dense-send never fires (not translating), so the gap ballooned (~1.6s) and the now-
exact projection sat far ahead -> the slerp jumped. Added an ANGULAR dense-send
(resync every frame while |yawRate|>0.1), mirroring the original's frequent-
orientation model -> gap stays tiny -> smooth.
Verified live-autonomous (BT_AUTODRIVE+BT_FORCE_TURN + BT_RENDHDG render-rate probe):
the ~180deg divergence + multi-radian snaps are GONE (rendered maxStep 0.05-0.10 rad,
no jumps). User confirms: no frame hang/hesitation. MOVER.cpp change is strictly
more correct (exact==crude for the small per-frame master case; only large-gap peer
extrapolation changes), so walking is unaffected.
KNOWN REMAINING (separate, smaller): the turn-STEP leg animation (trn clip, mech2.cpp
advance_normally) runs at a FIXED idleStrideScale cadence that does not scale with the
rotation rate, so the legs lag/skip vs the (now-correct) body rotation. Next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9eff043973 |
Diag: render-rate rotation-evenness probe + angular-drift instrumentation (task #50 spin residual)
Autonomous headless spin harness (BT_AUTODRIVE+BT_FORCE_TURN) + probes that finally measure what the eye sees, not the sim heading: - BT_RENDHDG (L4VIDEO render loop): per-RENDERED-frame peer heading step evenness -- avgStep/maxStep/max-avg ratio. Confirmed the residual spin 'hesitation' is UNEVEN rendered rotation (max/avg ~2.8x), correlated with the byAngle resync-flood bursts (maxAng ~= pi), NOT a render-vs-sim rate stall (render redraws a fresh heading every frame). - BT_SPIN / BT_ANGSIGN: resync trigger breakdown (byAngle/byVel/byRest) + the frame-level local/update/projected angular-Y that exposed the Abs() macro bug. - gBTReplRenderYaw: peer heading published from the sim to the render probe. - Normalize projectedOrigin.angularPosition after the peer-mirror advance (adding a scaled ang-vel VECTOR to a quaternion denormalizes it) -- correct, but NOT sufficient: the pi divergence is REAL, from the crude large-angle quaternion projection over the sparse angular-record interval during a PURE spin (linear dense-send doesn't fire when not translating; type-4 is the only orientation carrier and resets the horizon, so dense type-4 -> half-rate). Residual ROOT (scoped, not yet fixed): the reckoner (MOVER.cpp:457-466) projects rotation by ADDING vector*t to the heading quaternion -- a small-angle approx the original kept valid via dense records; our pure-spin case has 1.6s record gaps so it diverges to ~180deg -> uneven render. Fix path = proper quaternion integration in the reckoner, or refresh the angular origin without the type-4 horizon reset. Both touch the shared engine dead-reckon -> aligning before the change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7615ecd316 |
MP: FIX peer spin freeze/half-rate -- unparenthesized Abs() macro floods angular resync (task #50)
Root cause found via autonomous headless spin (BT_AUTODRIVE+BT_FORCE_TURN) +
frame-level [angsign] probe: the STYLE.H:118 macro
#define Abs(value) ((value>0) ? value : -value)
has NO parens around value, so Abs(a-b) mis-expands to (a-b>0 ? a-b : -a-b) ==
-(a+b) on the false branch, NOT |a-b|. The angular resync velocity gate passed
an EXPRESSION: Abs(localVelocity.angular.y - updateVelocity.angular.y). For a
steady spin the two are equal, so a-b==0 takes the false branch and yields
-(2*rate): on a REVERSE spin that is +2*rate > velDb, firing a type-4 resync
EVERY frame. The resync flood reset the peer's dead-reckon horizon every frame,
pinning its angular slerp at ~half rate and (as the drift periodically ran to
180deg) freezing the rotation for seconds while the leg turn-clip kept stomping.
Confirmed live-autonomous: velDrift 0 (was +2.618), byVel 0 (was 55/55), peer
rendered-rotation median dyaw/expected 1.00 (was 0.49), no multi-second dt gaps.
Fix (mech4.cpp resync gate, reconstruction side -- the engine macro is original,
the original master-perf avoided it by diffing into a temp):
- velDrift: diff into a temp, explicit (d<0?-d:d) so the macro never sees an
expression.
- angDrift: already reworked to a single-variable wrap-safe yaw delta (also
dodges the macro) -- and it corrects a prior units/wrap bug (was comparing a
raw quaternion-Y component against a radian deadband).
- velocity gate now diffs vs updateVelocity (the value the peer extrapolates
with), same scalar representation as localVelocity.
- BT_SPIN / BT_ANGSIGN / updYaw diagnostic probes (env-gated).
KNOWN RESIDUAL (smaller, follow-up): the ANGLE gate still bursts when the crude
large-angle quaternion projection in the reckoner runs updateOrigin stale between
writes and drifts ~180deg; causes occasional single-frame hitches (peer median is
still 1.0), not the freeze. Separate from this fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
6374efc26a |
MP: FIX peer gait jitter -- feed the leg channel the REPLICATED commanded speed (task #50)
Decomp-verified via multi-agent investigation (workflow wv1km7lvc, adversarially
checked against reference/decomp/all/part_012.c + engine T0).
ROOT CAUSE (D3 CONFIRMED): the peer's leg channel FUN_004a5028 reads its speed
demand from mapper->speedDemand (**(mech+0x128)+0x128, part_012.c:11947/11975/
12028). The reconstruction FABRICATED that input from the noisy dead-reckoned
velocity magnitude and PINNED it flat with a standSpeed*1.05 floor across the
walk threshold -- the leg stutter/skip + cadence-vs-travel desync, worst on
accel/decel. The authentic input is the replicated commanded speed
bodyTargetSpeed@0x6b4 -- the master's own throttle-commanded speedDemand
(mechmppr.cpp:749), stamped into every record (mech.cpp:2036) and read back on
the peer (mech.cpp:1833/1841/1871). It is the IDENTICAL value the master's leg
channel consumes, so the peer clears the stand->walk gate exactly when the master
does and ramps cadence continuously through accel/decel.
Confirms the 07-13/07-14 regression:
|