Move BitGrid from task::bit_grid to display::bitgrid as its canonical
home. Delete the old module. Rename fields to match known semantics:
_unused_04 β external_buffer, _unused_1c/20 β clip_left/clip_top,
width_dup/height_dup β clip_right/clip_bottom.
Convert bit_grid_init to BitGrid::init with *mut BitGrid parameter.
Replace magic vtable address with va::BIT_GRID_VTABLE constant.
Remove redundant double memset. Remove dead TaskStateMachine class.
Rename DDGame::display_gfx β display_bitgrid (it's a BitGrid, not
DisplayGfx). Rename Sprite::display_gfx β bitgrid_vtable. Clean up
tsm variable names and stale re-exports throughout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: rename openwa-wormkit crate to openwa-dll, output openwa.dllβ
The project hasn't been a WormKit DLL for a long time β it requires a
custom launcher. Rename the crate to openwa-dll with output openwa.dll
to match the rest of the naming convention (openwa-core, openwa-launcher).
Updates all references across Cargo.toml, Rust sources, PowerShell scripts,
CLAUDE.md, skill files, and docs. Replaces stale "wormkit hook/shim"
comments with accurate descriptions. Three remaining "WormKit" references
are legitimate (refer to the actual WA modding framework).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: replace raw pointer arithmetic with typed struct accessβ
- weapon_fire.rs: use DDGame.level_width/level_height instead of
.add(0x77C0/0x77C4); use new CTaskWorm accessors instead of
.add(0x34/0x48) on raw worm pointer
- sound_ops.rs: use CGameTask.pos_y instead of .add(0x88)
- sound_ops.rs + active_sound.rs: use SoundEmitter.local_ref_count
instead of .add(8) on raw emitter pointer
- game_task.rs: split SoundEmitter._unknown_04 to expose ref count
field at offset 0x08
- worm.rs: add fire_subtype_1/set_fire_subtype_1_raw/set_action_field_raw
accessors; rewrite state() to use subclass_data slice indexing
Co-Authored-By: Claude Opus 4.6 <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>
refactor: use Weapon enum for weapon IDs and classificationβ
Change CTaskWorm.selected_weapon from u32 to Weapon enum. Update
is_super_weapon, is_weapon_category_a, is_weapon_category_b to take
Weapon and match on named variants instead of hex magic numbers.
Replace raw 0x22/0x24 check with Weapon::Girder/GirderPack match.
Use SpecialFireSubtype variants in type-4 sound dispatch.
Idiomatic range check: Weapon::JetPack..=Weapon::CrateShower.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: add weapon fire type enums, use Fixed for aim/scale paramsβ
Add FireType, FireMethod, and SpecialFireSubtype enums to weapon.rs
with TryFrom<i32>. All weapon dispatch matches in weapon.rs and
weapon_release.rs now use typed enum variants instead of raw integers.
Rename fire_subtype_34 β special_subtype, fire_subtype_38 β fire_method
in WeaponEntry for clarity.
Use Fixed type for aim direction params and landscape_scale field.
Add Mul<i32> for Fixed (integer scaling without shift). Remove
fixed_mul_shrd β superseded by Fixed's Mul impl. Spawn offset
calculation now reads as: aim_dir_x * scale * 0x18.
Rename param_1/param_2 β spawn_x/spawn_y, param_3/param_4 β
aim_dir_x/aim_dir_y based on caller analysis (FUN_0051cb60 computes
adjusted worm position + aim direction from angle).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The only creation site for this task type is FireWeapon__Girder (0x51E350).
The "FireBall" name was a legacy RE guess; it's actually a short-lived
terrain placement task for girder/bridge objects.
Rename in ClassType enum (26 = Girder), weapon.rs comments, task-hierarchy
docs, and Ghidra labels.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: port weapon firing pipeline β CreateWeaponProjectile, ProjectileFire, CreateArrowβ
Port the core weapon firing functions from WA.exe to Rust:
- CreateWeaponProjectile (0x51E0F0): full replacement β allocates CTaskMissile
(0x40C bytes), SharedData lookup for parent CTaskTurnGame, pool capacity
check, calls original constructor. All projectile weapons flow through this.
- ProjectileFire (0x51DFB0): full replacement β advances game RNG, computes
random spread angle via Fixed16.16 trig table interpolation, applies 2D
rotation matrix to template velocity, calls ProjectileFire_Single per shot.
Used by Uzi, Handgun, Minigun.
- CreateArrow (0x51ED90): full replacement β allocates CTaskArrow (0x168 bytes),
same pool/SharedData pattern as CWP. Used by Shotgun and Longbow (which fire
physically simulated arrow entities, NOT hitscan).
Also adds:
- WeaponSpawnData struct (11 DWORDs) replacing raw [u32; 11] in CTaskMissile
- CTaskArrow struct (0x168 bytes) with vtable at 0x664198
- CTaskMissile size corrected from 0x41C to 0x40C (matching actual allocation)
- Pasβ¦
fix: trim trailing default bytes in V3 scheme serializationβ
V3 .wsc files only include extended options bytes that differ from
defaults. Parsing padded short files to full size, but to_bytes() wrote
the full payload back, breaking roundtrip equality.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: resolve all clippy warnings across core, wormkit, and test-runnerβ
The region at WeaponEntry+0x3C onward is passed as a pointer to all
fire sub-functions. Extract it as WeaponFireParams (0x194 bytes),
embedded in WeaponEntry. Internal layout still unknown but the struct
boundary is now explicit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: eliminate pointer arithmetic from FireWeapon implβ
- Rename WeaponEntry.fire_complete β fire_params_start (u32), since
its address is passed to sub-functions as a params base pointer
- Add CTaskWorm::fire_complete() / set_fire_complete() for the
completion flag at CGameTask.subclass_data[12] (worm+0x3C)
- Rename params β fire_params, worm_u32 β w for clarity
- All field reads now go through typed struct access, no raw offsets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: identify weapon_ctx as WeaponEntry pointer, add fire fieldsβ
Runtime investigation confirmed CTaskWorm+0x36C points to the active
WeaponEntry in the WeaponTable (entry index matches selected_weapon).
Add newly discovered WeaponEntry fields:
- fire_type (+0x30): weapon dispatch type (1-4)
- fire_subtype_34 (+0x34): subtype for types 3/4
- fire_subtype_38 (+0x38): subtype for types 1/2
- fire_complete (+0x3C): completion flag / params base
Type CTaskWorm.active_weapon_entry as *mut WeaponEntry.
Remove incorrect CTaskWorm weapon accessors (they read from the worm's
subclass_data instead of the WeaponEntry).
Verified: headless PASS (byte-identical).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: correct WeaponTable layout β no header, add entry fieldsβ
InitWeaponTable analysis revealed the weapon table is a flat array of
71 Γ 0x1D0 = 0x80B0 bytes with NO 16-byte header. The previous struct
had a phantom _header that made address math accidentally correct but
mislabeled the field being checked.
WeaponEntry now has proper fields:
- panel_state (+0x08, init 0xFFFFFFFF)
- defined (+0x10, checked by CheckWeaponAvail)
- availability (+0x24, init 0xFFFFFFFF, then 0/1 per weapon)
- enabled (+0x28, init 1)
check_weapon_avail now reads `entry.defined` instead of misnamed
`entry.name1`. Updated weapon-system.md to match.
Verified: headless PASS (byte-identical), headful 133+3 PASS / 0 FAIL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: add WeaponEntry/WeaponTable structs, use Weapon enum in matchβ
Add WeaponEntry (0x1D0 bytes) and WeaponTable (header + 71 entries)
structs from wkJellyWorm RE data. DDGame.weapon_table is now typed
*mut WeaponTable, enabling array indexing instead of pointer math.
Use Weapon enum names in check_weapon_avail match arms instead of hex
literals. Fix hex-to-decimal conversion errors (0x37=55=NuclearTest,
not 0x37=37=NinjaRope). Rename fields to match actual weapons:
- weapon_36_disabled β donkey_disabled
- weapon_42_mode β invisibility_mode
- weapon_avail_threshold β double_turn_time_threshold
- weapon_index_offset β aquasheep_is_supersheep
- weapon_restriction_active β supersheep_restricted
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>
fix: correct EXTENDED_OPTIONS_DEFAULTS and read from ROM at runtimeβ
The hand-transcribed EXTENDED_OPTIONS_DEFAULTS constant had bytes
shifted from offset 0x13 onwards, causing game_engine_speed to default
to 0x01018080 (257x) instead of 0x00010000 (1.0x). This made all
.wsc scheme files play at unlocked frame rate.
Two fixes:
1. write_scheme_to_dest now reads defaults from WA's ROM at runtime
via rb(SCHEME_V3_DEFAULTS), making it impossible to diverge
2. Replaced the constant with byte-exact ROM dump (verified at runtime)
Also accept variable-length V3 scheme files (payload between 0x124
and 0x192 bytes), padding missing extended options with defaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: reorganize openwa-core into subdirectory modulesβ