CTaskCloud::HandleMessage RenderScene was sending `pos_y` (field 0x34)
as the sprite Y, which the constructor masks to `(pos_x + render_y) &
0xFFFF` — always 0 — pinning every cloud to the very top of the level.
The original at 0x548527..0x54852f loads `[ESI+0x44]` into EAX (the
usercall Y register for RQ_DrawSpriteLocal) and pushes `[ESI+0x34]` as
the trailing stack arg, which lands in the queue command's `palette`
slot and acts as a per-cloud animation/palette index incremented each
frame by `vel_y`.
Rename the misnamed `y_seed: u32` to `render_y: Fixed`, swap the two
values in the DrawSpriteCmd write, and correct the field docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: fix DDGameWrapper size to 0x6F10 and remove WABox
DDGameWrapper had an off-by-16 size assertion (0x6F00 vs actual allocation
0x6F10); the 0x20-byte tail is unzeroed unknown padding. Fixed struct field
and size assertion.
WABox never had a Drop implementation so it was purely a leak-by-design
wrapper with no safety value. Remove it entirely and replace all uses:
- WABox::alloc(...).leak() → wa_malloc_struct_zeroed::<T>()
- WABox::from_value(val).leak() → wa_malloc_struct::<T>() + ptr::write(p, val)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refactor: rename TeamArenaState to TeamArena, use raw pointers for soundness
Port the cloud spawning factory (0x552960) that creates a CTaskFilter
and spawns CTaskCloud children with deterministic LCG randomization.
- Add rng.rs with shared wa_lcg() used by advance_rng, advance_effect_rng,
and cloud spawning (eliminates duplicated LCG constants)
- Port create_weather_filter in task/team.rs with naked asm bridge for
CTaskFilter constructor (usercall ECX=0x1B)
- Add Subscribe (slot 8) and SubscribeAll (slot 7) to CTaskFilterVTable
- Add CTASK_TEAM_CREATE_WEATHER_FILTER and CTASK_FILTER_SUBSCRIBE addresses
- Hook via MinHook in wormkit cloud.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SpecialFireSubtype::NapalmStrike (subtype 13) is actually used by
multiple weapons (Napalm Strike, Surrender, and others) — it's a
generic "send team message 0x2B" handler, not Napalm-specific.
Renamed to StrikeFire to match the WA function name (StrikeFire at
0x51E2C0). Also renamed TeamMessage::NapalmStrike to StrikeFire and
on_napalm_strike to on_strike_fire.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: eliminate noalias UB in vtable dispatch, add typed TeamMessage
The bind_! macro generated &mut self methods for vtable calls, giving
LLVM noalias guarantees that WA bridge calls violated. This caused
codegen-sensitive desyncs where unrelated code changes would shift
optimization decisions and expose the UB.
Fix: auto-generate _raw associated functions (taking raw pointers) for
every bind_! vtable method. Migrate all call sites in weapon dispatch,
weapon release, sound, cloud, and filter handlers to use _raw variants
and CTask::ddgame_raw() instead of the Task trait methods.
Also adds TeamMessage enum with NapalmStrike variant as the first typed
message, and CTaskTeam::on_napalm_strike handler (not yet wired up due
to a separate binary layout sensitivity issue under investigation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make CTask generic over its vtable pointer type:
CTask<V: 'static = *const c_void>
This allows subclasses to specify their typed vtable:
CTaskTeam { base: CTask<*const CTaskTeamVTable>, ... }
Changes:
FieldRegistry derive macro (openwa-derive):
- Handle generic type params by substituting defaults in size_of
expressions. offset_of uses the bare struct name (Rust applies
defaults). Builds a generic_defaults map from TypeParam::default.
bind macro (openwa-derive):
- Accept dotted field paths ($($vtable_field:ident).+) instead of
single idents, enabling bind_VTable!(Struct, base.vtable) for
inherited vtables.
CTask (openwa-core):
- Add generic parameter V with default *const c_void. All existing
code using bare CTask continues to work unchanged.
CTaskTeam (openwa-core):
- Use CTask<*const CTaskTeamVTable> as base type
- Replace manual vtable() and handle_message() with bind macro
- bind_CTaskTeamVTable!(CTaskTeam, base.vtable) generates typed
method wrappers automati…
feat: add CTaskTeamVTable and typed handle_message dispatch
Define CTaskTeamVTable (12 slots, vtable at 0x669EE4) via #[vtable] macro
with typed signatures for handle_message (slot 2), write_replay_state,
free, get_entity_data, and process_frame.
Add CTaskTeam::vtable() accessor and CTaskTeam::handle_message() method
that replaces raw vtable pointer arithmetic in weapon fire handlers.
Extract lookup_team_task() helper for SharedData CTaskTeam lookup.
Note: bind_CTaskTeamVTable! not used because CTask.vtable is *mut u8
(FieldRegistry derive doesn't support generic structs, so CTask<V>
would require manual impls across the entire task hierarchy).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: distribute task class addresses to their home modules
Move all CTask* class blocks from address.rs into their respective
task/ module files. address.rs re-exports via pub use so all existing
va::* call sites are unchanged.
Moved to task/base.rs: CTask, CTaskLand, CTaskDirt, CTaskSpriteAnim,
CTaskCPU, CTaskSeaBubble, + 10 ctor-only entities
Moved to task/game_task.rs: CGameTask
Moved to task/worm.rs: CTaskWorm
Moved to task/turn_game.rs: CTaskTurnGame
Moved to task/missile.rs: CTaskMissile
Moved to task/mine_oil_drum.rs: CTaskMine, CTaskOilDrum
Moved to task/supply_crate.rs: CTaskCrate
Moved to task/cloud.rs: CTaskCloud
Moved to task/filter.rs: CTaskFilter
Moved to task/fire.rs: CTaskFire
Moved to task/team.rs: CTaskTeam
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: add #[derive(FieldRegistry)] to all task entity structs
Apply FieldRegistry to every #[repr(C)] struct under task/:
CTaskMine, CTaskOilDrum, CTaskCrate, CTaskCloud, CTaskFilter,
CTaskFire, CTaskTeam, CTaskTurnGame, TurnGameCtx, BitGrid,
BungeeTrailTask, WeaponAimTask.
All entity types now have auto-generated field maps available in the
global registry, so the debug UI's raw memory view labels every known
field automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: remove extra loading progress ticks, cargo fmt
Reduce from 4 to 2 DDGameWrapper__LoadingProgressTick calls, matching
the original constructor. Tick counter +0x04D8 now matches exactly
(0x334). All DDGameWrapper fields match the original.
Remaining headless checksum mismatch is likely due to headless display
handling differences (our headless display is non-null vs original's
NULL display parameter).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: split task.rs into per-type modules under task/
Each task struct now lives in its own file. Tightly coupled types
are kept together (TurnGameCtx+CTaskTurnGame, CTaskMine+CTaskOilDrum,
SoundEmitter+CGameTask, CTaskWorm+CTaskWormVTable). mod.rs re-exports
all public types so external import paths are unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>