Port IMG_Decode (0x4F5F80) and DisplayGfx__Constructor (0x4F5E80) to
pure Rust, eliminating the last transmute bridges in the asset loading
pipeline. img_decode returns Option<DecodedBitGrid> with Display/Collision
discrimination; img_decode_cached always returns *mut DisplayBitGrid.
- Add asset/img.rs with img_decode and img_decode_cached
- Replace transmute bridges in gfx_dir.rs, ddgame_constructor.rs
- Hook IMG_DECODE and IMG_DECODE_CACHED for remaining WA callers
- Type DDGame fields: gradient_image, gradient_image_2, arrow_sprites
as *mut BitGrid instead of *mut u8
- Rename GfxResource__Create_Maybe β IMG__LoadFromDir (Ghidra + Rust)
- Rename DisplayGfx__Constructor_Maybe β IMG__DecodeCached (Ghidra + VA)
- Propagate Option<DecodedBitGrid> through call_gfx_find_and_load
and call_gfx_load_and_wrap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: port BufferObject ctor, replace more bridges with typed callsβ
- Port DualBufferObject__Constructor (0x545FD0) to Rust: the original
function was usercall with an implicit EDI register carrying the
second buffer's capacity. Traced the disassembly to find the capacity
is 0x2DC or 0x46C depending on game_info version fields.
- Replace PaletteContext__Init bridges in ddgame_constructor.rs with
the Rust palette_context_init() ported in the previous commit.
- Replace call_display_gfx_dirty_rect raw vtable dispatch with typed
DisplayBitGrid::fill_rect_raw(), using named struct field access
instead of raw pointer offset arithmetic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: replace raw pointer arithmetic with typed field access in game_state_initβ
Eliminate ~120 raw pointer arithmetic expressions (`ddgame_raw.add(offset)`,
`w.add(offset)`) in favor of named struct field access through typed pointers.
- Split DDGameWrapper._unknown_0f4 into 7 named team scoring arrays
- Split DDGameWrapper._unknown_2b4 to expose team_score_array_6
- Retype DDGameWrapper._zeroed_054 to team_task_ptrs: [*mut u8; 13]
- Split DDGame._unknown_77ac to add _field_77b0
- Change all function signatures from *mut u8 to *mut DDGameWrapper/*mut DDGame
- Replace wa_malloc + write_bytes with wa_malloc_zeroed (~12 sites)
- Propagate typed pointers (DisplayGfx, DSSound, PCLandscape) to vtable calls
- Update DLL trampoline shims to cast to typed pointers
- Remove unused helper parameters (_ddgame_raw, _wrapper)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: type CoordEntry fields as Fixed and eliminate pointer arithmetic in level_boundsβ
Retype CoordEntry fields from raw i32 to named Fixed center_x/y and
center_x_target/y_target. Extend viewport_coords array to 5 entries to
cover the init spill. Retype init_game_state_level_bounds param from
*mut u8 to *mut DDGame, removing all raw pointer arithmetic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: propagate types and named fields in game_state_initβ
Split _unknown blocks in DDGame (28 new fields) and GameInfo (17 new
fields) to replace raw pointer arithmetic with named struct access.
Retype game_speed, level bounds, and render_scale to Fixed. Change
game_info_sub parameters from *const u8 to *mut GameInfo across all
init sub-functions, eliminating the untyped pointer pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: reorganize display/ and render/ into unified render/ moduleβ
Merge the display/ and render/ sibling modules into a single render/
module with display/, sprite/, and ddraw/ sub-modules. Pure mechanical
refactor β file moves and import rewrites only, zero logic changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: eliminate DDDisplay, make DisplayBase generic, rename DDDisplayWrapperβ
- Make DisplayBase<V: Vtable> generic over vtable type (like CTask<V>, BitGrid<V>)
- Eliminate DDDisplay wrapper type β DisplayGfx is now the direct this type
for all display vtable methods, removing constant as_gfx() casts
- DisplayGfx.base is now DisplayBase<*const DisplayVtable> (38-slot vtable)
- bind_DisplayVtable!(DisplayGfx, base.vtable) uses nested field path
- Rename DDDisplayVtable β DisplayVtable, dd_display.rs β display_vtable.rs
- Rename DDDisplayWrapper β RenderContext (manages framebuffer + dispatches
to renderer backends β the DD prefix was misleading since WA supports
multiple graphics backends), display_wrapper.rs β render_context.rs
- Update all address constants (DDISPLAY_* β DISPLAY_GFX_*, etc.)
- Rename 51 functions in Ghidra to match
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: rename TeamArenaState to TeamArena, use raw pointers for soundnessβ
BitGrid is a core engine type used for both rendering (display layers)
and simulation (collision masks, terrain geometry). Move it from
display::bitgrid to crate::bitgrid to reflect its fundamental role.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: typed BitGrid variants with alloc constructorsβ
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>
feat: port DDGame__LoadFonts and DDGameWrapper__LoadFontExtension to Rustβ
Replaces the usercall bridge to 0x570F30 with pure Rust that loads 28
bitmap fonts, 23 font extensions, and sets the font palette via typed
DDDisplay vtable calls. Adds three new DDDisplay vtable slots (load_font,
load_font_extension, set_font_palette) and expands gfx_color_table from
9 to 11 entries, absorbing former _unknown_7330 as font palette data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: port CTaskTeam__CreateWeatherFilter to Rust, extract wa_lcgβ
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>
chore: Add new DDGame & GameInfo fields based on game state init functions
Fully port play_worm_sound/play_worm_sound_2 β the streaming sound
playback chain no longer calls into WA.exe via naked asm bridges.
New Rust implementations (sound_ops.rs):
- distance_3d_attenuation: stereo panning with i64 fixed-point math
- compute_distance_params: reads listener pos + attenuation from DDGame
- record_active_sound: ring buffer insert into ActiveSoundTable
- dispatch_local_sound: 3D audio + play_sound_pooled + record
- load_and_play_streaming / load_and_play_streaming_positional
Removed: naked asm bridges (call_load_and_play_streaming,
call_load_and_play_streaming_positional, LOAD_AND_PLAY_STREAMING_ADDR).
Unify SoundId and SoundSlot into a single SoundId type β they were
the same packed u32 format (low 16 = slot index, bit 16 = loop,
bit 17 = raw volume). Flag accessors moved to SoundId.
Other changes:
- Add Fixed Div/Div<i32> operators
- Name DDGame+0x77B8 (level_width_sound), GameInfo+0xF38C
(sound_attenuation), DDGame listener_pos() accessor
Co-Authored-Byβ¦
Replace DDGame raw offset accesses with named struct fields and methods:
- render_slot_count (0x72E4), render_entries, render_state_flag
- game_info, game_state_stream, version_flag_3 (direct field access)
- team_sound_id() method for 0x7768+team*0xF0 sound table
- weapon_stat_counter() method for 0x40CC-0x40D8 indexed counters
- fire_params.spread for type-4 subtype-4 sound ID
Replace raw vtable dispatch for HandleMessage with typed
lookup_team_task() + CTaskTeam::handle_message() bind macro.
Make lookup_team_task pub(crate) for cross-module reuse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full Rust reimplementation of the weapon release orchestration function
that handles ammo sync, spawn offset calculation, network timing, weapon
stat counters, sound/visual effects, and delegates to FireWeapon.
New module weapon_release.rs with:
- usercall trampoline (EAX=worm, 4 stack params, RET 0x10)
- Pure Rust: sync check, spawn offsets, delay, network timing,
SharedData HandleMessage (0x49), 4 weapon stat counters,
3 weapon category classifiers (IsSuperWeapon, CategoryA/B)
- Rust-to-Rust PlaySoundLocal via new sound::play_sound_local() helper
- Bridges for PlayWormSound/StopWormSound/SpawnEffect (addr-as-param)
- Direct call to fire_weapon_impl (made pub(crate))
- DDGame::advance_effect_rng() for secondary RNG at +0x45F0
Also adds 16 named fields to CTaskWorm (landscape_scale, shot_data,
fire_sync_frames, difficulty_level, weapon_params, sound_handle, etc.),
fills all WormState enum gaps (0x69-0x6B, 0x6F, 0x76, 0x79-0x7A,
0x84-0x85, 0x8A), and removes from_raw_unchecked antβ¦
refactor: eliminate pointer arithmetic in weapon.rs β typed params throughoutβ
- Rename DDGame.hurry_flag β version_flag_4 (confirmed via Ghidra as game
logic version byte, not a hurry flag)
- Replace raw fire_params pointer math with struct field access
(collision_radius.0, _fp_04) with polymorphism comments
- Type local_struct as *const WeaponSpawnData in projectile_fire_impl,
use named fields (initial_speed_x/y) instead of array indexing
- Type all naked asm bridge params: *mut CTaskWorm, *const WeaponFireParams,
*const WeaponSpawnData, *const u8, *const () instead of u32
- Simplify fire_weapon_special: remove params_38 u32 arg, compute
params_38_ptr inside from entry pointer
- Type CTaskArrow/CTaskMissile ctor calls with proper pointer params
- Remove duplicate section header comments
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: fully type CTaskCloud handler β no raw offsetsβ
Replace all raw pointer arithmetic in CTaskCloud::HandleMessage with
typed DDGame fields. Render path now uses RenderQueue::alloc directly
instead of delegating to original WA function.
New/renamed DDGame fields:
- render_phase (+0x764C): rendering phase, clouds render when == 5
- parallax_scale (+0x8150): renamed from crosshair_scale, also used
for cloud parallax X offset computation
- level_bound_min_x/max_x: changed from i32 to Fixed type
Cloud handler improvements:
- Uses TaskMessage enum (FrameFinish, RenderScene, SetWind)
- Uses Fixed arithmetic with from_int(128) for padding
- Calls RenderQueue::alloc::<DrawSpriteCmd> directly for rendering
- No ORIG_HANDLE_MESSAGE needed β fully self-contained
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: split ddgame.rs (2240 lines) into 3 modulesβ
New openwa-derive crate with FieldRegistry proc macro that generates
HasFieldRegistry impls for #[repr(C)] structs. Uses offset_of!() and
size_of() for compiler-verified offsets. Fields prefixed _unknown/_pad
are automatically excluded.
Applied to 5 key structs: DDGame, DDGameWrapper, GameSession, CTask,
CTaskWorm. Each now has a field_registry() -> &'static StructFields
method for runtime offset-to-name lookups.
This lays groundwork for typed vtable derive macros using the same
proc macro infrastructure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: port Surrender and Mail/Mine/Mole weapon handlers to pure Rustβ
Port two FireWeapon sub-handlers from WA bridge calls to pure Rust:
- Surrender (0x51E600): SharedData lookup + HandleMessage(0x29) + WormEntry counter
- Mail/Mine/Mole (0x51E670): version-conditional vtable call + HandleMessage(0x28) + counter
Fix replay loader V2 scheme bug: our code was clearing super weapon data
(memset 0x4C) for both V1 and V2 schemes when header < 0, but the original
WA only clears for V1. V2 schemes already contain valid super weapon data.
Other changes:
- Name WormEntry fields: effect_counter_04_Maybe (+0x04), turn_action_counter_Maybe (+0x08)
- Add TeamArenaRef::team_worm_mut() for mutable worm entry access
- Add mail_mine_mole replay test (Mail Strike, Mine Strike, Mole Squadron, Surrender)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three constructor bugs found and fixed:
1. Allocation size 0x98B8 β 0x98D8: original malloc(0x98D8) but memset(0x98B8).
Our code allocated only 0x98B8, causing 32-byte heap overrun.
2. Wrong function after InitPaletteGradientSprites: called LoadingProgressTick
(0x5717A0) instead of DisplayGfx__InitTeamPaletteDisplayObjects (0x5703E0).
3. Arrow collision region SpriteRegion params were all wrong:
- ECX should be sprite_w/2 (was 0)
- EDX should be sprite_h - margin_h (was margin_h)
- p2/p3 should be margin values (were 0)
- p4 should be sprite_w - margin_w (was margin_w)
- p5 should be sprite_h/2 (was margin_h)
- p6 should be arrow sprite ptr (was landscape gfx_resource)
This was the root cause of the longbow replay desync β wrong collision
boxes caused different terrain collision detection and RNG divergence.
Found via sub-object hashing: comparing pointer-canonicalized hashes of
objects pointed to by DDGame between Rust and original constructors.
Co-β¦
Constructor correctness (match original WA behavior):
- Load debris.spr and back.spr unconditionally, not gfx_mode-gated
- Initialize PaletteContext for masks.img (word=1, word=0xFE, Init)
- Document PCLandscape param_5 as game_info+0xDAAC
Debug tooling improvements:
- Hardware watchpoints: EBP-based stack trace on every hit
- Watchpoint base selection: OPENWA_WATCH_WRAPPER, OPENWA_WATCH_DISPLAY
- Display watchpoint arming during constructor for field tracing
- Snapshot: walk parent chain to CTaskTurnGame (true entity root)
Add docs/re-notes/desync-investigation.md β writeup of the longbow
desync investigation methodology and findings.
NOTE: Longbow desync (4 checksum mismatches) is NOT yet resolved.
These changes are correctness fixes and tooling for continued work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: pass ESI (layer_ctx) to LoadSprites usercall in DDGame constructorβ
GfxHandler__LoadSprites (0x570B50) is a usercall that reads ESI as the
display layer context. Our wa_load_sprites wrapper was declared as plain
stdcall, leaving ESI unset. This caused LoadSpriteFromVfs and
GfxResource__Create_Maybe to operate with garbage ESI, producing wrong
colour values (0xFFFFFFFF instead of palette indices 1-9).
Fixed by converting wa_load_sprites to a naked asm bridge that sets ESI
from a 5th parameter (layer_ctx) before calling the original function.
The gfx_color_table now matches the original constructor output.
Checksum mismatches persist β likely caused by other uncanonicalized
pointer-as-u32 fields in DDGame (like landscape_property). The snapshot
system confirms all gameplay state is identical.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: canonicalize landscape_property as pointer, remove arrow dumpβ
landscape_property (DDGame+0x468) is a heap address stored as u32.
Show as <ptr>/null instead of raw value to avoid false diff positives.
Removed arrow_sprites/arrow_gfxdirs from snapshot β confirmed identical
between constructors, not involved in the desync.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: add arrow_sprites to DDGame snapshot, narrow desync furtherβ
Added arrow_sprites/arrow_gfxdirs null/ptr pattern and raw hex dump
to snapshot output. Multi-frame comparison (frames 1, 1350, 1351)
confirms:
- Arrow arrays are IDENTICAL between constructors (no diff)
- Only 2 DDGame field diffs exist, both static from construction:
1. landscape_property: misclassified pointer (heap addr as u32)
2. gfx_color_table: [1..9] vs [0xFFFFFFFF x 9]
- All gameplay state identical: entities, positions, health, ammo, RNG
- Diffs are the same at frames 1, 1350, and 1351 (no drift over time)
Root cause remains: headless LoadSprites fallback in create_ddgame
doesn't correctly populate GfxDir colour data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New `snapshot` command captures canonicalized game state for diffing:
- DDGame: frame counter, RNG, game speed, camera, team arena, ammo
- Teams: name, alliance, worm count, per-worm state/hp/name
- Entities: BFS walk of task tree, rich dump for CTaskWorm/CTaskMissile,
raw hex with pointer canonicalization for unknown entity types
Snapshot trait implemented for DDGame, WormEntry, CTaskWorm, CTaskMissile.
Unknown regions dumped as raw hex with [ptr---] replacing heap pointers.
Usage: openwa-debug snapshot > /tmp/state.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: add missing LoadSprites fallback in DDGame constructor for gfx_mode=0β
The original DDGame constructor calls GfxHandler__LoadSprites (0x570B50)
when gfx_mode=0 (headless) or when GfxResource__Create fails. Our Rust
replacement skipped this call entirely, leaving DDGame+0x730C (color
table) unpopulated. This caused checksum mismatches in the longbow
replay.
Added the missing call with correct parameters (primary_gfx_dir as 4th
param for gfx_mode=0, 0 for resource creation failure). The color table
is still not fully matching vanilla (0xFFFFFFFF vs 1-9) β further
investigation needed in LoadSprites or CTaskGameState initialization.
Also: cleaned up bisect debug code, removed unused call_original
bridge functions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: add TeamArenaRef::from_ptr for typed constructionβ
Add from_ptr(*mut TeamArenaState) alongside the existing from_raw(u32)
used by trampolines. fire_skip_go now constructs TeamArenaRef directly
from the typed DDGame.team_arena field pointer β no u32 cast.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: use *mut u8 in TeamArenaRef, add team_header_mutβ
TeamArenaRef.base was *const u8 but team_header_mut() cast it to
*mut, which is unsound. Change base to *mut u8 so all mutable
access goes through legitimate mutable pointers.
blocks() now returns *mut TeamBlock. team_header_mut() no longer
needs a const-to-mut cast.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: name TeamHeader.turn_action_flags, clean up fire_skip_goβ
Identify TeamHeader._unknown_7c as turn_action_flags (u32 bitfield).
Skip Go toggles a bit here to mark the team's turn as skipped.
In game_version > 0x1C the bit toggles; in older versions it only sets.
fire_skip_go now accesses the field via TeamArenaRef::team_header()
instead of raw byte slicing into team_blocks_region.
Verified: headless PASS (byte-identical).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the opaque _unknown_4620 blob with a proper team_arena field
at DDGame+0x4628. The previously separate init_field_64d8 and
init_field_72a4 were actually team_arena.team_count and
team_arena.weapon_slots[754] respectively β now accessed correctly.
fire_skip_go uses ddgame.team_arena._teams_region directly instead
of raw pointer arithmetic through offsets::TEAM_ARENA_STATE.
Verified: headless PASS (byte-identical).
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>
refactor: type check_weapon_avail, add CoordList struct, name DDGame/GameInfo fieldsβ
Add three new typed structs replacing raw pointer arithmetic:
- TeamIndexMap (0x64 bytes): entries[i16;16] + count + gap + terminator.
Three instances at DDGame+0x7650 replace 6 fragmented byte arrays.
- RenderEntry (0x14 bytes): active flag + unknown data.
14 entries at DDGame+0x73B0 replace opaque _unknown_73b0 blob.
- NetBridge (0x2C bytes): ddgame back-pointer + net_config bytes.
Created for online games only, replaces ddgame_secondary raw pointer.
ddgame_init_render_indices now takes *mut DDGame and uses pure field
access. DDGameWrapper.ddgame_secondary renamed to net_bridge with
typed *mut NetBridge pointer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: name DDGame fields, add CoordEntry struct, unify gfx_color_tableβ
Add wa_malloc_zeroed() utility that combines wa_malloc + write_bytes.
Replace 10 malloc+zero pairs in DDGame constructor with single calls.
Type landscape temp buffers as [u32] arrays instead of [u8], enabling
direct indexing for coordinate data. Update wa_pc_landscape_ctor
signature to match (*mut u32 for output params). Use &raw mut for
obtaining raw pointers to DDGame fields.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: use stack arrays for temporary landscape buffers in DDGame ctorβ
Replace wa_malloc/wa_free with stack arrays for three temporary buffers
(landscape_coords_buf, landscape_byte_buf, landscape_temp) that were
originally stack locals in the C++ code. Rename stack_local_8 to
landscape_temp for clarity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add debug_watchpoint.rs β a self-contained x86 debug register
instrumentation module that sets DR0βDR3 write watchpoints on DDGame
offsets via an INT3βVEH trick (no external debugger needed). Logs the
exact Ghidra VA of every write to watched offsets.
Used the watchpoint debugger to investigate 5 DDGame constructor field
differences. Found:
- +0x041C/+0x0420: written by DDGame__InitPaletteGradientSprites
(0x5706D0) which our Rust constructor was missing. Fixed by adding
a stdcall bridge call after LoadHudAndWeaponSprites.
- +0x7820/+0x7824/+0x7828: stale observations β both constructors
now produce identical speech slot table values.
Also removes Shadow comparison code and the original DDGame constructor
bridge (ddgame_constructor_call) since the Rust constructor is fully
matched.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: use *const c_char for C string parametersβ
The high-level sprite lookup helper now accepts &CStr directly,
moving the pointer cast inside the function. Callers pass c"name"
without .as_ptr().cast().
Lower-level functions (gfx_dir_find_entry, gfx_resource_create,
DDDisplay vtable methods) keep *const u8 since they're called from
FFI hooks or thin vtable wrappers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: use c"..." CStr literals instead of b"...\0" binary stringsβ
Replace manual null-terminated byte strings with Rust's C string
literals for compile-time null termination guarantee:
- b"something\0".as_ptr() β c"something".as_ptr().cast()
- b"something\0" in arrays β c"something".to_bytes_with_nul()
Kept b"..." for two strings mutated at runtime (gfx_c_path, name_buf).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: palette init used DDDisplay pointer arithmetic (sizeof scaling)β
disp.add(0x3120) with disp: *mut DDDisplay addressed 0x3120 * 4 bytes
(sizeof DDDisplay = 4) instead of 0x3120 bytes. Cast to *mut u8 for
raw byte offsets into the DisplayBase region.
Bug introduced in DDDisplayVtable commit when disp type changed from
*mut u8 to *mut DDDisplay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>