Content extracted from `crusader_decompilation_notes.md`. Named via systematic analysis of 11,692 NE relocation fixup entries — the functions most frequently called through the `CALLF 0x0000:ffff` thunk mechanism.
| 1 | `000a:44fd` | `fatal_error_report_fmt_a_and_exit` | 331 | Reentrancy-guarded fatal report helper. Prints the shared banner at `0x44a5`, formats template `0x44cc` with caller words, then exits; earlier `0005:2c68` selector speculation is now rejected. |
| 11 | `0008:ce00` | `entity_call_two_vtables_base` | 117 | Calls vtable[0] at entity+`0x1e` and `+0x28` |
| 12 | `0008:bb8c` | `entity_check_flag_0x4000` | 115 | Short-circuits if flag `0x4000` set at `+0x16` |
| 13 | `0008:cda7` | `entity_free_both_word_lists` | 115 | Frees word lists at entity+`0x1e` and `+0x28` if optional pointers at `+0x24/+0x26` and `+0x2e/+0x30` non-null. Both call `entity_word_list_free_existing`. |
| 17 | `0009:c563` | `event_queue_push` | 82 | Circular buffer enqueue. Ring index (`+0xe`) masked `0x3f`, slot masked `0xfff8`. Writes event type word + data byte pair. |
| 18 | `0005:c448` | `list_remove_and_free` | 74 | Unlinks node from linked list via `FUN_0005_c495`, optionally calls `mem_free` if bit 0 of flags set |
| 19 | `000b:2e00` | *(no function in Ghidra)* | 74 | Analysis gap at seg109:0000. Needs manual function creation. |
| 20 | `0009:1f12` | `dos_file_lseek` | 73 | DOS LSEEK (INT 21h AH=42h) wrapper with error reporting to `0x867a` |
---
## Tier 2: Ranks 21–40 (56–73 callers)
| Rank | Address | Name | Calls | Description |
|------|---------|------|-------|-------------|
| 21 | `0009:3600` | `rotating_buffer_advance` | 73 | Advances 5-slot circular counter at `0x3eb6`, zeros pointer in table at `0x867c`, dispatches via jump table |
| 22 | `0009:943a` | `entity_rect_compare_and_dispatch` | 68 | Compares bounding rectangles of two entities, dispatches based on flag bits `4/2/1` at `+0x16` |
| 23 | `0009:1e61` | `dos_file_close` | 65 | DOS file close (INT 21h), error reporting, sets handle to `-1` |
| 24 | `0005:e252` | *(unnamed — unclear)* | 65 | Copies 11 words from Phar Lap extender area (`FUN_0000_12c6+5`), then calls thunk. Interrupt/trampoline setup? |
| 25 | `0003:dbcc` | `crt_format_string` | 64 | MetaWare High C formatting wrapper. Calls `FUN_0003_bb92` with runtime format dispatch table. |
| 26 | `0007:5a00` | *(no function in Ghidra)* | 64 | High-traffic raw target at `seg043:0000`. Earlier `debris_spawn` / seg001 mapping was rejected after checking relocation labels. Still needs manual function creation and direct analysis. |
| 27 | `000a:4742` | `assert_buffer_valid` | 63 | Validates handle: asserts `param_2 == cookie` at `0x45a6` and `param_1 < limit` at `0x87e0` |
| 28 | `0009:9216` | `entity_conditional_render_dispatch` | 63 | Checks entity flag bits 4 and 1 at `+0x16`, dispatches to vtable[`+0xc`] or thunk |
| 29 | `0008:cb2c` | `entity_flag20_clear_and_update_target` | 61 | Clears flag bit `0x20`, writes target `+0x12/+0x14`, calls refresh |
| 30 | `0008:cb5c` | `entity_flag20_set_and_init_target` | 61 | Sets flag bit `0x20`, inits target if zero, calls refresh |
| 42 | `000b:3ab2` | `sprite_node_dispatch_event` | 56 | Large event dispatch: checks event type (`2/4/8/0x100`), updates global focus ptr at `[0x4fd0:4fd2]`, dispatches via vtable methods `[+0x14/+0x18/+0x20/+0x24]` by event code. Switch table for 16 event types. |
| 43 | `000a:48ff` | `rng_next_modulo` | 55 | Advances seg091 RNG state and returns the result modulo the requested bound; returns 0 when bound is 0. |
| 44 | `000b:3362` | `sprite_tree_unwind_check` | 55 | Validates `SS == param_2` (stack segment guard), then decrements global counter at `[0x4fd6]` |
| 45 | `000b:40ee` | `sprite_node_update_and_dispatch` | 55 | If `sprite_node_is_dirty` returns false: marks dirty, calcs accumulated bounds via `sprite_tree_get_accumulated_bounds` (`3ed8`), then dispatches via thunk |
| 46 | `000a:7b5f` | `vtable_stub_trampoline` | 55 | Calls through fixup thunk (forwarder to another function) |
| 53 | `0003:a880` | *(no function in Ghidra)* | 49 | Analysis gap in CRT segment |
| 54 | `0006:170c` | `tile_class_get_byte` | 47 | Looks up class data: indexes into table at `[0x7e1e]` by `(*param_1 * 0x79)`, returns byte at offset `+0xc` |
| `000a:44fd` | seg091:00fd | 331 | Fatal report helper now identified; remaining gap is the exact human-readable template text at `0x44cc`/`0x44a5`, not control flow. |
| `0007:5a00` | seg043:0000 | 64 | Start of segment 43. Earlier seg001 `debris_spawn` port was rejected; still needs manual function creation and direct analysis. |
| `000a:48ff` | seg091:04ff | 55 | Recovered as `rng_next_modulo`; bounded wrapper around seg091 RNG state advance. |
| `0003:a880` | seg005:0880 | 49 | In CRT segment near `far_memcpy`. |
| `0003:ad75` | seg005:0d75 | 43 | In CRT segment near `mem_alloc`. |
- The earlier standalone seg001 port hypothesis in this subrange was wrong.
- Relocation data places raw `0007:5a00` at `seg043:0000`, and the named helper at `0007:5b6f` sits at `seg043:016f`.
- Because of that segment placement, standalone seg001 names such as `debris_spawn` (`0x7490`) and `entity_die` (`0x75ff`) should NOT be ported into this raw range.
-`0007:5b6f` no longer exists as a function after the PyGhidra repair pass. Its behavior now lines up with the repaired function `0007:5b7a = entity_set_at_target_update_facing`.
- The currently repaired functions at `0007:5a90` and `0007:5c1c` should keep their positional names until a later pass resolves the thunk-heavy bodies more clearly.
| 64 | `0005:1565` | `entity_action_by_type_dispatch` | 39 | Checks entity type against whitelist (`0x432,0x5a0,0x1fd,0x1fe,0x8f,0x59f,0x2b3,0x2ca`), dispatches by flags at `[0xc76]` and `[0x85f]` |
| 66 | `0009:6f5a` | `vga_palette_write` | 38 | Writes RGB triplets to VGA DAC (port `0x3C8/0x3C9`). Range `param_2..param_3` from palette data at `*param_1` |
| 67 | `0009:8ef6` | `line_draw_dispatch` | 38 | Compares `abs(dx)` vs `abs(dy)` to determine major axis, dispatches to appropriate line draw routine |
| 72 | `0009:c433` | `event_queue_align_index` | 34 | Returns `param_1 & 0xFFF8` — aligns ring index to 8-byte event slot boundary |
| 73 | `0009:2156` | `dos_file_get_size` | 33 | Saves file position, does INT 21h AH=42h AL=02 (seek to end), restores position. Returns file size in DX:AX |
| 74 | `000a:2c41` | `list_iterate_next` | 33 | Linked list iterator: if `*out==0` returns first from `obj+2`; else follows next at `ptr+2/+4`. Returns bool (has more) |
| 75 | `000a:454d` | `seg091_func_014d` | 32 | Recovered boundary. Shares flag `0x44a4`; checks optional long argument against the `0x45a6` cookie/context global. |
| 76 | `000b:2446` | `sprite_clear_redraw_flag` | 31 | Clears flag at `obj+0x17e`, then dispatches via thunk |
| 77 | `0005:1238` | `entity_get_class_word` | 30 | Looks up table at `[0x7e01]` indexed by `*param_1 * 2`, returns word. Sister of `entity_get_type_word` (which uses `[0x7df9]`) |
| 78 | `000b:1446` | `display_null_check_dispatch` | 30 | Null-checks far ptr params, dispatches to different thunks based on result |
| 79 | `000d:85da` | `vga_palette_set_all_black` | 29 | Sets byte at `global_obj[0x6828]+0x40 = 1` if global non-null, then calls thunk. *(Note: earlier name `map_object_set_dirty_flag` was incorrect; corrected in seg137 analysis.)* |
| 80 | `0005:1511` | `entity_destroy_trampoline` | 29 | Pure thunk forwarder to entity destruction |
| 96 | `0005:0376` | `entity_resolve_base_type` | 26 | Walks entity class hierarchy (bit 8 in `[0x7e01]`) via `[0x7ded]`, returns base type from `[0x7df1]` |
| 97 | `000b:2492` | `sprite_redraw_if_needed` | 26 | If redraw flag at `+0x17e` is clear, calls update routine + thunk |
| 98 | `0003:e4d3` | `dos_file_open_wrapper` | 26 | Zeros output byte, delegates to file open impl at `0003:bb92` |
| 99 | `0005:033e` | `entity_resolve_base_parent` | 25 | Same hierarchy walk as `entity_resolve_base_type` but returns parent from `[0x7ded]` |
| 100 | `000a:87fd` | `render_clip_rect_to_viewport` | 25 | Clips 4 rect params to viewport bounds at `[0x4014]`, sets dirty flag at `0x8a16`, increments draw counter at `0x4716` |
| 102 | `0009:80db` | `bbox_overlap_test` | 25 | Boolean rectangle overlap test for two 4-word bbox structs; unlike `bbox_intersect`, it does not write back an intersection |
| 103 | `000d:cc00` | `entity_compute_proximity_or_visibility_bucket` | 25 | Returns `0x40` if entity is null or projected bbox overlaps viewport; otherwise buckets world-distance from current reference entity (`0x7e22`) into `0x32/0x20/0x10/0x08` |
| 104 | `000d:d413` | `entity_refresh_recent_proximity_or_visibility_buckets` | 24 | Recomputes bucket values for the last four active entries in `0x69ac` and notifies backing handles via `000a:6343` when a bucket changes |