5d6ded73f0aee12a05ebf33db1c0b50e17d32f9b
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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>
|
||
|
|
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
|
||
|
|
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:
|
||
|
|
c50236a5e6 |
bench: shorten the leak-lamp run; verified condensers 1/4/6 resolve 0x2F/0x2B/0x29 (the binary's table), plus Myomers + SRM6 eng/quad lamps unregressed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0254d9ef34 |
#98: leaking condensers now flash the RIGHT button -- half of them flashed the wrong one, one flashed nothing
Field reports were all "intermittent": "the display buttons aren't always
flashing or lighting up on leaking components" (Oracle), "I'm getting leaks but
no indicators" (Lynx), "I am getting indicators sometimes" (Sauron). It is not
intermittent -- it is per-condenser, and three of the six were wrong.
condenserNumber is 1-BASED (verified live: 'Condenser6' reports 6), which the
lamp table's own comment stated. The guard was `n >= 0 && n < 6` -- a 0-based
bound -- and the table it indexed was missing 0x2C:
condenser 1..3 -> 0x2F 0x2E 0x2D correct
condenser 4 -> 0x2B WRONG (loop 5's button)
condenser 5 -> 0x2A WRONG (loop 6's button)
condenser 6 -> rejected NOTHING flashes
So a leak in loop 6 annunciated nowhere, loops 4-5 lit a neighbour's button, and
loops 1-3 were fine -- which from the cockpit reads exactly as "sometimes".
Condenser N drives cooling-loop N, whose six lamps are the same ones the
coolingLoop1..6 codes already resolve through, so the fix routes condensers
through that verified map (BTFixedLampOf(N-1)) and retires the duplicate,
partly-wrong table rather than patching it.
⚠ PROVENANCE: the retired table cited @0051d058 as byte-verified. That address
holds gauge-type NAME STRINGS, not lamp ids, and a byte search for the six loop
ids as consecutive int32 finds nothing -- so neither the old table nor the new
mapping is byte-verified. The correction rests on three checkable things: the
1-based numbering (live), the guard contradicting its own documented indexing,
and six condensers mapping onto the six cooling-loop lamps of the verified fixed
map. [T2 -- behaviour verified, not byte-grounded.]
Also adds the missing diagnostic on the silent path: an alarm item that matched
its condition but resolved no lamp now names the subsystem and why, instead of
returning quietly. That is what found this, and it immediately surfaced a
SECOND gap for someone to pick up: a destroyed HeatSink (condition 0) resolves
no lamp either, because it is neither Condenser nor Generator nor a
PoweredSubsystem with an aux screen.
Verified (scratchpad/night8/leaklamp.sh, BT_LAMP_LOG):
before [galarm] condition 2 ... sub 'Condenser6' -> NO LAMP RESOLVED
after [galarm] condition 2 ... -> lamp 0x2a FLASH
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ad9dfade88 |
#32: POOL the OpenAL sources instead of creating and destroying one per sound
Every 4.11.674 player log is saturated with acquisition failures -- 3031, 4657, 5245, 6275, 6571 across five machines. In Lynx's largest session the first failure lands 9.3% in and they continue to 96.8%: once it starts it never recovers for the rest of the match. CAUSE: RequestAudioChannels called alGenSources() per sound event and ReleaseSourceSet called alDeleteSources() on release -- create and destroy per sound. OpenAL sources are a scarce driver resource (OpenAL Soft caps a context at 256) and a combat burst churned straight through the ceiling. The two counters looked contradictory and were the tell: ACQUIRE FAILED always printed live=256 while the 30s census printed live=6. Same global, sampled at different moments -- sources spike to the cap during a burst and drain back between them. Churn, not a steady leak. FIX: generate sources once, up to a cap, and recycle them through a free list. Release scrubs and parks instead of deleting. Steady-state play performs no AL allocation at all. The scrub is load-bearing, not hygiene: a recycled source carries whatever the previous owner set, and the engine sets AL_LOOPING per sound (L4AUDLVL.cpp:327). Hand a looping source to a one-shot and it plays forever -- which is the "sound stuck looping" family (#51, #5). Every reusable property is reset at the single point where a source changes owner. VERIFIED (scratchpad/night8/audiopool.sh + the two-node mp_burst.sh): solo, sustained fire : 0 failures, pooled 152, reuses 21998 two nodes, 4 min : 0 failures on both, pooled 148/149, reuses ~7000 each ⚠ HONEST LIMIT: the bench does NOT reproduce the field failure -- the PRE-fix binary also scores 0 on it (peak 49 live), because solo/2-node combat is not dense enough to reach 256. So this verifies the pool works and allocates nothing in steady state; it does NOT by itself prove the field failures are gone. The five field logs remain the "before". Peak demand is set by how many audio COMPONENTS are alive (each reserves a SourceSet of up to 25 voices and holds them), not by audible sounds: measured high-water 138 solo, 149 two-node. That scales with player count, so the cap is set near the driver ceiling (240) and the pool now logs its high-water mark once per 25-source band -- so the next playtest sizes this from field data instead of a guess. Growth also self-limits: if a driver offers fewer sources than the cap, alGenSources simply fails, growth stops, and the pool recycles what it has. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3a22c334ac |
bench: two-node CROSS-POD damage verification for the #95 burst change
Projectile damage now Dispatches directly at the victim instead of going through
the shooter's SubsystemMessageManager, so cross-pod delivery had to be proven on
real nodes rather than assumed.
RESULT: cross-pod delivery intact, and the cluster count survives the wire.
Node A fired 38 missile rounds with bursts {1:5,2:6,3:6,4:7,5:7,6:7}; node B
received exactly the matching zone applications {2:12,3:18,4:28,5:35,6:42} --
i.e. rounds x burst, per burst band, exact. 135 of B's 142 explosive
applications carried burst > 1 (the manager path used to drop it to 1).
Energy stayed at burst 1 on both nodes; no crash on either.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
3b7c19c232 |
#95: REVERT the salvo-damage hack; deliver the cluster the way the binary does
Supersedes the mislanch change in |
||
|
|
efc3e9ff1a |
#95/#84: missile salvos deliver their FULL authored damage, and stop double-exploding
THE SALVO DAMAGE (#95). Players measured an LRM 15 landing "3ish points". The logs agreed: [projectile] IMPACT damage=3.33333 (Oracle, LRM15) and 3.5 (Rajel, LRM10). Those are right PER MISSILE -- 50/15 and 35/10 -- and the bench shows why the salvo still under-delivers: each launcher pushes N rounds of which exactly ONE carries damage. Two individually-correct changes composed into an N-fold shortfall: * The ctor does what the binary's MissileLauncher ctor does (@0x3ac/@0x3d4): damageData.burstCount = missileCount; damageData.damageAmount /= missileCount; The record holds the PER-MISSILE amount plus the count; the arcade reconstitutes amount x burstCount when it applies the hit. * Task #62 then correctly stopped the port applying the hit once per visual round (that was ~missileCount-x too lethal) by damaging only the lead round -- but handed it the already-divided amount. Our DamageZone::TakeDamage is `damageLevel += amount * scale` and drops burstCount, so the salvo delivered amount/missileCount. Since the port collapses the cluster to one damaging round, multiply the count back in there. Bench: an SRM6 salvo now lands amt=35 (the authored total) taking a zone 0 -> 0.556, where it previously landed 5.83. THE DOUBLE EXPLOSION (#84). Oracle: "missile appear to register hit explosions twice, once where target was and again where the target is." There are two spawn sites: BTSpawnRoundDetonation at the round's own impact point, and the message manager's bundled explosion at the CONSOLIDATED point a frame later. The duplicate was known and thought harmless -- "among a rippled volley it is invisible" -- which held only while a salvo landed N detonations. A projectile now marks its weapon (MarkRoundDetonated) and the consolidation skips queueing a second blast for it; direct-fire weapons never mark, so lasers/AC keep the bundled explosion they rely on. Bench: 4 missile impacts -> 4 SKIPPED, while 11 direct-fire hits still queue normally. SWEPT CONTACT (#84 tail). Contact was a 10-unit sphere sampled only at the END of each step. With the authored thruster live (#84) field rounds arrive at v=955 -- a ~16 unit step at 60fps, larger than the radius -- so samples can straddle the target. (Pre-#84 rounds flew ~100-300 = a 1.7-5 unit step and could never skip it: the velocity fix exposed this, it did not cause it.) Now tests the whole segment travelled and bursts at the point of NEAREST APPROACH, which also stops the detonation being flung past the target at speed. RETRACTION: I posted tunnelling as the leading explanation for the lost salvo. The bench disproves it -- zero fizzles, and the "missing" rounds are the dmg=0 visual rounds of the cluster, which never registered damage by design. The sweep is kept as speed-independent robustness, not as the #95 fix. Bench: scratchpad/night8/salvo.sh (BT_PROJ_LOG + BT_FIRE_LOG). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |