Builds on the project's existing proof that a Wwise sound replacement works, and asks the next question: how to ADD a genuinely new event (not overwrite an existing one), and how weapon/kiscule decls name the event they trigger. Found and structurally proven: a sound event's engine-side id is a hash of its lowercased name, the same mechanism already known from cutscene videos turns out to be identical for weapon fire events, and a complete new Event→Action→Sound chain (plus new audio data) can be appended to a copy of a real sound bank without touching any existing object, then be found again, correctly routed, and correctly decoded by the project's own unmodified readers. Everything here is a structural round trip on file copies — the game was never launched, so whether a posted event actually plays audible sound in-game is not yet known.
The naming mechanism
An event's HIRC object id is FNV-1 (not FNV-1a) of the lowercased name: digest = 0x811C9DC5; for each byte: digest = (digest * 0x01000193) XOR byte. Already known for play_<video> cutscene names; this pass confirms the same formula resolves real weapon event names pulled live from decls:
play_p61_pl_single -> real Event object (projectile/player/pistol's fireSoundEvent)
play_mp61_pl_fire_single -> real Event object (projectile/player/smg)
play_this_event_does_not_exist_zz9z -> not found (negative control)
Case does not matter — fnv1("Play_x") == fnv1("play_x") — which matters because decls spell event names with mixed case. 47 projectile decls carry a fireSoundEvent field; this is the field a mod edits to point a weapon at a different, or a brand-new, event — a plain text-decl edit, the same already-proven write path as any other decl value edit.
HIRC object formats (confirmed by building, not just reading)
- Event (type 4):
u8 count, u32[count] action_ids. - Action (type 3), Play (
0x0403):u16 type, u32 target, u8 isBus, <props block>, then undecoded trailing bytes whose exact meaning is not settled — handled safely by cloning a real Action's bytes and patching only the known fields, never composing new ones. - Sound (type 2):
u32 plugin, u8 stream_type, u32 source (media id), u32 size (must equal the DIDX row's size), u8 bits, thenNodeBaseParamsat offset 14 including au32 busfield — same clone-and-patch-3-fields discipline.
A Play action's target points at a Sound object directly in 91.5% of all 38,236 surveyed retail cases (the rest go through Random/Switch/Blend containers or music objects) — so building a new event as the simple Event → Action(Play) → Sound chain, skipping any container, is the representative case, not a toy simplification.
Bus routing
81 real Audio Bus (type 8) objects exist corpus-wide. Hashing plausible Wwise bus names and checking against the real id set confirms 6 real buses by direct id match, not by reading strings: Master Audio Bus (the root, parent id 0), Music, Ambience, Explosions, VO, and Weapons (0x577F56AC) — the bus this round's new Sound object was routed to, independently verified rather than guessed. TNC's entire mix-bus graph lives inside init.bnk's HIRC data; it has no soundmixing-decl equivalent (that mechanism is TNO-only).
The round trip (structural, PROVEN)
Three new HIRC objects + one new media slot were appended to a copy of a real bank, with zero existing objects touched, then re-read with the project's own unmodified readers:
- Sound cloned from a real object, 3 fields patched (
source,size,bus). - Action cloned from a real Play action, 1 field patched (
target). - Event built fresh, id =
FNV1(new_name), verified absent from the 85,719-id retail+DLC corpus beforehand. - A synthetic 16-bit PCM tone appended as new media, indexed by a new DIDX row;
HIRC's object count bumped by 3.
Result: the new event resolves by its hashed name, walks to the right action, to the right Sound, to the right byte-exact media, on the correct bus; every pre-existing sound in the same bank still decodes (regression control); a 1-character-different name does not collide; and — the sharpest negative control — leaving the HIRC object-count field un-bumped makes the new Event genuinely invisible to the same parser that otherwise finds it, confirming the count field is load-bearing.
Honest limitation: the real pistol event is structurally deeper than what this build uses — it goes through a type-6 Switch Container (for soundStateSwitchName variant selection), which was not decoded this round. Not needed for the deliverable, since a direct-to-Sound chain is the dominant retail shape anyway, but it means the switch-based variant mechanism itself remains unexplored.
Recipe to ship a new event in a mod
- Find the bank certainly loaded when the trigger posts the event — for a weapon, resolve its existing
fireSoundEventname and read off which bank it lives in (the pistol's issb_pl_p61.bnk, overridden by a patch-level pack — same name-precedence rule this project already documents for every other asset type). - Clone a real Sound and a real Action object from that same bank, patch only
source/size/busandtarget. - Append the media, add its DIDX row, append the 3 new HIRC objects, bump the object count — all through the project's existing archive write/finalize path.
- Point a weapon at it by editing the projectile decl's
fireSoundEventtext field. - Re-verify offline exactly as this round did before touching the real game.
INFERRED (not runtime-proven)
- Whether a newly-added event is resolvable only from the specific bank it was added to, or from any bank currently loaded in memory — the safe assumption is "same bank as the weapon's own real events," never verified against a running game.
- That the undecoded trailing bytes on Action/Sound objects are safely ignorable when cloned verbatim from a real object in the same bank.
- That posting a custom event through a real decl edit produces audible sound in game, as opposed to silence or a validation rejection.
In-game test (not yet run)
Patch two things together: the target bank gets the new Event/Action/Sound/media quartet (use a distinctive 2–3 second tone, not silence, so a "no audible difference" result is unambiguous), and the pistol's fireSoundEvent is redirected to the new event name. Fire the (non-silenced) pistol once. Three distinguishable outcomes: new tone plays (mechanism works end to end); normal pistol shot plays (event not found — the bank-scope assumption above is wrong, or something in the construction is off); no sound or a crash (the undecoded trailing bytes or the Switch Container layer are not safely ignorable after all). Revert and re-fire to confirm the change was scoped to only this bank/decl.
Related: Audio, Streamed Audio Containers, TNC Modding Surface.