diff --git a/config/parked.json b/config/parked.json index 5d92997f..63e8b96f 100644 --- a/config/parked.json +++ b/config/parked.json @@ -1083,6 +1083,25 @@ }, "objdiff_percent": 82.545456 } + }, + { + "unit": "source/game/game_engine_slayer", + "function": "_slayer_engine_display_score", + "class": "register-allocation", + "evidence": "Fuzzy-parked under the current house rule after reconstructing the complete January PC formatter from the target control flow and relocation graph, with the HCEA routine and string enum supplying the authenticated public name, callback signature, and B3-B6 string meanings. The retained ordinary C preserves January's unconditional typed player_get validation, independent legacy new-target score[128] block, named message/string constants, natural compiler inlining of slayer_get_score, packed game_engine_place return, and both show-score branches including the surprising nonteam team-score read. Target and candidate have the same 640-byte padded envelope and all 52 relocation identities in the same semantic order; the normalized prefix is exact through +0x161. Manual aligned review finds only two branch-local pointer allocation choices: January spills place_name to the dead message parameter slot and keeps format in EBX in the team branch, then uses EBX/EDI for place_name/format in the nonteam branch, while VC7 chooses the opposite legal registers. The fail-closed codegen blocker classifier reports UNKNOWN/unclassified because its atlas has no pointer register/spill-swap rule; it is recorded as diagnostic, not proof. A declaration-order variant was inert and a shared default-format lifetime worsened size/relocations, so no volatile access, artificial carrier, assembly, pragma, fake dependency, alias trick, undefined behavior, or nonsensical exactness control is retained. Reopen only for authoritative January local-variable records or a natural same-compiler register-allocation donor.", + "measurements": { + "target": { + "size": 640, + "relocation_count": 52, + "normalized_sha256": "0c232f29dab93c8f31dcea0fae6d71a6024bef79360a36634aedbeb8c8e97b08" + }, + "base": { + "size": 640, + "relocation_count": 52, + "normalized_sha256": "6b7e2cc293e7f6700db5239c71458697c54c918a5a62aad8f0615e2e92e6f0fb" + }, + "objdiff_percent": 98.98104 + } } ] } diff --git a/config/symbols.json b/config/symbols.json index 7bc45b3f..a87ea84f 100644 --- a/config/symbols.json +++ b/config/symbols.json @@ -2433,7 +2433,7 @@ { "file_offset": 671920, "flags": 32, "name": "_slayer_get_team_score_string" }, { "file_offset": 671968, "flags": 32, "name": "_find_next_target", "static": true }, { "file_offset": 672368, "flags": 32, "name": "_slayer_engine_player_killed_player" }, -{ "file_offset": 672512, "flags": 32, "name": "_code_000a4300" }, +{ "file_offset": 672512, "flags": 32, "name": "_slayer_engine_display_score" }, { "file_offset": 673152, "flags": 32, "name": "_slayer_player_update" }, { "file_offset": 673552, "flags": 32, "name": "_code_000a4710" }, { "file_offset": 673568, "flags": 32, "name": "_code_000a4720" }, diff --git a/docs/object_matching_logs/game_engine_slayer_obj_score_target_update_closeout_20260901.md b/docs/object_matching_logs/game_engine_slayer_obj_score_target_update_closeout_20260901.md index d91538f6..2152c872 100644 --- a/docs/object_matching_logs/game_engine_slayer_obj_score_target_update_closeout_20260901.md +++ b/docs/object_matching_logs/game_engine_slayer_obj_score_target_update_closeout_20260901.md @@ -3,9 +3,10 @@ ## Result The natural-C Slayer packet advances `source/game/game_engine_slayer` from -18 exact / 0 residual / 9 unwritten functions to 25 exact / 1 credible fuzzy -park / 1 unwritten function. The seven newly strict functions contribute -1,376 padded code bytes. +18 exact / 0 residual / 9 unwritten functions to 25 exact / 2 credible fuzzy +parks / 0 unwritten functions. The seven newly strict functions contribute +1,376 padded code bytes, and the reconstructed 640-byte display formatter +closes the final unwritten slot at a measured 98.98104% fuzzy match. | Function | Padded bytes | Result | | --- | ---: | --- | @@ -16,6 +17,7 @@ park / 1 unwritten function. The seven newly strict functions contribute | `_slayer_engine_player_killed_player` | 144 | strict exact | | `_slayer_test_flag` | 16 | strict exact | | `_slayer_player_update` | 400 | strict exact | +| `_slayer_engine_display_score` | 640 | credible fuzzy park | `_slayer_get_score_header_string` is retained as a natural 80-byte candidate against January's 96-byte function. It preserves all six relocation identities @@ -25,13 +27,32 @@ ordinary early-return and branch-local lifetime controls were exhausted without manufacturing an optimizer carrier, so the function is recorded in `config/parked.json` as a `tu-context-optimization` fixed point. -The remaining 640-byte `_code_000a4300` display routine is intentionally left -unwritten pending an authenticated semantic name and coherent reconstruction. +The former 640-byte `_code_000a4300` is authenticated as the public +`_slayer_engine_display_score` callback. The retained ordinary C reproduces +January's two-stage message topology, unconditional player-datum validation, +legacy dead `score[128]` formatting block, B3-B6 UI strings, packed-place +return, and the surprising team-score read in the nonteam branch. Target and +candidate have equal 640-byte padded envelopes and the same 52 relocation +identities in semantic order. They are byte-identical through offset `+0x161`. + +Manual aligned review confines the residual to two branch-local pointer +allocation choices. January spills `place_name` to the dead `message` parameter +slot and keeps the team format in EBX, then assigns EBX/EDI to +`place_name`/`format` in the nonteam branch; the available VC7 compile chooses +the opposite legal registers. Argument values, call order, and branch behavior +remain identical. The fail-closed blocker classifier reports +`UNKNOWN/unclassified` because its atlas has no pointer register/spill-swap +rule, so the park records this as a manual register-allocation classification +rather than claiming tool proof. Declaration-order changes were inert, while a +shared fallback-format lifetime made both size and relocation topology worse. ## Source and naming evidence The function and field names in this packet are authenticated by the later -Halo CE Anniversary source/PDB topology. The `player_datum` fields formerly +Halo CE Anniversary source/PDB topology. January's COFF symbol is external and +is referenced from the Slayer engine callback table, so the callback retains +public January linkage even though the later HCEA PDB describes a static +variant. The `player_datum` fields formerly named `unknown88` and `unknown_d1` are now `multiplayer_special` and `quit_out_of_game`; the same semantic rename was applied to their existing users without changing those translation units' exact-function counts. @@ -40,18 +61,22 @@ Public Slayer declarations live in the dedicated owner header `source/game/game_engine_slayer.h`. Adding those declarations to the broad `game_engine.h` changed VC7 definition position and reopened `units.obj`; the dedicated header preserves the January schedule and keeps `units.obj` at -189/189. Private helpers remain `static` in the Slayer translation unit. +189/189. The packed `game_engine_place` type and its two owner prototypes now +live in the narrow `source/game/game_engine_place.h` header instead of the +implementation file. Private helpers remain `static` in the Slayer translation +unit. ## Verification - `tools/campaign/gate.py source/game/game_engine_slayer --all`: 25 exact, - 1 residual, 1 unwritten. + 2 residual, 0 unwritten; both residuals are measurement-stable parks. - `tools/campaign/gate.py source/units/units --all`: 189 exact, 0 residual, 0 unwritten. -- Neighbor gates remain unchanged: Players 52 exact / 17 residual / 1 - unwritten; Game Engine 166 exact / 8 residual / 6 unwritten. +- The narrow owner header leaves Game Engine unchanged at 166 exact / 8 + residual / 6 unwritten. - Full Ninja build, semantic report, and progress report pass. - Strict campaign board moves from 710,595 to 711,971 padded bytes and from - 4,999 to 5,006 exact functions, with no unrelated exact-function loss. + 4,999 to 5,006 exact functions, with no unrelated exact-function loss; the + fuzzy display-score closure intentionally adds no strict-match credit. - No inline assembly, volatile optimizer control, fake dependency, address-name placeholder, or `point_from_line3d` COMDAT is introduced. diff --git a/source/game/game_engine.c b/source/game/game_engine.c index 3aabd95d..94d77869 100644 --- a/source/game/game_engine.c +++ b/source/game/game_engine.c @@ -540,6 +540,7 @@ symbols in this file: #include "cseries_windows.h" #include "errors.h" #include "game_engine.h" +#include "game_engine_place.h" #include "bitmaps/bitmap_group.h" #include "game_globals.h" @@ -4140,12 +4141,6 @@ void game_engine_weapon_fired( return; } -struct game_engine_place -{ - short flags; - short place; -}; - wchar_t *get_place_name( struct game_engine_place place) { diff --git a/source/game/game_engine_place.h b/source/game/game_engine_place.h new file mode 100644 index 00000000..530f5f77 --- /dev/null +++ b/source/game/game_engine_place.h @@ -0,0 +1,30 @@ +/* +GAME_ENGINE_PLACE.H +*/ + +#ifndef __GAME_ENGINE_PLACE_H +#define __GAME_ENGINE_PLACE_H +#pragma once + +/* ---------- headers */ + +#include "game/game_engine.h" + +/* ---------- structures */ + +struct game_engine_place +{ + short flags; + short place; +}; + +/* ---------- prototypes/GAME_ENGINE.C */ + +wchar_t *get_place_name( + struct game_engine_place place); + +struct game_engine_place game_engine_get_place( + long player_index, + enum get_score_type score_type); + +#endif // __GAME_ENGINE_PLACE_H diff --git a/source/game/game_engine_slayer.c b/source/game/game_engine_slayer.c index ad9e491b..5bfc3fc7 100644 --- a/source/game/game_engine_slayer.c +++ b/source/game/game_engine_slayer.c @@ -76,6 +76,7 @@ symbols in this file: #include "cseries/cseries.h" +#include "game/game_engine_place.h" #include "game/game_engine_slayer.h" #include "game/players.h" #include "memory/data.h" @@ -92,7 +93,12 @@ enum _multiplayer_sound_slayer = 0x15, _multiplayer_sound_team_slayer = 0x23, _slayer_message_new_target = 0x1E, + _game_engine_message_show_score = 0x16, _string_score = 0x9A, + _string_n_team_n = 0xB3, + _string_new_target_name = 0xB4, + _string_name_kills_score_n_team_score_of_max = 0xB5, + _string_name_kills_score_of_max = 0xB6, _game_engine_test_flag_rasterize_score = 1, _slayer_maximum_players = 16, }; @@ -495,6 +501,131 @@ void slayer_engine_player_killed_player( return; } +boolean slayer_engine_display_score( + long player_index, + long message, + long message_data, + wchar_t *buffer, + long buffer_size) +{ + wchar_t score[128]; + struct player_datum *player; + + player_get(player_index); + player = NULL; + + if (message == _slayer_message_new_target) + { + if (game_engine_get_variant()->has_teams) + { + long string_list_index = tag_loaded( + UNICODE_STRING_LIST_TAG, + "ui\\multiplayer_game_text"); + wchar_t *format = string_list_index != NONE + ? unicode_string_list_get_string( + string_list_index, + _string_n_team_n) + : L""; + + usnprintf( + score, + NUMBEROF(score), + format, + slayer_get_score(player_index, _get_score_individual), + slayer_get_score(player_index, _get_score_team)); + } + else + { + usnprintf( + score, + NUMBEROF(score), + L"%d", + slayer_get_score(player_index, _get_score_individual)); + } + + player = player_get(message_data); + } + + switch (message) + { + case _game_engine_message_show_score: + if (game_engine_get_variant()->has_teams) + { + struct game_engine_place place = game_engine_get_place( + player_index, + _get_score_team); + wchar_t *place_name = get_place_name(place); + long string_list_index = tag_loaded( + UNICODE_STRING_LIST_TAG, + "ui\\multiplayer_game_text"); + + wchar_t *format = string_list_index != NONE + ? unicode_string_list_get_string( + string_list_index, + _string_name_kills_score_n_team_score_of_max) + : L""; + + usnprintf( + buffer, + buffer_size, + format, + place_name, + slayer_get_score(player_index, _get_score_individual), + slayer_get_score(player_index, _get_score_team), + game_engine_get_variant()->slayer_variant_score_to_win); + } + else + { + struct game_engine_place place = game_engine_get_place( + player_index, + _get_score_team); + wchar_t *place_name = get_place_name(place); + long string_list_index = tag_loaded( + UNICODE_STRING_LIST_TAG, + "ui\\multiplayer_game_text"); + + wchar_t *format = string_list_index != NONE + ? unicode_string_list_get_string( + string_list_index, + _string_name_kills_score_of_max) + : L""; + + usnprintf( + buffer, + buffer_size, + format, + place_name, + slayer_get_score(player_index, _get_score_team), + game_engine_get_variant()->slayer_variant_score_to_win); + } + + return TRUE; + + case _slayer_message_new_target: + { + long string_list_index = tag_loaded( + UNICODE_STRING_LIST_TAG, + "ui\\multiplayer_game_text"); + wchar_t *format = string_list_index != NONE + ? unicode_string_list_get_string( + string_list_index, + _string_new_target_name) + : L""; + + usnprintf( + buffer, + buffer_size, + format, + player->name); + + return TRUE; + } + + default: + return FALSE; + } +} + void slayer_player_update( long index) { diff --git a/source/game/game_engine_slayer.h b/source/game/game_engine_slayer.h index 7ee942f4..ccc65151 100644 --- a/source/game/game_engine_slayer.h +++ b/source/game/game_engine_slayer.h @@ -88,6 +88,13 @@ void slayer_engine_player_killed_player( long dead_player_index, boolean friendly_fire); +boolean slayer_engine_display_score( + long player_index, + long message, + long message_data, + wchar_t *buffer, + long buffer_size); + void slayer_player_update( long player_index);