game: reconstruct slayer display formatter

This commit is contained in:
Jonas Volman committed 2026-09-01 17:52:15 -07:00
1 parent 484e637153
commit ecb25ee0cd
7 files changed
+225 -18

No files matched your search

+19
View File
@@ -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
}
}
]
}
+1 -1
View File
@@ -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" },
@@ -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.
+1 -6
View File
@@ -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)
{
+30
View File
@@ -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
+131
View File
@@ -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)
{
+7
View File
@@ -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);