417008a6d4817f056abbdb4ab52ad2f46b114307
734
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
417008a6d4 |
#118 faithful settlement: MFD soft keys NEVER eject -- panic/keypad only
The streamed .CTL dump (BT_CTRLMAP_LOG, 109 records on the madcat) settles
the eject-input question from CONTENT: no 0x200000 record exists -- the MFD
soft keys carry their authored per-page routes unconditionally (elem 0xB on
a weapon eng page = msg 0xB EjectAmmo, the round eject), so they must never
route to pilot-eject. The invite-cell routing (
|
||
|
|
b4b98a64c0 |
#118 fix-the-fix: armed eject routes ONLY the invite cells + Panic
Field-caught by the operator within minutes: the blanket MFD key-space routing (0x00-0x2F) made the DISPLAY navigation key eject the mech mid-page-switch while armed. Narrowed to exactly the guarded PANIC button (0x3D) and the three eng-page EJECT INVITE cells (0x0B/0x23/0x03, the addresses the engEject 0x85 alarm lamp flashes on). Navigation is safe while armed; the flashing cell still punches out (re-verified through the click seam: armed -> press 0x0B -> PUNCH-OUT). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b9430cc5a7 |
#118: the MFD eject cell ejects -- all pilot-keypad-space keys route while armed
The operator's pod flow ("the engineering panel's bottom-left button flashes
EJECT, you push it") is input #3 of 3. The eng-page eject cell resolves to
MFD side-column addresses 0x0B/0x23/0x03 (kBTEngBankTop - 4 per bank), and
the binary's ONLY armed eject binding is the KeyboardPilot group (0x200000
-> mech msg 0x19; exhaustive 0x200000 scan: no other consumer) -- so on the
pod those keys report through the pilot keypad matrix. Desktop mirror: MFD
key-space presses (0x00-0x2F) and Panic (0x3D) also land as pilot-keypad
keys. Outside panic mode the binding is mode-masked dead -- normal MFD
operation untouched; armed, the flashing cell (or any keypad key -- the pod
semantic) punches out.
Verified through the click seam: gens dead -> armed -> press 0x0B ->
PUNCH-OUT.
All three eject inputs now live: Panic button, Backspace/pad LeftThumb,
and the flashing MFD eject cell.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
0bd9bb6f51 |
#118: the FLASHING eject invite is authored -- engEject alarm items found
BT_LAMP_LOG now dumps each gauge-alarm stream item as it reads ([galm-item] sub/cond/itemCond/lampCode). First dump against the shipped content settles the operator's "shouldn't something flash?" question: GeneratorA itemCond=4 -> lampCode 0x85 (engEject) + 0x80 (gotoEngineering) GeneratorA itemCond=5 -> lampCode 0x85 + 0x80 The 1995 pod FLASHES "GOTO ENGINEERING" and the engineering page's EJECT cell when a generator goes OUT (conditions 4/5) -- the authored eject invite. The Panic button lamp itself is solid by authoring ([lamp] 0x3d <- 0x3c, no flash bits) -- both halves of the field observation are genuine. Remaining: the BT_KILL_SUBSYS scalpel skips the authentic state-4 transition, so the flash did not fire in the bench; verify a genuinely damaged/overheated generator reaches state 4/5 and lights the invite (the #118 "Generator Out vs !=4 clause" tail). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
751b1159b5 |
#118: the eject slot graphic lands -- BitMapInverseWipeScalar reconstructed
The operator's field report ("eject slot flashing but no graphic in it") was
the tracked-NULL bring-up stub in BallisticWeaponCluster. Reconstructed the
missing gauge class from the binary:
- BitMapInverseWipeScalar @004c61c8 (vtable 0x518a14; base @004c5e84,
Execute @004c5fb8, BecameActive @004c5fa4): a bitmap COLUMN SWEEP whose
level tracks a live Scalar -- two spans with INVERTED colour pairs,
[0..level] fg colorA / bg colorB, remainder the inverse.
- Wired per the binary call site (part_014.c:2312): bteejtm.pcc at (0xF,0)
on the weapon's eng port, colours 0/0xFF, watching the weapon's
PercentOfEject (@0x3F8, the round-eject 0..1 progress) via a new
complete-type bridge BTWeaponPercentOfEjectPtr (databinding rule).
At idle (level 0) the EJECT graphic now renders in the slot; during a
round-eject cycle it sweeps with the countdown. BTEEJTM.PCC ships in
content/GAUGE (the loose gauge bitmap set). Smoke-tested: constructs clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
611b1a8bbd |
#118: the PANIC BUTTON ejects -- keypad-bank routing + the 0x17/0x19 id bug
Two finds close the press path the operator field-tested: 1. The pod's guarded PANIC key reports through the PILOT KEYPAD bank -- the binary's ONLY panic press binding is keyboardGroup[KeyboardPilot].Add( mode 0x200000, mech, 0x19) (FUN_004d266c); there is NO buttonGroup[0x3d] consumer (0x3d is the lamp address). The desktop Panic click now ALSO lands as a pilot-keypad key (PadRIO::EmitButton -> EmitKeypad), so the armed-mode binding fires; a healthy press stays a mode-masked no-op, exactly the pod behaviour. 2. KeypressMessageID resolved to 0x17 via enum-chain arithmetic; the binary passes LITERAL 0x19 (two mapper ids are unreconstructed). The armed keypad send arrived at the Mech as msg 0x17 -- no handler, silently swallowed. Pinned to 0x19 (Mech::EjectPilot's id on the OWNER receiver -- the collision is the design). Verified headless through the REAL click seam (BT_BTNTEST=61): generators killed -> panic-arm ON -> screen-click Panic -> [lbe4key] unit 0 mode 0x650421 -> PUNCH-OUT. Also: IsDisabled eject refusals now log; [padkey]/ [lbe4key] seam tracing under BT_PAD_LOG. Still open (cosmetic): the flashing eject slot on the engineering MFD shows no graphic -- the streamed page element's pixmap is unreconstructed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0089b3284f |
Eject firsthand prep: G = coolant Flush binding + BT_FLUSH_HOLD scalpel
The Flush ACTION existed in the input engine but no key was bound in the shipped CONTROLS.MAP -- desktop players could never hold the flush. G (free) now maps to it. BT_FLUSH_HOLD=<frames> extends the BT_FLUSH_TEST scalpel past its 60-frame edge test for tank-dry benches. Dry-run finding (fl.log): a full flush drains the tank to 0/14 in ~30 s, but does NOT arm eject by itself -- InjectCoolant moves tank coolant INTO the loops (the bank RISES), and idle heat draw is too slow to burn the loops down to the 5% arm threshold in minutes. Firsthand arming needs sustained fire after tank-dry (or coolant-loop leak damage / generator loss). Matches the binary's intent: eject arms when LEAKED nearly dry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8165cee9da |
#118: the eject option comes alive -- per-frame arm chain closed (FUN_004a9b5c+0x10)
The evaluator's "unexported caller" found by E8-scan: the Mech MASTER PERFORMANCE (FUN_004a9b5c, in the 004a977x..004ab188 export hole) calls EvaluateEjectPermission as its FIRST act every frame. Without that call the port's ejectPermitted stayed at ctor-0 forever -- the panic-armed mode never raised, so the PANIC lamp never lit and testers "never saw the option for eject come alive". Restored at the top of Mech::PerformAndWatch (masters only, like the binary). Mislabel swept: mech+0x414 is ejectPermitted, NOT "missionReviewMode" -- the mapper's @004d196c edge-watch arms PANIC mode 0x200000 from it (the real review mode is the GLOBAL DAT_004fd550; btl4pb now reads that global, the GetMissionReviewMode stub is retired, mapper member renamed previousEjectPermitted). Verified live: kill 4 generators -> "[eject] panic-arm mode ON" -> the miniconsole Panic button lights dark->bright (pixel captures; the pad panel shades it from the same PadRIO lamp state the pod's physical button uses) -> BT_EJECT_AT press -> PUNCH-OUT -> respawn heals -> mode disarms -> healthy presses REFUSED. BT_EJECT_LOG=1 logs the arm edges. Note: no shipped gauge rides mode 0x200000 -- the 1995 eject indication is the button lamp; the weapon-eng MFD "UNJAM/EJECT" is the ammo-jam indicator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
440cee1e4f |
#87 dig closure: host response is linear (raw decomp re-read); the pixel curve was div-card firmware
Re-read the material damage watcher RAW (@004573e4 ctor / @00457784 change-push): 16 floats snapshotted, damaged = pristine x 0.1 (literal at the MakeMechRenderables call site, watching zone+0x158), linear lerp, 13 floats written back, then FUN_0048d4d4 -- which is "flush_material": a dpl command-stream marshal (opcode 100) to the i860 division card. The material->ramp->texel response curve was CARD firmware and is not in BTL4OPT.EXE; logged as an open question (ask: firmware/DIV docs/period screenshots). Field reconciliation, measured live: one 25-pt hit (ERLG class) snaps a 77-pt arm 0 -> 0.3247 in a frame -- "a single missile leaves visible armor damage" holds under the linear curve for heavy hits; 2-pt ERS creep (the leg-audit weapon) is the invisible case. Bench additions: BT_WALK_ZONES=<name-list> walker filter (concentrated per-region audits, scratchpad/night10/legwalk.sh) + BindArmourDamage ORPHAN inventory under BT_ARMOR_LOG (proved every drawn op is zone-claimed on the full tree). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fe48accb6b |
#87 root cause: the level-crossing zone-record send was missing -- observers never saw damage
The binary's effect watcher (FUN_0042aa2c) raises ForceUpdate(DamageZoneUpdateModelFlag) when a zone's damageLevel CROSSES a band-descriptor threshold (FUN_0042a5f4, master-gated on entity+0x28 & 0xc) -- that send is how every other pod's replicant learns zone levels mid-fight. The port's band hub kept only the graphic-state branch, so an observer's copy sat at 0.0 until destruction: no enemy hull darkening, no doll movement, ever (the night-10 "no armour discoloration" report). Restored the level branch with the already-reconstructed DescriptorCrossed (@0042a5f4) + the master gate on both branches (mesh swap still runs on every instance). 2-node verified: A-side armour watcher pushed 114 level changes; every replicant peak matched the master's finals to 4 decimals (dtorso 0.9321 == 0.9321). Render path cleared separately (gotcha #23 discharged): BT_ARMOR_FORCE 0/1 A/B captures prove the tint renders (hull -> charcoal at 1.0). Solo perception is the authentic economy: 2-25 pt lasers vs 68-185 pt pools. Bench: zonewalk.sh node A now carries BT_ARMOR_LOG + BT_SHOT_EVERY (the standing zone-replication receipt) + digest_walk.py; armorpx.sh is the force-pair pixel rig. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cb5426cc7f |
KB: #124 close-out -- triangle pick + the authored one-hull-segment zone rule
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
00dfbf8f9f |
#124 RESOLVED against the authored skeleton: the pick's zone is the
struck drawn segment's dzone, ALWAYS -- the cylinder embellishment removed
The open question ('what zone did 1995 designate for hull hits') was
already answered in the KB's #73 dig [T0+T1]: no software pick writer
exists -- the pod's pick was a dpl scene intersection, struck triangle
-> DCS segment -> its SKL-authored dzone; the cylinder lottery was only
ever the UNAIMED path (missiles/splash/rams). The live segpick dump
completes it with the authored data: the madcat's pickable skeleton is
18 segments -- hip, shoulders, guns, thighs, knees, ankles+toes, and
ONE hull piece (jointshakey -> dtorso). No ltorso/rtorso/utorso/rear/
door/searchlight segments exist. Aimed fire at any torso panel
authentically designates CENTER TORSO; the side/rear/upper panels strip
via the unaimed path.
Changes:
* the shared-carrier -> cylinder override (yesterday's embellishment)
is REMOVED: triangle hit -> that segment's dzone, period;
* SegAimPoint: the walker's hull anchor moves from the joint ORIGIN
(crotch gap -- rays sailed between the legs to the terrain sentinel)
to the carrier segment's cull center (the chest);
* [picktri]/[pickwin] diagnostics grew tri/seg/zone fields -- three
consecutive 'mystery' regressions in this stretch were the STALE-EXE
trap (CWD-relative cmake --build from content/ resolved to a
nonexistent dir and the old binary kept running) -- the harness doc's
warning, hit again.
Final zone-walk matrix (spinning target, real MP, full cycles):
limbs 55/70 in-zone (legs/feet 6/6; arm strays = hull/occlusion);
hull-family aims -> dtorso 56/69; victim consumption matches. The
night-10 field table now reads as this model's fingerprint: 'only LCT
gets hits' from aimed energy IS the pod -- one hull piece, dtorso.
What was actually broken and is now fixed: the sphere mis-picks
(rgun-from-a-dtorso-aim class), the unaimed path's 180-degree frame,
and the vertical scatter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
1a3c268278 |
#124: the aimed pick now intersects the DRAWN GEOMETRY -- sphere
approximation retired; shared-hull hits route through the cylinder The zone-walk matrix caught the pick red-handed: with the servo verifiably holding the reticle on the dtorso segment, the per-segment BOUNDING-SPHERE pick returned rgun/ruleg -- the gun pods and legs thread the ray before the torso from most angles (its own comments admitted a foot could be unhittable behind its own knee). The 1995 pick was a dpl scene intersection against the drawn geometry. Restored that semantic: * BTGetPickMesh caches each segment d3d_OBJECT's triangles CPU-side once (its own BGF VB/IB, managed-pool locks); MechSegmentPick now runs sphere PRE-FILTER -> Moller-Trumbore nearest-hit across the threaded segments' posed meshes; the old smallest-sphere selection survives only as the no-triangle fallback. * Segments claimed as CARRIER by 2+ zones (the shared hull: madcat seg 4 carries dtorso+ltorso+rtorso+utorso+rears) cannot resolve one zone from geometry -- those hits return zone -1 WITH the accurate triangle point, and the victim's bit-verified (frame-fixed) cylinder assigns the panel by band/wedge. Unique carriers (legs, feet, arms, gun pods) keep the direct zone. * ZoneAimPoint: the walker aims at a zone's VISUAL center (largest pick object's cull-center) instead of the segment origin -- joint origins made feet/lower legs strike the part above. * Walker upgrades from live operation: 3-column truth (aim/pick/land), engage gate, damped servo with polarity watchdog, approach port. Zone-walk verdict (full cycles, spinning target, real MP): limbs 5-6/6 direct in-zone; every hull panel routes CYL; the victim's landed zones now include ltorso/rtorso/reardtorso/rearutorso/rearrtorso -- the panels night-10 reported unhittable. Residuals tracked on #124: rtorso/utorso aim-anchor placement, doors/searchlight small-zone sample, twisted-torso twist-sign verify. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
16e75d6e1a |
#124 bench: the ZONE-WALK MATRIX -- systematic per-panel targeting, operator-watchable
The precision rig the operator specified: two nodes, target visible, every zone aimed at deliberately through the REAL reticle/pick path. * BT_SPIN_SELF=<deg/s> (target node): rotates the node's own viewpoint mech in place (the BT_SPAWN_AT write pattern per frame) so every aspect passes the shooter's boresight; the operator watches this node's paper doll take the hits. * BT_ZONE_WALK=<secs/zone> (shooter node): walks the target's damage zones in order, resolves each zone's carrier segment's live world position (BTResolveSegmentWorld + the new BTMechZoneSegAndName bridge), SERVOS the torso twist + aim elevation until the centered reticle ray (BTGetAimRay) points at the segment, fires 3 laser pulses, advances. [walk] ZONE/FIRE/HOLD on A pairs with [dmghit] zone/level lines on B. * scratchpad/night10/zonewalk.sh: launch both + relay, map sed'd to grass/day, NO kill timer -- the session stays up for observation. Hard-won servo constraints (documented in test-harness.md so they are never relearned): the ENGAGE GATE (ray live + range<150 + bearing<1.1; outside it RELAX the twist -- servoing at the twist limit while the goto marches is a limit-clamp fight that visibly shakes the mech, and a 0.55 gate deadlocks against the goto's ~0.55 resting bearing); YAW POLARITY -1 (the twist cell's angular sense is opposite atan2(x,-z) world yaw -- operator-observed live, the SECOND witness for the #124 SelectSlice twist-sign flip) with a divergence watchdog that self-flips; damped correction (gain .40, cap .025/frame -- the aim ray lags the twist write a frame); and a one-shot approach PORT to 100u off the target instead of a cross-map march. First live run: walker cycles zones, settles, fires; the target's luleg climbed 0.458->0.523 under its own aimed pulses -- aimed shot, correct panel, damage consumed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b0b7adc4ea |
#124 CORRECTION: the frame adapter is a Z-REFLECTION, not a pi rotation --
proven by the mech's own pods
The first fix (
|
||
|
|
c5dfb002f3 |
#124: hit-location ASPECT was rotated 180 degrees -- the resolver now speaks the 1995 frame
Night-10's systematic targeting audit (rear panels unhittable from direct fire, Owens left torso hitting the RIGHT weapon pod and vice versa, missiles favoring the far side) measured down to one geometric fact: the authored wedge ring puts the Front* cells in the +Z arc (slice-name audit, bit-verified tables), i.e. the 1995 resolver frame has mech-forward = +Z -- while OUR engine's entity frame carries forward along -Z. Bench proof: a victim facing its attacker took frontal beams at local z = -4..-10, theta ~270 deg, resolving the REAR-named cells (zones 12/14/15) every time. The two frames differ by a pi ROTATION about Y -- rotation, not reflection, because the field flank observations CROSS (left->right) rather than persist. ResolveHit now rotates the local impact into the cylinder's frame (negate x and z; y untouched; handedness preserved so the SelectSlice torso-twist add keeps its sign). A/B (172 resolves, live missiles+beams on a facing victim): before theta~4.7 -> rear zones only; after theta~1.42-1.59 -> utorso/dtorso/ ltorso/rtorso/arm -- the front-authored cells, with the per-burst re-roll scatter intact. NOT this bug: the "one band LOW" Y-axis half of the night-10 table -- that is #16 (pick-ray boresight parallax, code-confirmed, still open). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
788031bc36 |
gitignore: tester notes + lastrun dumps join the field-log privacy rule
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1df2c571ee |
#119: heatLoad filter restored to the POD's 28 Hz sample cadence
The clipping coolant-leak voice ("warning... war... warning coo...")
measured to a ReportLeak relaxation oscillation at a drained tank:
draw = zoneDamage x heatLoad hunts across the authored 0.0025/0.003
hysteresis band (constants byte-verified) as the dry sink's conduction
collapses and the tank trickle refills it. State machine, constants,
refill dynamics and the sequencer's chase-and-cut stop (T0 AUDSEQ.cpp)
are all the binary's own -- the ONE divergence was the 15-sample
heatLoad filter: per-Perform in the binary = a 0.536 s window at the
pod's 28 Hz, but only 0.25 s at the port's ~59 fps -- heatLoad twice as
twitchy, the flap at double the pod cadence (the myomer-kinetic dt-less
class, third instance).
UpdateHeatLoad now accrues real time and samples at 28 Hz on any
machine (catch-up capped at the 15-sample window). Per-instance clock
in a static map -- the heat-family layouts are factory-size-locked
(sizeof(Myomers) == 0x358 exact), no new members. Ctor/reset prime
calls sample unconditionally.
A/B (260 s leak-to-empty bench): trigger fires 12 -> 8, transitions
11 -> 7. The residual slow restart at a bone-dry tank is authentic 1995
behavior (same engine sequencer chase-cut); a minimum-retrigger
interval would be an opt-in deviation for the operator to decide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
1efe8efc68 |
#118 tails: death score cost + the DeathWithoutHonor console notice
* SpecialCaseDeathPenalty restored to the engine ScenarioRole (SCNROLE.h/ .cpp) -- the 4.10-only role field the 2007 WinTesla source dropped (record slot [1] after KillBonus, fail-hard read; role+0x20; the DefaultRendererRate pattern). Its consumer decoded from the @004c05c4 export gap (@004c07cd, missing from the #52 reconstruction): every death with advancedDamageOn hands the ENGINE base ScoreMessageHandler (@0042da20 == PLAYER.cpp:138, a DIRECT call bypassing the BT type Verifies) a type-1 award of -penalty. Wired verbatim. Shipped content authors the key nowhere -> cost 0 in the field. NUANCE [T4]: the 1995 engine adds at Player+0x1c8, which no BT scoreboard reads (+0x278) -- the pod's cost may never have displayed; our single-cell port shows it. * The eject console notice: FUN_004c198c IS the ConsolePlayerMechDeathWithoutHonorMessage ctor (T0 BTCNSL.CPP survives and is compiled) -- "death without honor" is the punch-out record. BTPlayerEjectBookkeeping now sends it to the console host through the same ConsoleClientID path as the VTVDamaged notify, then latches suppressConsole: the eject notice REPLACES the death notify. Verified two-node: "notice -> console host 1" fires on the ejecting node; the relay-side delivery of client-5 traffic is a pre-existing question shared with the never-observed VTVDamaged sibling (operator-console workstream). * Eject alarm-10 audio: resolved NEGATIVE -- the five Eject*.wav ship in AUDIO/ but BTL4.RES references them nowhere (orphaned like the SQUAT clips); wiring them would be a stand-in. Operator-optional deviation. * RIO 0x38 panic control: deferred with the plumbing documented (the glass Flight-Controls window already renders the 0x38 bank; consumer = read buttonGroup[ButtonPanic] alongside the binding-engine edge) -- pod-hardware-only, untestable on this bench. Two-node verify (fresh-exe rerun after a stale-exe trap): REFUSED healthy, PUNCH-OUT after generator kill, death + wreck swap + smoke + un-wreck warp all replicated on the observer, respawned mech refuses again, console notice fired. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e996be249d |
EJECT/PANIC wired: Mech::EjectPilot (id 0x19 @0049f854) + the crippled-mech gate
The button died twice before reaching game logic: no handler (0x19 was
unregistered) and no sender (the pod's panic was a control bit, not a
mappable). Both halves reconstructed from raw disasm -- the handler AND
its permission evaluator sat in export gaps.
@0049f854 EjectPilot: press-only; gated on ejectPermitted (@0x414) and
!IsDisabled; console eject notice (relay wire = tracked tail; the
suppressConsole@0x258 latch that prevents the death double-notify IS
wired); graphicAlarm -> 10, which kills via the >=9 predicate; then a
self TakeDamage {inflicting=SELF, zone -1, Explosive, amount =
ScenarioRole::killBonus}. Role layout byte-settled via the role reader
@00429bec dest offsets + the ctor record copy: +0x1c IS killBonus, +0x20
is the 4.10-only SpecialCaseDeathPenalty, +0x28 returnFromDeath (all
prior citations reconciled). KillBonus authors NOWHERE in shipped
content -> the charge is 0 and the ALARM does the killing: our bench
outcome is the pod outcome.
@0049fa1c EvaluateEjectPermission: eject only from a CRIPPLED mech --
bank coolant fraction < 0.05 | zero live generators | live weapons
below mech+0x448 (no exported writer: zero, clause inert) | leg-gimped
novice. A healthy mech REFUSES the button; no free resets.
Input: binding-engine "Eject" action -- Backspace / pad LeftThumb
(default profile + shipped CONTROLS.MAP). Bridges per the databinding
rule: weapon/generator/bank/player reads land in their complete TUs;
MechSubsystem gains the both-cells destroyed accessors (gotcha #22).
Bench scalpels: BT_EJECT_AT=<frame> (path-identical synthetic press),
BT_KILL_SUBSYS now takes a comma list.
Verified single-node: healthy press REFUSED (x2), four generators
killed, next press PUNCH-OUT -> death, wreck smoke, respawn; the
respawned mech refuses again (permission re-evaluates after Reset).
Death rides the normal damage/death chain, so MP replication is the
proven path. Tails tracked: console relay notice, RIO 0x38 panic
control, alarm-10 eject audio/canopy, SpecialCaseDeathPenalty consumer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
e1ae92264a |
#96: the sim TIME MODEL closed from bytes -- myomer kinetic term restored to the faithful 28 Hz
The open question under every heat bracket was what the 1995 sim does with time. Answered at the byte level: * FUN_0041c018 IS the arcade Simulation::PerformAndWatch: slice = (till.ticks - last.ticks) / DAT_0052140c, stored, Perform(slice) -- VARIABLE-STEP in real SECONDS, line-for-line the WinTesla SIMULATE.cpp body. Every dt-carrying reconstruction is unit-faithful; no tick/second gap exists anywhere. * The myomer heat term's ONE caller is the unexported MyomersSimulation body @0x4b8b9a (E8-scan + raw disasm; export gap): once per Perform, raw slice, gated measuredVoltage>0 only. The dt-less kinetic pulse therefore fires at the FRAME cadence -- 28 Hz nominal, sagging under load (emergent hardware, not a code constant). * Mover velocity is u/s (dead-reckoner @0x421f7c: pos += vel x ticksD/28), so the v operand is unit-faithful too. The 0.5 "loaded-rate" witness bracket is RETIRED -- it modeled the sag as a constant, wrong in exactly the reported scenario (open-field cruising is a light scene; the pod held ~28 there). kSeekHeatCalib = 1.0. Measured at the faithful rate (madcat cruise benches): recommended gear equilibrates ~1450 -- inside the authored degradation band as a SOFT 20% output governor (heatFactor = 1 - d^2/range^2), weapons untouched at ~110-150, ZERO jams, zero bay fires from movement alone; supercharge ~1770 (deep governor -- the manual's own expert-mode warning). That is the authored system: degradeT=1000 is governor onset, failT=2000 the cliff, and the coolant-loop controls are the player's lever. New leads filed in open-questions: the climb term's gravity operand samples 0 (bridge points at a dead cell); the engine Mover's own gravity subtraction is per-frame dt-less (port impact unread); gait-noise phantom acceleration feeds the accel term. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d3de5f94ee |
#87/#96: 'mechs die with lightly damaged armor' AUDITED -- no port bug; it's ammo cook-off
Byte-level audit of the entire damage chain vs the binary, all links exact: zone stream reads (@0041df5c == engine DAMAGE.cpp), ctor normalization (@0049ce50; constants 1.0/1e-4/0.5; shipped cells uniform 1/armorPts), TakeDamage @0041e4e0, the BT override @0049c690 (vital/leg 1.0/0.5/0.0), CriticalHit @0049ccc4 (0.5 fraction, 1.0 cap, authored-percentage cap), cook-off chain (@004bc3fc per-salvo stamp / @004bd300 rounds x amount / @004ac274 divide across the bin's plugged zones). Measured kill route: 6 of 7 bench deaths were bay-fire detonations -- LRM15 bin 10 rounds x 50 = 500 into its ONE authored carrier zone (content: madcat bins single-zone; 15 of 22 zones vital) -> one application 0->1.0 -> instant death, armor bright everywhere else. Field logs confirm dominance (Rajel 12 bay-fire events, ConnMan 10). The mech that died conventionally ended 0.97/0.93/0.92/0.85 charred -- darkening accumulates fine when mechs live long enough. Levers: the #96 heat economy (arms the bay fires) and purge training (EJECT hold extinguishes -- wired, logged, manual-trained in the pod). Also corrected a wrong KB address: FUN_0041e4e0 was labeled '.DZM res compiler' in decomp-reference.md/rendering.md -- it is DamageZone:: TakeDamage (byte-matches DAMAGE.cpp:379); swept both files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
152249cb76 |
#95: the SCOREBOARD banked one missile per salvo -- credit the DELIVERED amount
Two consumers of one impact, only one was ever verified: the victim gets
TakeDamage{amount=per-missile, burstCount=cluster roll} and applies it
burstCount times (armor was always right); the score post on the next line
sent the bare per-missile amount. An LRM10 salvo dealing 7-35 armor banked
3.5 points -- Rajel's "~3 points to score", to the digit.
Ground truth: the binary's score is the victim handler's tally (amount once
per applied burst @0x4a04da + crit bonuses) reported to the INFLICTING
player (the id-0x16 tail, deferred #45). The shooter-side stand-in now
posts amount x burstCount -- the identical figure handed to the victim, at
the identical one-post-per-TakeDamage granularity. Same pass:
* splash never credited score at all -- the binary tallies every
TakeDamage; now posted per splash victim (amount x falloff bursts);
* the bridge credited the LOCAL player for ANY registered hit -- AI-master
fire on the player, a dying mech's death-blast splash; now refused
unless the shooter IS the local vehicle (MP unaffected: only local fire
carries live damage on a node);
* direct-fire unchanged -- beams author burstCount=1 (emitter.cpp:355).
Verified per the harness doctrine: single-node field composition (madcat,
real fire, real enemy) 22/22 impact credits paired at damage x burst, zero
bare 3.33 posts; two-node replicant-victim run BOTH directions 31/31
paired across three missile authorings (3.33/2.0/5.0 per-missile) + 25-pt
ballistics, leftovers all burst-1 beam amounts. test-harness.md gains the
map=grass note (MP.EGG authors cavern/night; GOTO mechs shoot rock).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
08ca66e5b5 |
weapon SPEC AUDIT: the fleet's authored numbers in one pass -- missile delivery fully reconciled
"do we need to audit every possible weapon independently?" -- one dump answers
all of them. [wspec]/[wspec-msl] (BT_SPEC_LOG) print every weapon's AUTHORED
damage/recycle/range/heat + missile count and post-divide per-missile at build.
THE KEY DISCOVERY: the catalog's FULL-NAME loadouts (madcat, thor, avatar,
sunder, vulture, loki, blkhawk, owens) author DIFFERENT weapons than the
4-char variants (mad1, thr1, ...) -- and the named ones are what players fly.
Every night-9 field number reconciles exactly:
madcat/thor LRM15 = 50 total -> 3.33/missile (field: 3.33, bursts 3..15)
avatar/sunder LRM10 = 35 -> 3.50 (field: 3.50, bursts 2..10)
vulture LRM20 = 65 -> 3.25 (field: 3.25, bursts 5..20)
loki SRM4 = 35 -> 8.75 (field: 8.75, bursts 1..4)
Missile delivery is FAITHFUL across the real fleet: per-missile = authored /
count (divide confirmed live post-ctor), delivered total = per-missile x the
binary's Random(n)+n/4 roll = 25..100% of listed, zone-scattered.
The "80+ point hits" recollection fits the data as an ALPHA: madcat full alpha
= 50 + 25 (AFC100) + 6 + 2 = 83. A single 4.10 salvo cannot exceed 65
(vulture LRM20). The "3-5 points" reports read our [projectile] IMPACT
per-missile diagnostic as a total.
Also: bac1 is not a valid vehicle (unhandled AV at btl4+0xac4cd on spawn --
noted for a guard someday); wspec-msl now prints AFTER the ctor divide.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
47d9a61402 |
hit-location cylinder: raw-bytes audit of the shipped tables + chassis->table map + wedge orientation
Audit of Cyd's hit-model artifact against THE BINARY (user mandate: not vs the port). res29_scan.py parses the type-29 streams straight from BTL4.RES bytes: 18 streams / 8 distinct confirmed; the 7-wedge cells (BLH band 6, VUL band 4) and the all-fixed BLH/OWN twist patterns are authored fact; every artifact spot-check cell matched the raw bytes verbatim. Chassis->table resolved by live-dump matching (MadCat and Avatar ride DIFFERENT same-zone-set tables). Authored slice names fix the wedge orientation: FRONT = the +Z arc, W1|W2 seam = dead ahead -- corrects the #92 comment's W6-frontal claim. Collision type-0 divert re-grounded at 0x4a0361/0x4a036d: rams never touch the cylinder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
07072fc9f0 |
hit-location GROUND TRUTH: all 8 chassis' authored cylinder tables, named + fingerprinted
For verifying Cyd's hit-location artifact (the platform serves it to this
session as an unreadable public artifact, so the diff base is published here
instead). scratchpad/night9/hitmodel_groundtruth.txt: every chassis's 7
layers x per-layer slices with zone NAMES and percentages, from the live
BT_DMGTABLE_LOG dump joined against [zone-armor] names.
Structure fingerprints (rot = rotateWithTorso per layer 0..6):
ava/lok/mad/snd/thr/vul rot=0001111 (legs hull-fixed, torso bands twist)
bhk/own rot=0000000 (chicken-walkers: NOTHING twists)
slices: 8 everywhere EXCEPT bhk layer 6 = 7 and vul layer 4 = 7
Variants share tables: lok2==lok1, mad2==mad1 -- '8 distinct chassis' is exact.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
9b1353bfbf |
ONE Explosion per salvo -- the port detonated every visual round, amplifying explosion sound + smoke + audio demand by the missile count
The user challenged the 7% clock story ("i think its not just a 7% discrepancy.
did you base your analysis in the decomp") -- correctly. The stop-path trace
it forced found the real amplifier, and it is byte-grounded:
THE ARCADE (byte-verified in the #95 work): FireWeapon @004bcc60 spawns ONE
Missile entity per trigger -- no loop, missileCount never read at fire. One
Missile -> one detonation (@004be078) -> ONE Explosion entity: one 4-voice
sound + one authored smoke package per salvo. The cluster's N-fold nature is
delivered as DAMAGE (burstCount), not as N explosions.
THE PORT: the N-round visual ripple (a deliberate [T3] port visual; its rounds
are damage-0 tracers) called BTSpawnRoundDetonation at EVERY round's contact --
N full Explosion entities per salvo, N x BigExpInt x 4 voices, N x the
explosion PFX, N=2..20. Measured in a 100s solo missile bench: 559 explosion
sound setups; BigExpInt 208. On observer nodes the stale-point mirror bug
(fixed in
|
||
|
|
6dbeb3d266 |
wreck smoke dies WITH the wreck; the audio/effect clock is calibrated to the byte-proven 28
USER REPORT: "when you destroy a mech, it continues to smoke for a very long
time after the mech disappears... up to 30secs to a minute."
MEASURED (new [pfx] timed census with per-def particle attribution): the
death plume (psfx 1 DDTHSMK) was re-armed on a 10s cadence as a WORLD effect
(ownerTag=0) -- so neither the burial transition nor the respawn cleanup
(StopAllEntityEffects) could touch it. The last-armed window kept emitting
over the empty spot for up to 10s after the hulk vanished, plus 6+-2s authored
particle lives, and back-to-back windows stretched the visible tail to the
reported range. The re-arm itself is a marked PORT ADDITION [T3]; the binary
fired DDTHSMK once -- its 10s window + particle lives ended right around the
~17s hulk sink. The authored design: the smoke dies WITH the burial.
FIX: the plume is spawned ATTACHED (BTStartPfxAttached, tagged to the dying
entity -- it also rides the sinking hulk now), and the burial transition calls
BTStopEntityPfx alongside the respawn cleanup that already did. Verified over
a 175s run containing FOUR wreck events (the enemy kill + three player bay-
fire deaths): every plume's emission ends at its wreck's burial or respawn,
particles fade within ~8-11s, no orphan emitters remain.
THE COUPLING (user relay of Oracle: "prolonged smoke and explosion INCLUDING
SOUND... same lever?") -- CONFIRMED as a family: the binary's audio layer
times in FRAME COUNTS converted by the engine frame-rate global:
AudioTime::Seconds_To_Frames == fmul [0x52140c]=28.0; fadd 0.5; round
(@0x42c611, @0x42dd86 -- the 0x42xxxx cluster of the ~90 global consumers)
Our engine's DefaultRendererRate said 30: every audio duration, sequence
delay and compression window ran ~7% fast. Calibrated to 28 [T1]. NOT yet
explained by this: a truly SUSTAINED/looping explosion sound (7% is not
"prolonged") -- the effect stop-path trace stays open on #114/#51.
Tooling: BTPfxParticle carries defIndex; [pfx] census (BT_PFX_LOG, 2s) prints
emitters + particles PER EFFECT SLOT with a timestamp; wrecksmoke.sh.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
2df1369168 |
#116 THE REAL STEAM CAUSE: experience never rode the lobby -- joiners' pages were authored by the HOST as the "veteran" fallback
The user's question broke my first story: "did lynx host tho?" He did (Oracle joined LYNX's lobby that night) -- but more importantly the affected players' logs read mode=steam, which gets the FULL menu, so the FeJoinOnly fix in |
||
|
|
3109cfc49a |
#116: steam JOINERS were silently locked to VETERAN -- restore their experience selector
Oracle isolated it: "standard mode still having heat and leaks only occurred in
steam not in solo. Sauron and Lynx did not have the issue but Conn Man, Rajel
and myself did." The night-9 logs close the case:
affected (Oracle/ConnMan/Rajel): [exp] experience=2 heatModelOn=1
clean (Sauron's std sessions): [exp] experience=1 heatModelOn=0
CAUSE: the glass front end's JOIN layout trimmed the menu to "the mech list +
the JOIN button; everything else is the operator's call" -- and the experience
selector's hidden default is 2 (veteran). So HOSTS got whatever they picked
(Sauron hosted the lobbies all night -> his standard landed), while every
JOINER launched as veteran regardless of anything they did -- which is why
Oracle's cycling-the-settings experiment changed nothing, and why solo (full
menu) never showed the problem. heat >= veteran, hence "standard but heating".
DESIGN CONFIRMED BY THE USER before fixing: experience is PER-PLAYER by the
original design -- the sysop set each user's tier and mixed-experience matches
were legal. Architecture already supports it end to end: each node's master
player reads its OWN egg's experience (btMission+0xE4 -> BTPlayer @004c0bc8),
so per-node choice IS per-player choice.
FIX: the join layout gains the GroupExperience selector (the selection->egg
write path is the pre-existing host path, field-proven). Also: the front end
now prints "[fe] pilot experience=<x> (join|host/solo)" at launch, so every
future field log answers this class of report without asking anyone.
VERIFICATION BOUNDARY, stated plainly: builds both configs; the write path is
shared with the host flow which the field already exercises; the visible
selector + the [fe] line need one join-mode launch to eyeball, and the field
re-test is Oracle running a standard steam match as a JOINER and seeing no
heat.
Tooling note recorded in test-harness.md: bash-heredoc python collapses one
backslash level even single-quoted -- a "\n" arrives as a real newline and
replaces silently no-op. Build backslashes from bytes([92]); verify replaces
by length delta, not by the script saying "fixed". (This burned four edit
rounds tonight and several earlier C2001 hunts.)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
1bcee1f9eb |
#96: the effective-rate theory is corroborated -- upgrade the calibration provenance T3 -> T2
Frame-rate testimony from the morning follow-up, now on record beside the bytes:
* Oracle, asked if the pod frame rate was rock solid: "I think I could do as
well with a flipbook"; heavy fights "would turn to a slide show".
* Lynx (original era): "RP had a target frame rate of 30FPS. It wasn't hard
coded or locked. BT never had a locked frame rate in 1st release
testing... coming from 3.0, where 20FPS was a miracle."
* The engine pacer's own design agrees: fixed budget + background fill + NO
overrun catch-up (APPMGR.cpp, T0) = "target 28 but allowed to slip", with
the 18.2065 BIOS fallback anticipating degraded timing.
Also settles epilectrik's open confirmation ("frame rate is advancing at least
some of the subsystems -- i have to fully confirm that"): ALL of them --
UpdateManager::Execute runs every interesting master every frame,
Entity::PerformAndWatch runs every executable subsystem per entity tick, no
throttle at any layer. The crisp rule: CADENCE is frame-linked for
everything; only terms missing a dt multiply are rate-SENSITIVE.
Corollary worth keeping: a choking 486 generated LESS heat/s exactly during
the biggest fights -- the slideshow was mercy. Veteran memory of seek-4
endurance encodes the LOADED rate, which is why bracketing by their consensus
is the correct instrument rather than a concession.
Follow-up filed in the KB: sweep the reconstruction for OTHER dt-less per-tick
accumulators (known: particle trail density, rendering.md; the 15-sample
heatFilter window is rate-dependent smoothing, benign). Each found one should
normalize against the same 28-nominal/bracketed-effective pair.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ac94ce6a31 |
#96: DISSIPATION AUDIT -- byte-faithful end to end; the cooling-too-weak hypothesis is DEAD
"no guesses. we want to decompile and find the right answer" -- done. Every
function in the heat-removal chain read from the decomp and diffed against our
reconstruction:
HeatSinkSimulation @004ad924 E += pending; T = E/mass; alarms EXACT
UpdateHeatLoad @004ad7f0 radiated = T x coolant; clamp(0.002x) EXACT
ConductHeat @004ad8ac exchange into linked + rebalance EXACT
ComputeHeatFlow @004ad9ec (T1*mScale - (E1+E2+pend2)/den) * tau
* (1 - e^(-dt*k*(cool/cap)*flow/den)) EXACT
BalanceCoolant @004ada94 radiated-equalising move, 3 clamps EXACT
Condenser sim @004ae4d8 mScale = clamp>=1((1-zoneDmg)*refrig) EXACT
Reservoir sim @004aef78 inject-only; thermally passive EXACT
Every term is properly dt-scaled -- the dissipation side is rate-INDEPENDENT.
The only rate-dependent energy term in the whole thermal system remains the
myomer kinetic generation, i.e. the calibration knob is pointed at the right
factor and nothing on the cooling side can absorb the blame.
LIVE chain verification (new [heatflow] census, incl. a RADIATOR tag on the
bank's own Performance, which the base-sim census could not see):
Myomers 490 -> Condenser5 420 -> bank 337 -> ambient (relaxing onto the
300K setpoint). The exit exists, runs, and holds the bank just above 300.
AND ONE REAL FINDING -- my night-8 "no per-chassis cooling variation" claim was
WRONG, and Oracle's stated mechanism is vindicated to the digit:
bank k = 0.1 x HeatSinkCount x 231000: Owens count=5 -> k=115,500
Thor count=13 -> k=300,300
The Thor sheds 2.6x the Owens' heat through the mech's ONLY ambient exit --
"a thor could run seek 4 for extended periods because of the number of
heatsinks" is exactly the authored model. My hsparm chassis comparison had
printed RESOURCE constants BEFORE the ctor's count scaling; the dump now
carries a warning to stop the next person misreading it the same way.
(The KB was never wrong -- open-questions.md:593 documented the radiator
scaling when task #9 landed it. The wrong claim lived in my #96 comment.)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
c28555bdc9 |
#96: TRACE THE CLOCK ORIGIN -- the pod ran at 28 fps (byte-proven); myomer heat re-bracketed to 14 Hz effective
epilectrik: "the issue seems to be related to a tick rate that advances the
heat accumulation... i need to trace out what the clock origin was." Traced.
THE CLOCK ORIGIN [T1]: the DOS binary keeps its engine frame rate in the
global DAT_0052140c, set once at startup:
0x401ace: mov [0x52140c], 0x41E00000 = 28.0f (nominal)
0x401ada: mov [0x52140c], 0x4191A6E0 = 18.2065f (BIOS-tick fallback)
The DOS main pushes it straight into the ApplicationManager ctor (0x401189:
push [0x52140c]), and ~90 sites across the image fmul/fdiv by it -- it is THE
per-frame<->per-second conversion scalar of the whole 1995 engine. The engine
executes every interesting master every frame and every executable subsystem
every entity tick (UPDATE.cpp / ENTITY.cpp, T0 -- no throttle at any layer),
so subsystem Performance cadence == this rate: the pod's myomer tick was
NOMINALLY 28 Hz. My previous 30 Hz reference (the i860 BOARD frame) was
wrong by 7%, not the 2x the veterans hear.
THE RESIDUAL IS EFFECTIVE RATE, NOT NOMINAL: Oracle, on the halved build,
still asks "If you could please halve the myomer heat rate again. We will
bracket to something reasonable." A 486 host missing beats halves the real
Performance cadence without changing any constant -- and the 18.2 fallback
existing at all says slow paths were expected. Statics cannot settle the
in-pod effective rate, so his bracket is the right instrument:
effective default = 28 [T1 nominal] x 0.5 [T3 testimony calib] = 14 Hz
BT_MYO_HZ=<hz> still overrides absolutely for bracketing
the resolved rate now logs under BT_MYO_LOG, tier tags inline
Measured (Owens, seek 4, flat out): peak T 1007 -> ~640, degradation never
reached -- the halving Oracle requested, delivered as one auditable knob
instead of a silent constant edit.
KB: decomp-reference.md gains the DAT_0052140c section (~90 consumer sites --
whenever the decomp shows an unexplained mul/div by _DAT_0052140c it is
per-second<->per-frame at 28); btl4main.cpp's "30 = pod authentic" pacer note
corrected (30 kept deliberately for display smoothness, the divergence now
documented). Candidate follow-up recorded: the frame-rate-dependent particle
trail density (rendering.md) should normalize against the same 28, which may
bear on Ronin's smoke-density report (#114).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
c791fae0f8 |
KB: new topic test-harness.md -- the bench machinery + the verification doctrine
User mandate (2026-08-02): "do tests like this from now on" -- after the #110 grind bench, where the field composition (peer fire destroying an arm over the wire) replaced the constructed proxy that had let #86 be called fixed while players kept hitting it. The topic carries two halves on purpose: DOCTRINE -- what counts as VERIFIED: 1. reproduce the REPORTED scenario, not a convenient adjacent one 2. scalpel hooks (BT_SELF_DAMAGE_ZONE / BT_KILL_SUBSYS / BT_FORCE_*) locate defects; they support a "fixed" claim only with proven path-identity to the field composition -- and the field composition still gets run 3. MP symptom -> two-node proof (master-side correctness says nothing about what a peer sees) 4. visual symptom -> pixel proof (gotcha 23) 5. coverage claims need the axis enumerated and measured (all gates, all chassis), because per-chassis behaviour lives in authored data 6. an unexplained extra effect in a passing run means the run has not passed MACHINERY -- the bench_common.sh contract (summarized, file = source of truth), single-node and two-node skeletons (relay, ports, affinity, fire cadence, GOTO_STOP standoff), process hygiene (stale-node taskkill first, never double-background, teardown kill order, stale-exe tells), and log-reading rules (capped diagnostics are not evidence of absence; alarm lines are not trends; name the actor at every refusal; field logs have no gates set -- spawn-time summaries ungated, per-frame traces gated). Routed: Quick Lookup row, CLAUDE.md reasoning step 4, build-and-run parity section, reconstruction-method Key Relationships. checkctx CLEAN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5d6ded73f0 |
#110: cross-pod verified BOTH ways -- the cascade replicates, and peer fire alone drives it
Two two-node benches close the last stated gap ("no two-node run has watched a
peer's view of an arm death"), the second at the user's insistence on the REAL
field composition after the first used the self-damage scalpel:
1. mp_armchain.sh (scalpel): B self-destroys dz_rarm mid-fight. A's screen
receives the whole cascade -- [zone-repl] dz_rarm lvl 0->1 AND the cascaded
dz_rgun lvl 0->1 -- plus the respawn reset coming back clean (1->0). B's
post-cascade AFC pushes turned out to be the RESPAWN RE-ARM, correct
behavior, not a gate failure. (Also confirmed: the self-damage harness
dispatches a real Entity::TakeDamageMessage -- the identical path a peer's
round takes, so the scalpel and the field share every line from the handler
down.)
2. mp_armgrind.sh (the field composition): stand-off at 180, NO self-damage
anywhere, A grinding B with real cross-pod fire for six minutes. On B:
* 25 incremental arm-zone hits from A's weapons via the cylinder tables
(dz_rgun reached 1.0 from pure accumulation -- 15 cascades on rehits)
* dz_rarm finished by B's OWN AFC bay cooking off (AmmoBinAFC100 BAY FIRE
350, half into dz_rarm) -- itself lit by A's fire crits: peer-originated
end to end
* [cascade] zone 9 -> AFC100 + AmmoBinAFC100 + Condenser6 force-failed,
fire gate refusals logged
On A: [zone-repl] dz_rarm 0->1 and dz_rgun 0->1 -- the shooter SEES the arm
and pod die. B symmetrically received A's torso deaths.
Also: DamageZone::ReadUpdateRecord gets a change-only [zone-repl] diagnostic
(BT_MP_LOG) -- the probe that made the peer side measurable; mp_armgrind.sh
opens with the stale-node taskkill the other benches already had (a leftover
node from an aborted launch joined a later lobby as a third instance).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
614849b4a6 |
#110 coverage: all 8 chassis + all 3 weapon-class gates verified through ZONE death
Answering "are you 100% positive this works for every weapon type" -- it was not a yes when asked, and the gaps were closed with measurement rather than assurance: 1. AUTHORED DATA, all eight chassis (zonesweep.sh; the [zone-armor] dump now carries the zone NAME): every arm zone AND every missile-rack zone on every chassis authors descend=1 (ava/bhk/lok/mad/own/snd/thr/vul); every gun/rack zone lists 1-4 critical subsystems; doors are inert (0/0/0); bhk uniquely adds destroySibs=1 on its arm chain. No chassis is missing the cascade authoring. 2. WEAPON-CLASS GATES, end to end through a ZONE death (not direct weapon destruction, which was #86's verification gap): ballistic AFC100 (projweap gate1) -- dz_rarm, ava1 [prior commit] energy PPC (emitter gate) -- dz_larm, ava1 [prior commit] missile NRK5_2 (projweap gate1) -- dz_rmissle, own1 [this pass] [cascade] zone 12 -> NRK5_2 + AmmoBinNRK5_2 force-failed [ammo] NRK5_2 -> NoAmmo (gate1): destroyed=1 All three fire-gate code paths are now exercised by the cascade. 3. One anomaly chased to ground rather than waved off: the Owens rack cascade also killed Avionics/HUD/Searchlight/ThermalSight -- zone 15 dz_searchlight, an authored CHILD SEGMENT of the missile rack (the sensor mast sits on the shoulder pod). Authored skeleton + authored descend flag, walked faithfully. Remaining surface NOT bench-verified, stated plainly: cross-pod -- the cascade is master-authoritative (crit plugs bind on masters only) and the resulting alarm states ride the existing weapon update records, but no two-node run has watched a peer's view of an arm death. The field re-test (Lynx/Conn Man's own audit, in MP) covers exactly that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
06adaee523 |
#110: the zone destruction cascade is LIVE -- an arm now takes its gun pod with it
Lynx: "You can destroy an upper arm, and the gun pod is intact and can still fire." Conn Man's audit: three chassis, both arms, reproducible. And the user is right that this family was claimed fixed before: #86 gated fire on the WEAPON's own destruction and was verified by destroying the weapon directly (BT_KILL_SUBSYS) -- the field scenario was the ARM ZONE dying with the weapon subsystem healthy, which that bench never reproduced. Oracle's 693-night "I can still fire destroyed energy weapons" was almost certainly this mechanism. ROOT CAUSE -- a silent stub. Mech__DamageZone::RecurseSegmentTable @0049cad4 was transcribed faithfully... against three local shim types whose iterators unconditionally return NULL: struct SegmentIterator { DZRef *Next() { return 0; } }; struct SegTableX { SegmentRecord *operator[](int) { return 0; } }; The cascade fired on zone death (bench: 72 [cascade] lines), "descended", and touched nothing. Every line read correct; none of it did anything. THE REAL WALK (decomp re-read, FUN_0049cad4): iterate the mech's segment table (mech+0x300, vtbl+0x34 GetNth) to this zone's segment, then destroySiblingsOnDestruction (Wword 0x68): recurse every other zone on the SAME segment (seg+0xD0), setting graphic state 2 (Gone) descendOnDestruction (Wword 0x67): recurse every zone on every CHILD segment (seg+0xE8 -> child+0xD0) -- the arm takes the gun The engine ALREADY HAS both structures: EntitySegment::damageZoneTable @0xD0 / childIndexTable @0xE8 (TableOf is 0x18 -- byte-identical to the decomp offsets), populated at stream time by JMOVER.cpp:373 [T0]. The stub was never necessary. Implemented with the engine iterators via two new read accessors on EntitySegment (SEGMENT.h); the binary's asymmetry is reproduced as-is (the sibling loop checks graphic state != 1, the child loop does not; re-hits on a 1.0 zone re-run the cascade -- idempotent in effect). VERIFIED (ava1, self-damage to one zone, autofire everything): dz_rarm -> [cascade] zone 9 -> zone 17; AFC100 + AmmoBinAFC100 + Condenser6 ForceCriticalFailure'd; "[ammo] AFC100 -> NoAmmo (gate1): destroyed=1"; torso LRM5/SRM2 keep firing (correct) dz_larm -> [cascade] zone 2 -> zone 6; PPC + Condenser4 force-failed; "[emitter] 'PPC' fire REFUSED (destroyed=1)" x4998 -- the emitter gate now logs refusals BY NAME (the FIRED line never named its weapon, which is how #86's verification gap survived) regression smoke: frame time 7.13/7.20ms, 0 asserts, 0 cascades in ordinary combat (they fire only on genuine zone deaths) Authored data (now dumped under BT_DMG_LOG): descend=1 on exactly the four arm zones (ava1: 2/6/9/17), destroySibs=0 everywhere -- so legs/torso behavior is untouched by this change. KB: combat-damage.md cascade section; reconstruction-gotchas #26 (the silent-stub trap: a shim that returns EMPTY converts a reconstruction into fiction that reads correct -- shims must Fail() loudly or log their emptiness). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
da70bd58b2 |
#84: the replicant salvo now HOMES AT THE LIVE TARGET -- the stale-point double detonation is gone
Oracle (674): "missile appear to register hit explosions twice, once where
target was and again where the target is". Rajel (693): "once where the mech
was when I fired and on the expected impact".
MECHANISM, measured on two nodes (BT_PROJ_LOG "DET at" now logs EVERY contact
detonation, including damage-0 rounds, which were invisible before): the
replicant salvo mirror pushed rounds with NO entity handle at the FROZEN
fire-time aim point, and the pool's contact test is proximity to p.targetPos --
so mirror rounds "contacted" the empty air where the target USED to be and
detonated there, on every peer, every salvo (45 salvos -> 234 frozen-point
air bursts in a 200s bench). The true impact appears at the live position
(the shooter's own homing rounds on his node; the victim's damage reaction on
peers) -- two sites per salvo, exactly as reported.
THE BINARY'S MODEL (CLASSMAP, Missile flight-entity cluster): missiles are
ENTITIES with authoritative/GHOST Performance variants (Projectile
PTR_LAB_005129e8 / 005129f4) and their own WriteUpdateRecord (@4bef4c, slot 7)
-- peers ran ghost missiles updated from the wire and saw the TRUE trajectory.
One explosion, correct place, every node. The frozen-point mirror is the port
infidelity (the entity Missile itself stays blocked by the documented 2007
Entity-base mismatch; mislanch.cpp:301).
FIX: the MissileLauncher update record now carries the locked target's EntityID
(salvoTargetID; EntityID::Null = point fire). The mirror resolves it on the
receiving node via HostManager::GetEntityPointer (engine T0, an index-socket
Find -- NULL-safe on any ID), refuses non-mechs via BTIsRegisteredMech, and
pushes the mirror rounds WITH the handle -- they re-lead on the live local
replicant exactly as the master's own rounds do, and detonate at the true
position. Guards: record-length gate (a short record from an older build has
no ID field -- its tail would be garbage and must not resolve) + Null check +
registered-mech check.
VERIFIED (two nodes, 200s, missile autofire both ways):
* every record arrives len=52/52 with sane aims ([mlrec] diag)
* 22/46 salvos resolve (tgtID=3:22 -> the victim's local master) and their
mirror rounds home: the frozen-air DET population fell 234 -> 102, the
live-homing population rose to 155/207 per node
* the unresolved remainder is BENIGN and correct: EntityID::Null point fire
plus hostID=-1 LOCAL entities -- i.e. the victim's WRECK, which autofire
keeps shooting after the kill; a wreck does not move, so the frozen aim IS
its true position
Downstream expectations: Ronin's smoke-screen (#114) should drop by ~the mirror
half, and the audio census (#32) loses the duplicate Static3DPatchSource
explosion sounds on peers -- both to be read from the next field logs.
Also in this commit: [projectile] DET diagnostic (every contact detonation with
position/damage/handle/aim), [mlrec] record-arrival diagnostic (len/recID/
subsys/counter/aim, capped 200), scratchpad/night9/mp_double.sh.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
3354db4bd1 |
#32: RETRACT the retention diagnosis; count what matters (steals + true drops); name the saturating class
The reopen said "the pool fills and never returns a source". The 30s census in
the SAME field logs disproves it: free returns to ~227-230 between bursts and
reuses climbs ~20/s all session. free=0 on the failure line is true by
DEFINITION at the instant of a failed acquire -- the third instance of the
counter-sampling trap (live=256-vs-6, then hsparm greps, now this), read off the
alarm line instead of the trend line.
WHAT THE LOGS ACTUALLY SHOW
* The pool cycles; release-on-stop exists and works (the engine steal loop).
* During firefights CONCURRENT demand exceeds 240 and the priority steal loop
services each new sound by killing an old one -- continuously through
combat (failures spread across every decile of every combat session).
* Idle standing demand is ~13 sources. My "each component holds its SourceSet
to entity teardown, ~20-25 per mech" narrative was wrong.
* The raw ACQUIRE FAILED line count (6.8k-19k per log) is NOISE: the steal
loop retries after every failed attempt, so lines accumulate per EVENT and
most events still play via a steal. True drops were never counted.
CHANGES
1. Census now carries steals= and drops= (drops = the steal loop ran dry and
the sound NEVER played) plus a per-class drop histogram
("[audio] dropped by class: {class 1005 x4v: N} ...") -- all ungated, so
the next field logs are decisive instead of suggestive.
2. The ACQUIRE FAILED print is rate-limited to 1/30s and now names the
requesting class + voice count. 19k-line log spam distorted this triage.
3. BT_AUDIO_SOURCES=<n> now raises the POOL cap too (it previously raised the
AL context budget while the pool stayed at 240, making the field
experiment impossible to run).
MEASURED (9 mechs, missile autofire, 150s)
* cap 240: peak 130 sources, 0 fails -- demand tracks SHOOTER count, not mech
count; one shooter cannot saturate. A 6-shooter lobby pins 240.
* cap 48 (BT_AUDIO_SOURCES=64): saturation reproduced -- census
steals=223 drops=455, histogram names the classes.
* Dominant field requester (requested=4) = class 1005 Static3DPatchSource:
world-placed effect sounds, i.e. EXPLOSIONS. 1001 DirectPatchSource x1v
dominates drops at low cap; 1002 Dynamic3DPatchSource x3v present.
CONSEQUENCE FOR THE FIX ORDER: #84's stale-aim double detonation duplicates
exactly the saturating class on observer nodes. Fix #84 FIRST, then re-read the
field census; only if it still saturates does the budget experiment
(BT_AUDIO_SOURCES with frame time measured) become the play.
KB: the wrong night-9 entry in open-questions.md replaced with the corrected
diagnosis; gotcha candidate noted -- an alarm-line counter is not a trend.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
202ebbf84a |
KB + tracker: night-9 playtest (build 4.11.693)
CONFIRMED FIXED and closed: #95 (missile salvo damage), #97 (leak gauge 3 levels), #98 (loop-6 lamp), #99 (leak alarm audible). #95 deserves a note: a tester reported "LRMs still doing 3.3 damage", but that is the PER-MISSILE figure. 382 impacts across three 693 logs show the cluster being delivered correctly -- LRM20 3.25 x burst 5..20 (total up to 65), LRM15 3.33 x 3..15, LRM10 3.50 x 2..10 -- and those burst ranges match the binary's Random(n)+n/4 roll exactly. #32 REOPENED. The pooling change removed the alloc/free churn (the ~10% frame time is real) but every 693 field log still shows ACQUIRE FAILED (requested=4 live=240 pooled=240 free=0 ...) 19,182 fails (Sauron) / 15,333 (Rajel) / 6,818 (Ronin), high-water 225 of 240, starting ~1% into every session. `free=0` is the diagnosis: sources are never returned. This is precisely the risk flagged when the fix landed -- retention, not pool size. Players called audio "fine" the same night; the log disagrees. #84 still present, but the surviving explosion is a DIFFERENT mechanism from the bundled one that was fixed: "where the mech was when I fired" is the stale fire-time aim point already recorded as an open in rendering.md, i.e. an MP visual, not the message-manager path. Needs a two-node bench logging the OBSERVER -- the earlier two-node run verified damage delivery but never the visual. New: #108 ghost mech (peer never sees the death; shots still register, so it is a visual/replication desync, the inverse of #94), #109 panic/eject, #110 arm parent/child linkage (gun pod survives its mount -- three chassis, both arms; parentArtifactZone exists but only drives LOD), #111 death warp bubble shown to peers, #112 AFC shotgun spread (may be authentic Ultra-AFC burst), #113 LRM burn duration, #114 missile smoke as a smoke screen, #115 hot box, #116 Standard-vs- Expert heat gating, #117 per-chassis cockpit bounce. ⚠ A MEMBER OF THE ORIGINAL TEAM IS NOW TESTING -- "Ronin" (log user torak). He worked on the 1995 game and has played the pods recently. Treat his pod comparisons as primary evidence above reconstruction inference. First two contributions recorded in the KB: per-chassis cockpit bounce was deliberate, and NARC likely shipped as data without an implementation. ⚠ Conn Man ran the OLD build (.674) for all seven of his sessions, so his reports are not evidence about 693. His arm-linkage audit is still valuable -- nothing in 693 touched that path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9623399ec4 |
gitignore: never commit player field logs (Steam IDs / machine names / usernames)
Field logs have been kept out by hand until now -- nothing enforced it, so a stray 'git add -A' would have published player Steam IDs and machine names. Ignore *.log/*.rar/*.zip under scratchpad/night*/ while leaving the bench scripts in those directories committable, which is the existing convention. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5a1a407ed1 |
pre-ship: resolve the new audio diagnostic gates ONCE instead of per frame
RunSequence and AudioIdleWatcher::Execute run per sequence per frame, so the BT_ATTRBIND_LOG probes added while chasing #99 were calling getenv ~2700x/sec on a 15-sequence mech. Small, but this is the same path the source-pooling fix (#32) just bought 10% of frame time in, and an unset diagnostic should cost nothing. Cached in function-static flags. Pre-ship smoke (combat, 2 kills, a respawn, clean teardown): frame time 7.18 / 8.27 / 8.71 ms -- matches the post-pooling 7.79ms baseline 0 asserts, 0 access violations, 0 audio acquireFails Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7c08e8bd03 |
#99: commit the missed audseq.h probe member (case-mismatched path in the previous commit)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5a425320b7 |
#92: map the direct-fire hit->zone path; the FOOT is pickable over only 3% of a mech's height
Oracle, night 7: "Unable to damage the foot panels on Loki via direct fire from
front or side. Was able to damage on Thor."
FIRST, THE MECHANISM -- it is not what the issue assumed (missing collision
geometry). A direct-fire hit gets its zone one of two ways, decided at
Mech::TakeDamageMessageHandler by `invalidDamageZone`:
AIMED (invalidDamageZone=0): the zone rides in on the message from the
per-part aim pick, MechSegmentPick (#73, btl4vid.cpp).
UNAIMED (invalidDamageZone=1): DamageLookupTable::ResolveHit runs the authored
cylinder lottery -- impact HEIGHT picks a layer, ANGLE picks a pie
slice, and a weighted random roll picks the zone from that slice.
Measured live, both paths are in use: the Thor logged 16 unaimed hits
(invalidZone=1 zone=-1 -> ResolveHit) alongside aimed ones carrying real zones.
THE FINDING. MechSegmentPick is a BOUNDING-SPHERE test whose primary key is
SMALLEST RADIUS WINS -- a larger sphere can never beat a smaller one the ray also
grazes. The leg spheres, dumped in world space and IDENTICAL on both chassis:
knee centre y=1.868 r=1.505 spans y 0.364 .. 3.373
toe centre y=0.302 r=1.689 spans y -1.387 .. 1.991
The toe sphere is BIGGER than the knee's and they overlap heavily, so the toe can
only win where the ray misses the knee sphere outright -- i.e. below y=0.364.
Against a reference height of 11.16 that is a 0.36-unit window, **3.3% of the
mech's height**, and it sits right on the ground. Everywhere else a shot at the
foot is credited to the LEG. That is the reported symptom.
This is a PORT ARTIFACT, not authentic: btl4vid.cpp's own comment concedes the
sphere test approximates "the per-part semantic the 1995 mesh intersection
produced". Real mesh intersection has no such interference -- aiming at the foot
mesh hits the foot.
NOT EXPLAINED, and stated plainly: the per-CHASSIS asymmetry. Loki and Thor have
identical leg spheres, identical foot geometry (LOK_LFOT.BGF and THR_LFOT.BGF are
both 3082 bytes with the same token layout), and identical foot layers in their
damage tables (only the upper/cockpit layers differ). So nothing found here says
the Loki should behave differently from the Thor. The bench could not settle it
because BT_AIM moves the drawn RETICLE, not the pick ray -- there is currently no
harness to aim the pick at a chosen height. That harness is the next step.
Diagnostics added:
[pickgeom] one-shot dump of every pick sphere in WORLD space (zone, r, centre)
[pickcand] which spheres a ray actually threaded, their perpendicular d, and
which won -- the probe that makes "smallest wins" visible
[dmgtable] the whole authored DamageLookupTable: layers, slices, zone weights
[dmgresolve] per hit: localY, heightRef, layer, theta, resolved zone
[cylgate] invalidDamageZone / table pointer / incoming zone at the gate
(the last three under BT_DMGTABLE_LOG, the first two under BT_PICK_LOG)
plus scratchpad/night8/footpick.sh.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
8daac37e40 |
#99: VERIFY IN THE IMAGE -- the shared-alarm Stop is authentic; do not diverge
Answering "did you check the decomp or disassembly?" -- I had not. I had checked
the authored data (BTL4.RES) and the MUNGA engine source and called it authentic
on source lineage alone. Now verified in BTL4OPT.EXE:
same sources compiled in : d:\tesla_bt\munga\AUDSEQ.CPP / AUDWTHR.CPP strings
object factory : @00466184 case 0x4b -> alloc 0x5C -> ctor @0043ccec
vtable : 0x4ead48
ReceiveControl : @0043d3a4 -- switch on control IDs 1 / 2 / 9 / 12,
which is exactly our enum Start / Stop / Idle / Tempo
Stop case : @0043d3ca -> unconditional call StopSequence @0043d2d4
StopSequence : tests only its OWN isRunning (+0x28) then the Chase
loop -- NO refcount, NO awareness of other leak sources
Structurally identical to engine/MUNGA/AUDSEQ.cpp. So the arcade behaved the same
way: with 19 subsystems sharing one alarm sequence, any one of them clearing its
leak silences the alarm while the others still leak. AUTHENTIC -- do not "fix".
(The tempo bound check could not be used as an anchor: Verify() compiles out of the
retail build, so the AUDSEQ.CPP assert strings are present in .data but unreferenced
by code and invisible to the decomp export -- gotcha 21 again. The object-factory
route via the numeric ClassID is what actually located the class.)
Recorded in context/gauges-hud.md with the full chain and the address table, so the
next person does not re-chase it or "fix" it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
e058b6acb7 |
#99: trace the coolant-leak alarm end to end -- it IS implemented; found the real defect
Oracle: "alarm is not sounding despite an active leak" ... "was sounding,
resolved a leak, but there was another".
RETRACTION FIRST. I previously told the user the coolant alarm was "genuinely
unbuilt -- there is no alarm implementation to starve". That was wrong, and it
was asserted without checking. The alarm is fully implemented and the entire
chain works. Traced live, in order:
Condenser6.ReportLeak (= HeatSink::coolantActive) changes 0 -> 1
-> [watchpoll] CHANGE on that exact address
-> [matchfire] val=1 -> ctl 1 (StartAudioControlID)
-> [seqstart] the alarm AudioControlSequence, events=25 looped=1
-> [seqsend] ctl 8/16 (select), 6/1 (volume), 1/0 (START), 2/0 (STOP), ...
a three-part looping alarm: two chirps then an 8s sustained tone.
The authored design, read from BTL4.RES: 19 subsystems per mech each bind TWO
AudioLogicalTriggers to their ReportLeak flag -- match 1 -> Start, match 0 ->
Stop -- and ALL of them drive ONE shared alarm sequence.
SO WHY WAS IT SILENT IN .674? Almost certainly the audio source pool, fixed
after that build in
|
||
|
|
e6103d02b2 |
#97: leak gauge reaches all THREE levels -- the normalisation was dropped with an x87 expression
Players: "you can get up to three traingles which drains really quickly" ...
"only seeing one level (lowest) right now" (Oracle), corroborated by Draco
("3 triple delta being the top level").
The display was never the problem: BitMapInverseWipe is already built with
frames=3 and stacks three segments at two levels each (0..6). The LEVEL
computation was wrong.
Our Execute rounded the raw leak rate:
level = round(value)
The binary's @004c5d08 does NOT. Ghidra renders the round as a bare
`FUN_004dcd94()` because it drops the x87 expression feeding __ftol -- exactly
KB gotcha 19, which we already had written down. The real prologue, disassembled:
fild dword ptr [ebp-0x14] ; ST0 = (float)fullWidth (frames*2 = 6)
fmul dword ptr [ebx+0xb4] ; ST0 *= value (CoolantMassLeakRate)
fdiv dword ptr [ebx+0xb0] ; ST0 /= third (full-scale divisor)
call 0x4dcd94 ; level = round(ST0)
i.e. level = round(fullWidth * leakRate / fullScale).
Without the normalisation the gauge rounded a value that never exceeds ~1.0
(coolantDraw = zoneDamage * heatLoad, and heatLoad is clamp(0.002*T, 0, 1) --
constants re-verified from the image, including the 80-bit extended 0.002). So
level could only ever be 0, or 1 via the >0.0025 floor: ONE triangle, always, no
matter how bad the leak. Three of the six condensers being mis-lamped (#98) hid
how systematic this was.
Also corrected on the way:
* `third` was declared int but the binary FDIVs it -- it is a float. The two
call sites passed *(int *)(subsystem+0x150), i.e. the BIT PATTERN of a float
read from a RAW OFFSET into our own layout (the databinding trap): at +0x150
our layout has filterDecay, not the binary's field. Now resolved through a
complete-type bridge, BTHeatSinkLeakFullScale.
* filterDecay was initialised 0.4f; @004b8fec writes 0.15f (param_1[0x54] =
0x3e19999a). It is written-once-never-read elsewhere in the port, so the
correction is behaviourally safe and it IS the gauge's full scale.
Verified live: Condenser6 at 10% zone damage -> draw 0.0358 -> level 1 (one
triangle); the same formula reaches all three at heavier damage, and the mapping
spans draw 0.0026 (half of one) to 0.15 (three full).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ee079083a3 |
#96: FIX -- normalise the myomer kinetic heat term to a reference frame rate
The previous commit only instrumented. This is the behavioural change.
The binary's drive-heat integrator (FUN_004b8d18) multiplies its climb and accel
terms by the time slice but NOT the kinetic one:
heat += ratio^2 * (1+dmg) * [ (1-accEff)*|vy|*m*g*dt
+ (1-velEff)*(0.5*m*|v|^2) <-- no dt
+ (1-accEff)*|v|*|a|*m*dt ]
A per-TICK energy is a constant on a fixed-frame 1995 machine and a variable on a
modern PC: heat/second scales with frame rate, so two players on different
hardware -- or one player in a heavy scene versus an empty one -- get different
heat from identical throttle. That is a defect independent of what the pod's
rate actually was.
The kinetic term is now multiplied by (dt * kPodFrameHz), which is exactly 1.0
when dt == 1/30 and makes the result machine-independent. This is the one
deliberate divergence from a verbatim transcription of @004b8d18, and it is
marked as such in the source.
Measured, Owens at seek 4 flat out:
before crossed degradeT(1000) at ~27s, peaked 1213, settled ~710
after peak 1007, settled ~525
i.e. roughly halved at our measured ~59Hz, which is the direction the players
asked for ("too much IMO", "def need to tone down the myomer heat some").
⚠ CALIBRATION IS NOT PROVEN. 30Hz is the i860 BOARD frame ([T1],
rendering.md:177); the HOST simulation rate is not separately established, and no
fixed-timestep constant exists in the image (searched 1/15..1/60 and 30/60 Hz --
the arcade computed dt from a clock, as we do). So the reference rate is the
best-documented value, not a proven one. BT_MYO_HZ overrides it with no rebuild
so it can be calibrated by feel.
Precedent for the bug class, already logged in the KB: "trail density is
frame-rate-dependent (2/frame @60fps = 2x pod density)" (rendering.md:222).
NOT settled: whether the per-chassis ordering matches player memory (a Thor
sustaining seek 4 where a light chicken-walker cannot). Post-fix benches put the
Thor HOTTER (crosses at 40s vs the Owens at 47s), but those runs are BT_GOTO
path-driven and the acceleration term is large and path-dependent, so they are
too noisy to conclude from. The cooling data being identical across chassis IS
solid (measured, previous commit).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
9dd7d48b41 |
#96: instrument the myomer heat model -- per-mech profile CONFIRMED, overheat cause LOCATED
Two player claims, both now answered from the decomp + measurement rather than
inference.
THE EQUATION (FUN_004b8d18, constants read from the image: _DAT_004b8ee4=0.5,
_DAT_004b8ee8=0.0 (the fabs), _DAT_004b8eec=1.0):
heat += ratio^2 * (1+damageLevel) *
[ (1-accEff)*|vy|*m*g*dt climb POWER
+ (1-velEff)*(0.5*m*|v|^2) kinetic ENERGY -- NO dt
+ (1-accEff)*|v|*|a|*m*dt ] accel POWER
Our implementation already reproduces this verbatim, dt-less term included.
CLAIM 1 -- "each mech has a unique heating profile". TRUE, and working, but NOT
by the mechanism the player described. Measured across thr1/own1/mad1/vul1:
* the myomer record is IDENTICAL on every chassis
(velEff 0.995, accEff 0.8, gears 3000/5000/7000/9999, rec 2,
degradeT 1000, failT 2000, thermalMass 250000)
* the heat-family COUNT is identical too -- 6 Condensers, 1 HeatSinkBank,
1 Reservoir, 4 Generators on all four
* every cooling parameter is byte-identical Thor vs Owens (condenser
conductance 315000 / mass 420000, bank 231000 / 1.39e6, reservoir
190000 / 3.42e6)
So there is NO authored per-chassis cooling variation. The profile emerges from
the equation instead: heat ~ m*v^2, and light mechs are faster. Measured at
seek 4, flat out:
thr1 mass 70000 |v| 11.34 kinetic/tick 49026
own1 mass 35000 |v| 17.22 kinetic/tick 57350
The Owens is HALF the mass and generates 17% MORE drive heat, because v^2 beats
m. That reproduces the player's OUTCOME (a Thor sustains seek 4, a light
chicken-walker cannot) via speed, not heatsink count.
CLAIM 2 -- "it runs too hot". The kinetic term carries NO dt: it adds an ENERGY
every TICK, so its contribution per SECOND scales with the tick rate. Measured
dt here is ~0.017 (~59Hz) and variable. The other two terms are power terms and
are rate-independent. On flat ground the climb term is additionally dead --
gravity reads 0 (the carried "environment gravity unwired" open), so hills do not
heat at all right now.
NOT yet established: the 1995 tick rate the dt-less term was calibrated against.
Until that is pinned the OVERHEAT FACTOR is unquantified -- flagged, not guessed.
Adds three diagnostics, all under BT_MYO_LOG:
[myoheat] now splits climb/kinetic/accel + dt + the kinetic share
[myoparm] one line per myomer: efficiencies, gears, thermal thresholds
[hsparm] one line per heat subsystem: conductance + thermal mass
and three benches (myoheat/myoparm/myocmp) that produced the tables above.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
56f15b569a |
#98: prove the lamp fix -- table pinned to the image, three condensers verified live
Follows the request to prove the whole fix rather than the one case I had.
1. TABLE, all indices: scratchpad/night8/lamptable_check.py asserts the
reconstructed arrays against BTL4OPT.EXE and exits nonzero on drift.
kBTCondenserLamp @0051d058 MATCH 07 2F 2E 2D 2B 2A 29
kBTPlacementLamp @0051d070 MATCH 29 1A 1B 1C 1D
kFixed (FUN_004cc148 switch) MATCH 2F 2E 2D 2C 2B 2A
This covers condensers 2/3/5 without hunting zones for them: there is no
per-index code path, only the array contents, and those are now pinned.
2. LOOKUP PATH, live: condensers 1, 4 and 6 resolve 0x2F, 0x2B and 0x29 --
exactly the table. Condenser 4 is the meaningful control: my reverted "fix"
would have given 0x2C. Myomers (eng-page path, 0x25/0x21) and SRM6_1 (quad
button, 0xd) still annunciate, so other subsystem classes are unregressed.
3. GUARD: condenserNumber is parsed from the name's trailing digit
(NameTrailingNumber, mirroring the binary's atoi), and every condenser in
BTL4.RES is Condenser1..Condenser6 -- no Condenser0, no bare name. So the
1..6 guard covers every shipped case and slot 0 (0x7) is unreachable. This
was the real risk in changing `n >= 0` to `n >= 1`; it is closed.
4. PIXELS: leaklamp_pixel.{sh,py} capture a leaking run and an undamaged control
run from the cockpit and difference their per-pixel temporal variance. The
leak is visibly real -- the COOLANT reservoir drains on screen (S 331->330
while the control sits at 329).
⚠ WHAT THE PIXELS DID NOT SETTLE, and it is not a testing gap. Lamp 0x29 is
ALSO kBTPlacementLamp[0]: DAT_0051d058[6] and DAT_0051d070[0] are the SAME int32
-- the two tables abut. So condenser 6's lamp may not be its own loop button at
all, and slot 6 may be an overrun in the BINARY too (its read is unchecked).
Reproducing it is the faithful choice either way, and we now do exactly what the
binary computes -- but whether a pilot sees loop 6's own button light is a
question only someone who played the original can answer. Asked on the issue.
CORRECTION:
|