Find where the game reports powerup hits for every racer (runtime spike) #17
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
#18 Powerup hit events on the game-state websocket
mintopia/blur
Reference
mintopia/blur#17
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Confirm, at runtime on
BLUR_ENGINE=recomp, where the game reports powerup hits for every racer and what the payload means, so the feed can emit hit events (follow-up ticket). Research spike: a throwaway hook + log, findings written up, no feed changes.What static analysis found (shipped
Blur.exe, sha256dba0264b...)0xef64f0, slot 4 (+0x10) = handler0xc8ee40(void __thiscall(this, int event_id, ?, const Payload*),retn 0xc). It switches onevent_id0..14 (byte map0xc90014, jump table0xc8fff4): ids 0, 2, 4, 10, 11, 12, 14 are handled.0xc8f63a): it playsPickupHitCar,PickupHitShield,PickupSplashHitCar,PickupDoubleHitCar,PickupBackwardsHitCar,PickupModHitCar,PickupMiniHitCar... (strings0xef665c..0xef677c) and setsFocusCarFired,IsFocusCar,PickupLevelHit.Payload fields used:
+0x8u8,+0x9u8 racer index (looked up with0xb915d0-> racer object, whose+0xbc> 0 selects the...HitShieldsound, so probably the car that was hit),+0xau8 other racer index or0xff(probably the firer),+0xcint powerup kind (forced to 1 when+0x20 == 4; values 2 and 6 take other branches),+0x10s8 level,+0x14int,+0x20int hit kind.0xc8ef14,PickupBackwardsEnded/PickupDoubleEnded/PickupEnded...: payload+0x180..9) and event 12 (case0xc8ee77,PickupHitWorld/PickupHitProjectile) are the other pickup events.+0x9to work outIsFocusCar(0xc8f6dc..0xc8f716).0xc8ee40is a function in the current recomp build (work/recomp/m3/gen/recomp_table.cpp) and runs during races (work/recomp/m2/tb_blur_race.txt), so it can be hooked: add it toHOOKSintools/recomp/emit.py, regenerate, and register inrecomp::hooks()likesrc/host/skip_intro.cpp.UIEnums.GameMessage.Wreckedmessage and showsMPL_WRECKED_BY(dumpedScreens/Ingame/Hud.luaMessageUpdate), and Lua hasGetWreckerModLoadout(player_index), but these are for local players only. The AI dialogue triggers (DriverHitByPerk,PlayerHitByPerk,DriverIsFatallyDamaged, name table0xbcfb20) are SP AI-only. Neither covers every racer.0xe851d0, subscriptions keyed by+0x14, unsubscribe0x523cd0), racer health, wreck flag, damage amount.Steps
0xc8ee40that logsevent_idand the first 0x30 payload bytes (plusrace_time) and then calls the original.tools/modes/plans,work/ws-capture). Note each hit (who fired, who was hit, powerup, shielded, wrecked).+0xebcis set to -1.0 at init0x450cd7, a candidate; check "DriverIsLowHealth" thresholds) and whether 5 Hz polling inrace_scan.cppwould catch wrecks. Report damage per hit if found.Deliverable
A comment on this issue with the payload layout, powerup kind table, availability matrix (SP / split-screen / LAN x local / AI / remote) and the recommended hook point, plus an update to ADR 0013 (or a new ADR) if hooking becomes the recorded approach. Do not commit the hook unless it is the follow-up's code. Recomp only.
Acceptance criteria
Spike results (recomp, runtime)
I ran a throwaway hook in a private build (not committed). It logged every event reaching
0xc8ee40and every send through0x535770, plus 5 Hz snapshots of each racer's object. Runs:BLUR_NO_AUDIO=1Firing used virtual pad A; keyboard fire didn't work in the driver. About 690 hit events in total. ADR 0016 (
docs/decisions/0016-powerup-hits-from-game-event-send.md) records the approach.Event 4 payload (0x28 bytes)
0xaac4c005(pickup messages are0xaac4c0NN)0xffRacer index = racer
+0x314, the same numbering asstandings/racer. Shield and wreck aren't in the payload. Read them from the victim racer when the event fires, because damage is already applied by then:+0x3dc: full is 599/649/699 depending on the car, stops at 0+0xbc > 0; damage 0 in all 11 shielded hits+0x38c/+0x3acare also 1 while wrecked)Powerup kind (
+0xc; game enum at0xf6ea80) and damage measured+0xa= self)+0x8racer,+0xc= 7)Other events on the same channel: 2 = pickup ended/projectile expired (
+0x8racer,+0xckind,+0x10serial), 12 = hit world/projectile.Availability matrix (event 4 seen?)
LAN: both instances logged the same 5 hits with the same serial
+0x14and the same wall-clock time (within about 10 ms). Each instance uses its own racer numbering: on each one, local = 0. Health+0x3dcmatched on both. The damage-taken (+0x598) and hits-taken (+0x5d8) counters only update on the victim's own instance. In this driver the unfocused instance ignores pad input, so only the focused one fired.Recommended hook point
0x535770, the game's event send (this= hub0x111d1e8, argschannel, event_id, a2, payload). Filter onchannel == 9 && event_id == 4.0x475dbd,0x47691d,0x476aab,0x476e44.(*this)[9](= the audio listener0xc8ee40) before calling it, so it doesn't depend on audio. That comes from reading the code; a missing sink was never seen at run time.BLUR_NO_AUDIO=1only drops our dsound backend. The sink still exists and still gets the events.Wreck/damage polling
Needed for wrecks.
+0x3dc= 0 (and+0x38c= 1) for 2.6-4 s until respawn, so the existing 5 Hzrace_scan.cpppoll always catches it.+0xebcis a timer, not health.#18 is updated with this mapping.
Completed and merged by Harmonic (task 18).