Crusader: No Remorse — NE Segment 1 Game Logic
This file covers the standalone analysis of NE Segment 1 (seg001_code_off_37600_len_8400.bin), imported as a raw binary at base 0x0000, language x86:LE:16:Protected Mode. All 35+ identified functions have been renamed and annotated in Ghidra.
Cursor Subsystem (0x0060–0x0d5f)
| Address |
Name |
Description |
0x0060 |
cursor_update_hover |
Hover update: if mouse active & entity set, calls cursor_set_target |
0x00e9 |
cursor_set_target |
Positions cursor on entity, updates sprite + direction visual |
0x0322 |
cursor_shutdown |
Frees cursor resources, resets state |
0x0398 |
cursor_animation_update |
Angle-based cursor rotation (0x27d4, 0-359 → 0x168=360). Sprite at 0x27d6 |
0x050f |
cursor_draw_tick |
Per-frame cursor draw (calls cursor_animation_update if dirty) |
0x0c24 |
action_key_valid |
Returns 1 if action code (param_1) is a valid game action key |
0x0d5f |
cursor_direction_input |
Arrow-key input: rotates cursor angle, updates direction sprite |
Input Handling
| Address |
Name |
Description |
0x0526 |
input_keyboard_handler |
Key dispatch: 0x01=LMB, 0x0D/0x0C=scroll, 0x2C=save, 0x44=load |
Cursor State Data (at DS:0x27xx)
| Address |
Field |
Meaning |
0x27c4 |
cursor_sel1 |
Selection counter 1 |
0x27c6 |
cursor_sel2 |
Selection counter 2 |
0x27c8 |
current_entity |
Handle to currently targeted entity |
0x27ca–0x27ce |
cursor_state |
Cursor interaction state bytes |
0x27d0 |
cursor_entity_type |
Current entity type index |
0x27d2 |
z_offset |
Z-height offset for terrain adjustment |
0x27d4 |
cursor_angle |
Rotation angle (0–359) |
0x27d6 |
cursor_sprite |
Sprite handle for cursor visual |
0x27d8 |
cursor_dirty |
Set when cursor needs redraw |
0x27d9 |
cursor_active |
Master cursor enabled flag |
0x27da |
cursor_no_turn |
Flag disabling cursor rotation |
0x27ed |
difficulty |
Enemy accuracy divisor (used in projectile_init_vector) |
0x27fd |
hard_mode |
Two-step mode (combat vs. explore) |
0x27fe |
move_mode |
Movement phase flag |
0x27ff |
mouse_active |
Mouse/input system active |
0x2800–0x2811 |
various |
UI state: active sprite, facing byte, cur entity handle |
0x283f/0x2841 |
menu_obj_ptr |
Active menu/dialog object far pointer |
0x2844 |
in_save |
In-progress save game flag |
0x290e |
entity_count |
Number of active entities |
0x2910–0x2947 |
snap_type_ids[10] |
Entity types that snap-to-ground in snap_entity_to_ground |
Input / Action Dispatch
| Address |
Name |
Description |
0x2420 |
entity_command_dispatch |
Dispatches player commands to target entity; reads 0x27d0, 0x2de4, sends events 0x14/0xf, handles state machine 0x27ca/0x27cd/0x27ce |
0x279a |
cheat_code_check |
Checks input record byte+1 against five bytes starting at 0x2833; toggles 0x844/0x6045, emits helper/event code 0x103 |
| Address |
Name |
Description |
0x2e53 |
cursor_event_notify_a |
Vtable thunk: forwards event to 0x27ca area handler |
0x2e96 |
cursor_event_notify_b |
Vtable thunk: forwards event to 0x27ca area handler (alt path) |
0x2ed9 |
menu_event_notify_a |
Vtable thunk: forwards event to 0x2843 (near menu object) |
0x2f0c |
menu_event_notify_b |
Vtable thunk: forwards event to 0x2843 (alt path) |
0x2ff3 |
stub_noop_2ff3 |
Empty stub, noop |
0x2ff8 |
entity_collision_callback_a |
Calls touch handler then func(entity+0x1e, seg, 2); opt: extra func if param_3&1 |
0x3046 |
set_active_menu |
Writes param_1/param_2 to 0x283f/0x2841 (active menu far pointer) |
0x3058 |
entity_collision_callback_b |
Same as entity_collision_callback_a (second vtable entry) |
Entity System (0x2401–0x5a50)
| Address |
Name |
Description |
0x2401 |
clear_cursor_selection |
Zeros 0x27c4/0x27c6 (selection counters) |
0x2899 |
cursor_switch_target_entity |
Switches cursor target: unloads old entity, loads new, re-registers |
0x29d8 |
get_z_offset |
Returns func() + *(0x27d2) = adjusted Z/height |
0x2a09 |
is_player_in_range |
Checks if entity is at player (0x2de4) X/Y +/-0xf0 range |
0x2a46 |
entity_ai_update_loop |
Loops entities 2–255, checks visibility, triggers fire/move |
0x2c36 |
ui_update_callback |
Calls cursor_state_clear then vtable[2] on menu object |
0x2c6b |
cursor_state_clear |
Clears cursor state bytes 0x27ca–0x27ce, clears entity flag bit1 |
0x2c92 |
dialog_spawn |
Allocates dialog object, vtable=0x28b5, registers callback at 0x39ca |
0x2d47 |
entity_pick_handler |
Handles entity selection or save-game trigger (type 0x38d) |
0x2df9 |
clear_active_menu |
Zeros 0x283f/0x2841 (active menu far pointer) |
0x2e18 |
game_mode_init |
Initializes game mode state, resets sprite/cursor/menu state |
0x2f3f |
entity_table_set_sprite |
Reads 0x7df9+slot2; writes entity type table 0x7e1e[slot0x79+0x0d]=param_2, +0x10=0 |
0x3c97 |
snap_entity_to_ground |
If entity type in snap_type_ids[10], resets Z to 0xf0 and adjusts XY |
0x3d6e |
spawn_entity_checked |
Spawns entity with explosion pool limit check (0x84c0, 0x84c2) |
0x3f2f |
entity_spawn |
Allocates entity, vtable=0x29aa/0x39ca, positions it |
0x40d4 |
entity_remove |
Removes entity: destroys sprites, clears 0x2802/0x2804 if needed |
0x4172 |
entity_animation_frame_update |
Advances/retreats anim frame ([+0x1d]) toward target [+0x1c/0x1b] based on quality |
0x42f8 |
stub_noop_42f8 |
Empty stub, noop |
0x42fd |
entity_registry_decrement |
Calls cleanup func then decrements entity count at 0x290e |
0x4314 |
entity_sprite_move_delta |
Updates shot sprite handle (entity+0x3f) position by adding delta params |
0x4552 |
entity_set_position |
Sets entity+0x3e (type_handle), world_x/y (entity+0x45/47), base_x/y (entity+0x4f/51) |
0x452b |
shot_set_spawn_pos |
Calls entity_set_position then sets entity+0xbe = param_3 (extra spawn field) |
0x4591 |
entity_try_place |
entity_set_position with validation — position only set if placement succeeds |
0x5092 |
entity_deactivate |
Calls vtable[2] to deactivate, or finds in registry and removes |
0x5a50 |
entity_list_contains |
Checks if entity ptr exists in active entity list at 0x294c |
0x5b05 |
stub_noop_5b05 |
Empty stub, noop |
Entity Object Layout (NE Segment 1 entities)
| Offset |
Field |
Meaning |
+0x00 |
vtable_ptr |
Vtable pointer (0x29aa for generic, 0x2a57 for debris) |
+0x02 |
slot_index |
Entity slot index (used for registry at 0x39ca) |
+0x04 |
entity_type |
Entity type ID |
+0x19/+0x1a |
flags |
Entity flags (bit0=debris, bit1=cleared by cursor_state_clear, bit6=active, bit8=valid) |
+0x1b |
vel_x |
X velocity (clamped ±0x20) |
+0x1c |
vel_y |
Y velocity (clamped ±0x20) |
+0x1d |
vel_z |
Z velocity (clamped ±0x10) |
+0x1e |
fire_handle |
Weapon/fire handle |
+0x1f |
is_enemy |
1 if entity is an enemy type |
+0x20/+0x21 |
pos_frac_x/y |
Fractional position (sub-tile) for movement |
+0x22 |
pos_frac_z |
Fractional Z |
+0x36 |
weapon_type |
Active weapon type ID |
+0x38 |
facing |
Current facing direction (0–15) |
+0x3c |
sprite_handle |
Sprite for this entity |
+0x3f |
shot_sprite |
Sprite handle for active projectile (0xFFFF = none) |
+0x45/+0x47/+0x49 |
world_x/y/z |
Current world position (integer) |
+0x4f/+0x51/+0x53 |
base_x/y/z |
Base/spawn position |
+0x54/+0x56/+0x58 |
prev_x/y/z |
Previous frame position |
+0x59 |
attack_active |
Attack in progress flag |
+0x5a |
at_target |
Reached target flag |
+0x5e–+0x65 |
delta_x/y/z/high |
Per-step movement deltas (fixed point) |
+0x66/+0x68 |
step_active |
Stepping active (1=yes, 0=off) |
+0x6a/+0x6c |
weapon_slot/dist |
Weapon slot and total travel distance |
+0x6e |
delta_z |
Alt Z delta |
+0x70 |
projectile_type |
Projectile class (2/0xD=splash, 3=spread, 5=homing, 0xE=chain) |
+0x72/+0x74/+0x76 |
target_x/y/z |
Target position with deviation |
+0x77 |
target_entity |
Target entity handle |
+0x79 |
secondary_pos |
Secondary position struct pointer |
+0xad |
owner_entity |
Owning entity handle |
+0xaf |
shot_owner_flags |
Shot owner (entity/player) |
+0xb1 |
bounce_count |
Bounce counter (used with homing, type 5) |
+0xb3 |
has_bounce |
Has bounce trajectory active |
+0xbd |
actor_type |
Actor type byte (used for direction table lookups) |
Shot Entity Lifecycle (0x435e–0x44a9)
| Address |
Name |
Description |
0x435e |
shot_entity_alloc |
Alloc/init shot entity: vtable=0x297e, registry vtable=0x2969, zeros all state, copies player pos to +0xb5/b7 |
0x44a9 |
shot_entity_free |
Cleans up shot entity: frees sprite at +0x3c if valid (set to 0xFFFF), clears callbacks; optional full free if flag&1 |
Projectile / Combat (0x4659–0x5a99)
| Address |
Name |
Description |
0x4659 |
projectile_init_vector |
Sets up shot trajectory: target XY±deviation, step rate from weapon table at 0x2536 |
0x4a91 |
entity_fire_weapon |
Fires weapon from entity using 0x129b/0x12ac direction offset tables |
0x4b18 |
fire_weapon_from_cursor |
Gets cursor angle sprites, fires projectile at cursor target |
0x4b78 |
projectile_check_hit |
Hit test: if entity_type==0 uses bbox+0x79; else full 3D range; copies +0xa0→+0x77 (hit entity) |
0x4c2e |
projectile_step_update |
Advances projectile one step; type 3 spawns sub-shots via spawn_entity_checked |
0x4d28 |
projectile_trace_ray |
Interpolated path trace: divides distance/0x10 into steps, collision checks each step; on hit calls projectile_apply_hit + entity_deactivate |
0x51ad |
projectile_update_tick |
Full projectile tick: move, check reach target, bounce, call projectile_check_hit |
0x5a99 |
projectile_apply_hit |
Applies hit effects: if impacted obj byte+6 non-zero, calls damage func with weapon_slot/type/target/owner |
Weapon Type Table (0x2536)
- Each entry is 0x11 bytes (17), accessed as
weapon_type * 0x11
[0] = step divisor for distance calculation
[0x19] = max range threshold (used in projectile_update_tick)
Direction Tables (0x129b / 0x12ac)
- Indexed by facing (0–15): dx offsets at 0x129b, dy offsets at 0x12ac
- Values are multiplied by distance (e.g.
*0x500) for projectile spawn offsets
Collision Detection (0x60c1–0x621e)
| Address |
Name |
Description |
0x60c1 |
aabb_overlaps_3d |
3D AABB overlap test — box layout [xmin,ymin,zmin,,,xmax,,ymax,,zmax] |
0x621e |
bbox_translate |
Translates a 3D bounding box by (dx, dy, dz) — both min and max points |
Enemy AI / Spawning (0x6aed–0x6d21)
| Address |
Name |
Description |
0x6aed |
map_find_spawn_point |
Finds map tile matching entity conditions; returns packed XYZ tile coords |
0x6bfc |
actor_find_in_view |
Finds actor visible in current view frustum (temp data at 0x7eca) |
0x6ce9 |
enemy_spawn_with_target |
Wrapper: spawns enemy with player as target (param5=1) |
0x6d05 |
enemy_spawn_no_target |
Wrapper: spawns enemy without targeting player (param5=0) |
0x6d21 |
enemy_spawn_at_position |
Full enemy spawn: activates entity, assigns velocity from direction table (0x2a00/4/A) |
Player / HUD
| Address |
Name |
Description |
0x50ee |
player_position_update |
Updates player position from direction data; clamps to screen bounds |
0x6ff7 |
player_health_update_and_effect |
Encodes player HP into RGB bitfields at 0x7e46+0x1bec, spawns effect |
Destruction / Death (0x7490–0x75ff)
| Address |
Name |
Description |
0x7490 |
debris_spawn |
Spawns debris/fragment: vtable=0x2a57/0x2a1a, velocity, facing, linked list |
0x75ff |
entity_die |
Death handler: spawns 1–4 debris objects, picks best explosion direction |
Entity Type Constants (weapon_type/entity class)
| Value |
Entity Class |
0x17 |
Robot/mech type A |
0x18 |
Robot/mech type B |
0x1 through 0x3c |
Various entity/weapon types |
0x3d |
Robot/mech type C |
0x3e |
Robot/mech type D |
0x2f5–0x2f7 |
Special movement entity |
0x595/0x597 |
Platform/elevator entities |
0x31c/0x322–0x327 |
Explosive/effect entities |
0x38d |
Save game trigger entity |
0x426 |
Spark/scatter sub-shot |
0x59a |
Player cursor/select indicator |
Entity Data Table at 0x7e1e
- Stride:
0x79 bytes (121 bytes per entry)
- Indexed by entity type (integer) or entity slot
+0x59 offset = class-detail flags byte (entity_class_get_flag8 returns bit 0x08; other callers also clear bit 0x10 here during at-target facing updates)
+0x5a offset = flags byte (bit4 = special entity flag, bit3 = armor/shield flag)
+0x68 = targeting flag
Map / Resource Tables
| Address |
Content |
0x2833 |
Cheat code input sequence (null-terminated) |
0x283d |
Cheat sequence match position counter |
0x7ded |
Map X coordinate array (2 bytes per entry) |
0x7df1 |
Map Y coordinate array (2 bytes per entry) |
0x7df5 |
Map Z array (1 byte per entry) |
0x7df9 |
Entity state array (2 bytes per slot) |
0x7e46 |
Player state block far pointer |
0x7e1e |
Entity type table (stride 0x79) |
Entity Vtable Index (NE Segment 1)
| Address |
Entity Class |
0x28b5 |
Dialog/menu object vtable |
0x287b |
Cheat-spawned entity (cheat ON) vtable |
0x2892 |
Cheat-spawned entity (cheat OFF) vtable |
0x2969 |
Entity registry vtable (stored at 0x39ca+slot*4, not entity's own vtable) |
0x297e |
Shot/projectile entity vtable |
0x29aa |
Generic/AI entity vtable |
0x2a1a |
Corpse entity vtable (variant) |
0x2a33 |
Actor/corpse entity vtable |
0x2a57 |
Debris fragment entity vtable |
Cheat System Analysis
cheat_code_check internals
- Direct raw-EXE recovery:
cheat_code_check in CRUSADER-RAW.EXE is 0007:0d0a-0007:0e08.
- It has exactly one direct caller in this build:
FUN_0007_04dc at 0007:0511, which prepares a small local event record and then calls cheat_code_check before continuing normal input dispatch.
FUN_0007_04dc itself is not only a low keyboard-queue consumer: drawlist_init at 0007:f654 calls it directly during higher-level setup, so the cheat-dispatch body can be reached from at least one non-ISR path in this build.
Variable and constant roles from the recovered body:
0x2833 is not a clean dedicated data object in this raw EXE — raw bytes and surrounding disassembly show that it lands in the middle of entity_animation_frame_update (0007:26e2-0007:2867). Starting on PUSH AX; CMP byte ptr [0x27fd],0; ... the first five bytes are 50 80 3e fd 27 followed by a 0x00.
0x283d is the current match index into that byte table. On a successful byte match it increments; on mismatch it resets to zero and immediately retries the current input byte against the first table byte (overlapping prefixes still work).
input_event_record[+1] is the compared input/event token.
0x844 is the main cheat-enable flag. The success path toggles it by converting the current byte to a boolean and negating it (0 -> 1, non-zero -> 0).
0x6045 is written with the same post-toggle value as 0x844 — a mirrored cheat-state latch.
- Constant
0x103 is pushed into the shared helper at 000a:5276 immediately after the toggle (emit the cheat-toggle side effect).
0x8c52 is forced to 1 on success before the side-effect path continues.
After a full table match, the code resets 0x283d to zero, sets 0x8c52 = 1, toggles 0x844 and 0x6045, and calls the shared 0x103 helper. It then branches on the new cheat state: cheats-on uses DS:0x287b; cheats-off uses DS:0x2892.
Cheat-enable sources
Two independent cheat-enable sources verified:
- The hidden input matcher in
cheat_code_check toggles 0x844 and 0x6045 after matching the five-byte event-code table at 0x2833.
- The command-line parser at
0004:635c-0004:63b8 recognizes the literal switch -laurie and directly sets 0x844 = 1. This path does not write 0x6045.
Current model for the two cheat bytes:
0x844 = master cheat-permitted / cheat-framework-enabled flag.
0x6045 = live cheat-active mirror latch used by low-level keyboard handling.
- The hidden five-byte matcher enables both at once, but
-laurie only enables the master flag.
- The separate event-
0x7e path at 000c:942d requires 0x844 != 0, flips 0x6045, and displays one of two local notification messages (0x6087 vs 0x6091).
jassica16 status:
- No literal
jassica string is present in the current string table, while -laurie is present as plain text.
- The ordinary keyboard ISR producer still does not support the old byte-for-character model cleanly: it feeds normalized scan-code-style values into record byte
+1, while the matcher source at 0x2833 is a live code-byte sequence with two values (0x80, 0xfd) that do not fit that ISR path.
- The direct call
drawlist_init -> FUN_0007_04dc is the first concrete static evidence for a higher-level path in this build.
F10 key behavior (verified in raw build):
seg001_input_keyboard_handler at 0006:ec29 handles input byte 0x44 and immediately returns unless cheats are enabled through 0x6045.
- "Plain F10 when cheats are enabled" is verified; "Ctrl+F10 enables god mode" is not supported by the current code path.
- When a current
0x7e22 entity exists, the branch resolves the current selection and refreshes per-entity bookkeeping.
- In the
local_4 == 1 case the branch becomes a large restore/reset routine that tears down and rebuilds multiple linked objects around 0x7e22, retries dispatch up to 0x14 times per stage, and fires the event batch 0x33d, 0x33f, 0x340, 0x341, 0x33e before re-enabling channels 4, 1, and 0.
Cheat-related string table (seg014 / 000e:xxxx)
| Address |
String |
Notes |
000e:9c5e |
"FART ...TRY... -laurie (Have fun, Jely)" |
Dev Easter-egg comment; no static code xref |
000e:9c87 |
"CHEATS ON" |
Cheat-on status string |
000e:9c91 |
"CHEATS OFF" |
Cheat-off status string |
000e:9c9c |
"TARGETING RETICLE ACTIVE." |
Correlates to event 0x441 / byte 0xee0 toggle |
000e:9cb6 |
"TARGETING RETICLE INACTIVE." |
Paired off-state |
000e:9cd2 |
"CD TRANSFER DISPLAY ACTIVE." |
Correlates to event 0x241 / 0x141 toggle area |
000e:9cee |
"CD TRANSFER DISPLAY INACTIVE." |
Paired off-state |
000e:9dff |
"HACK MOVER ON" |
No static code xref; USECODE/scripting layer |
000e:9e0d |
"HACK MOVER OFF" |
No static code xref; USECODE/scripting layer |
000e:6450 |
"Immortality disabled." |
No static code xref; USECODE/scripting layer |
000e:6466 |
"Immortality enabled." |
No static code xref; USECODE/scripting layer |
000e:647b |
"Cheats are now active." |
Shown in -laurie startup path |
000e:6492 |
"Cheats are now inactive." |
Paired off-state |
Cheat event dispatch summary (000c segment)
All cheat-related event case-handlers reside as shared-frame case bodies within a large event dispatch function in segment 000c. Each body inherits BP from the enclosing prologue and exits via POP DI; POP SI; LEAVE; RETF.
| Address |
Symbol |
Event |
Action |
000c:8e16 |
event_0x441_cheat_debug_overlay_toggle |
0x441 |
Toggles DS:0xee0 (boolean-NOT); calls [0x2bd8] vtable +0x2c; gate = DS:0x844 |
000c:8e46 |
event_0x241_cheat_debug_overlay_toggle |
0x241 |
Toggles DS:0x2bc9 (1-current); same vtable dispatch; gate = DS:0x844 |
000c:8e72 |
event_0x141_cheat_debug_overlay_toggle |
0x141 |
Toggles DS:0x2bca (1-current); same vtable dispatch; gate = DS:0x844 |
000c:942d |
event_0x7e_cheat_latch_runtime_toggle |
0x7e |
Requires 0x844 != 0; flips live latch DS:0x6045; notification at DS:0x6087 (on) or DS:0x6091 (off) |
000c:9154 |
event_0x142_cheat_fullscreen_mode1_refresh |
0x142 |
Gate = DS:0x604b; palette-black, seg126 shell, mode-1 000c:3c0e, tail 0004:70f1 |
000c:92cd |
event_0x143_cheat_fullscreen_mode0_refresh |
0x143 |
Same as 0x142 but mode-0 000c:3c0e, tail 0004:6f15 |
000c:9703 |
event_0x410_cheat_flag_604f_toggle |
0x410 |
Toggles DS:0x604f (boolean-NOT); notification at DS:0x60d2 (on) or DS:0x60ee (off); gate = DS:0x844 |
Cheat-dispatch keyboard functions (seg007)
| Address |
Name |
Description |
0007:04dc |
keyboard_input_cheat_dispatch |
Processes one keyboard event: calls cheat_code_check, then dispatches on raw scan-code [record+1]. Tab/J (0x0f/0x24) → context-sensitive entity action via FUN_0005_e119/252; KP* (0x37) → cheat_entity_slot_cycle_and_update_sprite; Space (0x39) → movement/entity_command_dispatch; KP- (0x4a) → cheat_anim_type_cycle_and_refresh; KP+/KP0/KPDel (0x4e/0x52/0x53) → selected object vtable +0x18(0xb,...) dispatch. ASCII H (0x48) absent; HACK MOVER comes from a higher scripting layer. |
0007:0d0a |
cheat_code_check |
Five-byte stateful matcher at DS:0x2833; toggles 0x844+0x6045; cheats-on notification via display_null_check_dispatch(..., 0x287b); cheats-off via display_null_check_dispatch(..., 0x2892). |
Cheat-dispatch helpers (000c:81xx)
| Address |
Name |
Description |
000c:8072 |
cheat_entity_slot_cycle_and_update_sprite |
Cycles slot 1..5 for 0x7e22 entity; picks sprite ID by class flags; calls entity_table_set_sprite. |
000c:81c0 |
cheat_anim_type_cycle_and_refresh |
Cycles animation-type 0x0b..0x19 for 0x7e22; writes per-entity +0x19; calls 0008:4bba(0x20). |
000c:8221 |
cheat_flag_6050_clear |
Clears DS:0x6050 to 0. |
000c:8227 |
cheat_flag_6050_read |
Returns DS:0x6050 in AL. |
000c:822b |
cheat_flag_6050_set |
Sets DS:0x6050 to 1. |
Additional cheat-dispatch hotkeys in keyboard_input_cheat_dispatch
Verified byte tests in the caller-side dispatch:
0x37 calls 000c:8072 (cheat_entity_slot_cycle_and_update_sprite)
0x4a calls 000c:81c0 (cheat_anim_type_cycle_and_refresh)
0x0f and 0x24 share a context-sensitive branch via FUN_0005_e252 with event IDs 0x3a, 0x38, or 0x0b
0x39 and 0x52 share a branch computing a queued delta via entity_command_dispatch
0x4e and 0x53 are separate guarded selected-object lanes dispatching through the selected object's method table
Immortality mechanics (event 0x410 / flag 0x604f)
How immortality works at the C level:
DS:0x604f is the Immortality flag. It is toggled by event_0x410_cheat_flag_604f_toggle at 000c:9703.
The sole gameplay read site is player_receive_damage_and_dispatch_effects (0004:c055) at 0004:c205.
When 0x604f != 0 (Immortality ON), the damage path in 0004:c205 does:
CALLF 0009:9ea1 — begin hit-effect lock (animation gating sequence)
CALLF 0003:c368(0x10001) — arm anim-stagger mode (seg001:4d68 path)
IDIV 0x40000 — divide the 32-bit incoming damage value by 262,144 → result is effectively 0 for any realistic HP scale
- Apply the negligible reduced damage via
CALLF 0003:dbcc
- Spin on
DS:0x31a2 != 0 event-break gate before re-enabling channels
When 0x604f == 0 (Immortality OFF, normal path):
- Jump to
0004:c25b → CALLF 0003:ac7e (seg001:367e) — full damage / death dispatch
The hit stagger still plays in immortality mode (the Silencer visually flinches). Technically HP decreases by 0 per hit (integer truncation from /262144), so there is no true invulnerability flag that bypasses all HP accounting, just extreme attenuation.
What sends event 0x410 to toggle it:
The 000c event handler at 000c:9703 is entered via the large cheat-event dispatch switch at 000c:8c56-000c:8d16. That switch is driven by the seg021 event scheduler, not by the static keyboard dispatch in keyboard_input_cheat_dispatch.
Key negative result: no function in the compiled C code directly pushes the value 0x410 into the game's event broadcast path. All three occurrences of the immediate 0x410 in the disassembly are: (a) the CMP BX,0x410 comparison inside the 000c switch, (b) a multi-event subscription list at 000b:b5cb (registering to receive the event), and (c) an abort-function error code at 000d:5290 unrelated to the cheat.
Conclusion: event 0x410 is generated exclusively by the interpreted USECODE lane (centered on EUSECODE.FLX), not by any static keyboard-level scan-code path in the compiled binary. The F10 keyboard branch in seg001_input_keyboard_handler is a separate 0x44 path gated by 0x6045, not by 0x410. Separate follow-up work on the imported ASYLUM.DLL shows that DLL exports ASS_* audio routines, so it should not be conflated with the immortality toggle path. The in-game trigger is still best modeled as a USECODE item or controller script, consistent with the surrounding string evidence (000e:6337 "CruHealer", 000e:6341 "BatteryCharger", 000e:6445 "Controller", 000e:64ab "AutoFirer" — these are USECODE process class names bracketing the Immortality string).
Secondary handler (000b:b62c):
000b:b62c subscribes to event 0x410 via the registration at 000b:b5cb. When event 0x410 is received by this handler, it writes state code 0xe (decimal 14) into the event object's field +0x6 and passes it to 000b:b7f3 for processing. This is a parallel state-machine path that runs alongside the 000c toggle; likely it drives an associated USECODE process or animation object into state 14.
| Address |
Symbol |
Role |
0004:c055 |
player_receive_damage_and_dispatch_effects |
Renamed. Contains the 0x604f immortality gate at 0004:c205. |
DS:0x604f |
Immortality flag |
Set/cleared by event 0x410. Read only at 0004:c205. |
DS:0x60d2 |
Immortality-on notification ptr |
Near pointer in DS; resolves to far ptr → "Immortality enabled." display. |
DS:0x60ee |
Immortality-off notification ptr |
Near pointer in DS; resolves to far ptr → "Immortality disabled." display. |
000a:b988 |
video_bios_state_snapshot |
Called after notification display in the 0x410 toggle to refresh screen state. |
Conservative folklore verification
- "Cheats can be enabled with
-laurie" is directly verified.
- "There is a hidden five-byte matcher that toggles cheats" is directly verified.
- "F10 performs a large cheat-only restore/reset action" is directly verified.
- "Ctrl+F10 enables god mode" is not supported — the verified F10 branch does not require a modifier.
- "H enables hack mover" is real at runtime (strings confirmed), but not found in the static low-level byte dispatch; the activation comes from the USECODE scripting layer.
- "Immortality makes the player invincible" is partially verified: damage is divided by 262,144, making HP loss negligible; the hit stagger still plays. There is no bypass of the HP system entirely.
- "Immortality is toggled with a keyboard combo" is not supported in compiled C code: event 0x410 has no static keyboard dispatch path. It is USECODE-triggered.
- The hidden five-byte matcher compares bytes from live code at
0007:2833, and the ordinary keyboard ISR producer does not naturally emit byte values 0x80 and 0xfd into record byte +1.