Reconcile Claude fifty-object lane with canonical

Merge verified source, data and owner corrections from 4e84c212. Resolve overlapping HS/path symbols against canonical, retain the dynavobgeom ownership rejection, document Layer 2/P7 source-shape decisions, and preserve held research. Verified 14 exact functions, 17 new Halo objects, zero regressions under objdiff 3.3.1.
This commit is contained in:
Jonas Volman committed 2026-09-24 07:20:01 -07:00
commit f6d00a8c1d
1170 files changed
+285338 -4236

No files matched your search

+17 -17
View File
@@ -383,7 +383,7 @@
{ "name": "source/sound/sound_scenery_group.c", "index": 8, "status": "Matching" },
{ "name": "source/sound/sound_scenery.c", "index": 9, "status": "Matching" },
{ "name": "source/sound/sound_preferences.c", "index": 10, "status": "Matching" },
{ "name": "source/sound/sound_manager.c", "index": 11, "status": "NonMatching" },
{ "name": "source/sound/sound_manager.c", "index": 11, "status": "Matching" },
{ "name": "source/sound/sound_environment_definitions.c", "index": 12, "status": "Matching" },
{ "name": "source/sound/sound_dsound_xbox.c", "index": 13, "status": "Matching" },
{ "name": "source/sound/sound_definitions.c", "index": 14, "status": "Matching" },
@@ -404,18 +404,18 @@
{ "name": "source/cache/xbox_sound_cache.c", "index": 29, "status": "NonMatching" },
{ "name": "source/cache/predicted_resources.c", "index": 30, "status": "Matching" },
{ "name": "source/cache/physical_memory_map.c", "index": 31, "status": "Matching" },
{ "name": "source/cache/cache_files_windows.c", "index": 32, "status": "NonMatching" },
{ "name": "source/cache/cache_files_windows.c", "index": 32, "status": "Matching" },
{ "name": "source/cache/cache_files_decompress_windows.c", "index": 33, "status": "Matching" },
{ "name": "source/cache/cache_files.c", "index": 34, "status": "Matching" },
{ "name": "source/units/vehicles.c", "index": 35, "status": "NonMatching" },
{ "name": "source/units/vehicle_definitions.c", "index": 36, "status": "Matching" },
{ "name": "source/units/units.c", "index": 37, "status": "NonMatching" },
{ "name": "source/units/units.c", "index": 37, "status": "Matching" },
{ "name": "source/units/unit_scripting_commands.c", "index": 38, "status": "Matching" },
{ "name": "source/units/unit_dialogue.c", "index": 39, "status": "Matching" },
{ "name": "source/units/unit_definitions.c", "index": 40, "status": "Matching" },
{ "name": "source/units/dialogue_definitions.c", "index": 41, "status": "Matching" },
{ "name": "source/units/bipeds.c", "index": 42, "status": "NonMatching" },
{ "name": "source/units/biped_limp_noodle.c", "index": 43, "status": "NonMatching" },
{ "name": "source/units/biped_limp_noodle.c", "index": 43, "status": "Matching" },
{ "name": "source/units/biped_definitions.c", "index": 44, "status": "Matching" },
{ "name": "source/text/unicode.c", "index": 45, "status": "Matching" },
{ "name": "source/text/text_group.c", "index": 46, "status": "Matching" },
@@ -425,7 +425,7 @@
{ "name": "source/tag_files/tag_groups.c", "index": 50, "status": "Matching" },
{ "name": "source/tag_files/tag_files_xbox.c", "index": 51, "status": "Matching" },
{ "name": "source/tag_files/tag_files.c", "index": 52, "status": "Matching" },
{ "name": "source/tag_files/files_windows.c", "index": 53, "status": "NonMatching" },
{ "name": "source/tag_files/files_windows.c", "index": 53, "status": "Matching" },
{ "name": "source/tag_files/files.c", "index": 54, "status": "Matching" },
{ "name": "source/structures/structures.c", "index": 55, "status": "NonMatching" },
{ "name": "source/structures/structure_visibility.c", "index": 56, "status": "Matching" },
@@ -434,7 +434,7 @@
{ "name": "source/structures/structure_lens_flares.c", "index": 59, "status": "NonMatching" },
{ "name": "source/structures/structure_detail_objects.c", "index": 60, "status": "Matching" },
{ "name": "source/structures/structure_bsp_definitions.c", "index": 61, "status": "Matching" },
{ "name": "source/structures/leaf_map.c", "index": 62, "status": "NonMatching" },
{ "name": "source/structures/leaf_map.c", "index": 62, "status": "Matching" },
{ "name": "source/structures/detail_object_definitions.c", "index": 63, "status": "Matching" },
{ "name": "source/structures/cluster_partitions.c", "index": 64, "status": "Matching" },
{ "name": "source/structures/radiosity/radiosity_io.c", "index": 65, "status": "Matching" },
@@ -456,7 +456,7 @@
{ "name": "source/render/render_sprite.c", "index": 81, "status": "NonMatching" },
{ "name": "source/render/render_sky.c", "index": 82, "status": "Matching" },
{ "name": "source/render/render_particles.c", "index": 83, "status": "Matching" },
{ "name": "source/render/render_objects.c", "index": 84, "status": "NonMatching" },
{ "name": "source/render/render_objects.c", "index": 84, "status": "Matching" },
{ "name": "source/render/render_media.c", "index": 85, "status": "Matching" },
{ "name": "source/render/render_detail_objects.c", "index": 86, "status": "Matching" },
{ "name": "source/render/render_debug.c", "index": 87, "status": "NonMatching" },
@@ -482,7 +482,7 @@
{ "name": "source/rasterizer/xbox/rasterizer_xbox_vertex_shaders.c", "index": 107, "status": "Matching" },
{ "name": "source/rasterizer/xbox/rasterizer_xbox_transparent_geometry.c", "index": 108, "status": "Matching" },
{ "name": "source/rasterizer/xbox/rasterizer_xbox_text.c", "index": 109, "status": "Matching" },
{ "name": "source/rasterizer/xbox/rasterizer_xbox_shadows.c", "index": 110, "status": "NonMatching" },
{ "name": "source/rasterizer/xbox/rasterizer_xbox_shadows.c", "index": 110, "status": "Matching" },
{ "name": "source/rasterizer/xbox/rasterizer_xbox_screen_effect.c", "index": 111, "status": "Matching" },
{ "name": "source/rasterizer/xbox/rasterizer_xbox_profile.c", "index": 112, "status": "NonMatching" },
{ "name": "source/rasterizer/xbox/rasterizer_xbox_plasma_energy.c", "index": 113, "status": "Matching" },
@@ -512,7 +512,7 @@
{ "name": "source/physics/collision_models.c", "index": 137, "status": "Matching" },
{ "name": "source/physics/collision_model_definitions.c", "index": 138, "status": "Matching" },
{ "name": "source/physics/collision_features.c", "index": 139, "status": "NonMatching" },
{ "name": "source/physics/collision_debug.c", "index": 140, "status": "NonMatching" },
{ "name": "source/physics/collision_debug.c", "index": 140, "status": "Matching" },
{ "name": "source/physics/collision_bsp_definitions.c", "index": 141, "status": "Matching" },
{ "name": "source/physics/collision_bsp.c", "index": 142, "status": "NonMatching" },
{ "name": "source/physics/bsp3d.c", "index": 143, "status": "Matching" },
@@ -521,7 +521,7 @@
{ "name": "source/objects/scenery_group.c", "index": 146, "status": "Matching" },
{ "name": "source/objects/scenery.c", "index": 147, "status": "Matching" },
{ "name": "source/objects/placeholder_definitions.c", "index": 148, "status": "Matching" },
{ "name": "source/objects/objects.c", "index": 149, "status": "NonMatching" },
{ "name": "source/objects/objects.c", "index": 149, "status": "Matching" },
{ "name": "source/objects/object_types.c", "index": 150, "status": "Matching" },
{ "name": "source/objects/object_shadows.c", "index": 151, "status": "Matching" },
{ "name": "source/objects/object_lights.c", "index": 152, "status": "NonMatching" },
@@ -533,7 +533,7 @@
{ "name": "source/objects/widgets/widgets.c", "index": 158, "status": "Matching" },
{ "name": "source/objects/widgets/lightning_definitions.c", "index": 159, "status": "Matching" },
{ "name": "source/objects/widgets/lightning.c", "index": 160, "status": "NonMatching" },
{ "name": "source/objects/widgets/light_volumes.c", "index": 161, "status": "NonMatching" },
{ "name": "source/objects/widgets/light_volumes.c", "index": 161, "status": "Matching" },
{ "name": "source/objects/widgets/light_volume_definitions.c", "index": 162, "status": "Matching" },
{ "name": "source/objects/widgets/glow_definitions.c", "index": 163, "status": "Matching" },
{ "name": "source/objects/widgets/glow.c", "index": 164, "status": "NonMatching" },
@@ -557,7 +557,7 @@
{ "name": "source/networking/network_client_manager.c", "index": 182, "status": "Matching" },
{ "name": "source/models/models.c", "index": 183, "status": "Matching" },
{ "name": "source/models/model_definitions.c", "index": 184, "status": "Matching" },
{ "name": "source/models/model_animations.c", "index": 185, "status": "NonMatching" },
{ "name": "source/models/model_animations.c", "index": 185, "status": "Matching" },
{ "name": "source/models/model_animation_definitions.c", "index": 186, "status": "Matching" },
{ "name": "source/memory/texture_page.c", "index": 187, "status": "Matching" },
{ "name": "source/memory/stack_memory_pool.c", "index": 188, "status": "Matching" },
@@ -626,7 +626,7 @@
{ "name": "source/interface/ui_widget.c", "index": 251, "status": "NonMatching" },
{ "name": "source/interface/terminal.c", "index": 252, "status": "Matching" },
{ "name": "source/interface/progress_bar.c", "index": 253, "status": "NonMatching" },
{ "name": "source/interface/player_ui.c", "index": 254, "status": "NonMatching" },
{ "name": "source/interface/player_ui.c", "index": 254, "status": "Matching" },
{ "name": "source/interface/overhead_map.c", "index": 255, "status": "Matching" },
{ "name": "source/interface/meters.c", "index": 256, "status": "Matching" },
{ "name": "source/interface/meter_definitions.c", "index": 257, "status": "Matching" },
@@ -641,7 +641,7 @@
{ "name": "source/interface/unit_hud_interface_definition.c", "index": 266, "status": "Matching" },
{ "name": "source/interface/motion_sensor.c", "index": 267, "status": "NonMatching" },
{ "name": "source/interface/hud_weapon.c", "index": 268, "status": "NonMatching" },
{ "name": "source/interface/hud_unit.c", "index": 269, "status": "NonMatching" },
{ "name": "source/interface/hud_unit.c", "index": 269, "status": "Matching" },
{ "name": "source/interface/hud_sounds.c", "index": 270, "status": "Matching" },
{ "name": "source/interface/hud_nav_points.c", "index": 271, "status": "NonMatching" },
{ "name": "source/interface/hud_messaging_definitions.c", "index": 272, "status": "Matching" },
@@ -738,7 +738,7 @@
{ "name": "source/camera/following_camera.c", "index": 363, "status": "Matching" },
{ "name": "source/camera/flying_camera.c", "index": 364, "status": "Matching" },
{ "name": "source/camera/first_person_camera.c", "index": 365, "status": "Matching" },
{ "name": "source/camera/editor_flying_camera.c", "index": 366, "status": "NonMatching" },
{ "name": "source/camera/editor_flying_camera.c", "index": 366, "status": "Matching" },
{ "name": "source/camera/director.c", "index": 367, "status": "Matching" },
{ "name": "source/camera/dead_camera.c", "index": 368, "status": "NonMatching" },
{ "name": "source/camera/camera_track_definitions.c", "index": 369, "status": "Matching" },
@@ -802,7 +802,7 @@
{ "name": "source/ai/path_smoothing.c", "index": 427, "status": "NonMatching" },
{ "name": "source/ai/path_obstacles.c", "index": 428, "status": "NonMatching" },
{ "name": "source/ai/path_obstacle_avoidance.c", "index": 429, "status": "NonMatching" },
{ "name": "source/ai/path.c", "index": 430, "status": "NonMatching" },
{ "name": "source/ai/path.c", "index": 430, "status": "Matching" },
{ "name": "source/ai/encounters.c", "index": 431, "status": "Matching" },
{ "name": "source/ai/ai_script.c", "index": 432, "status": "Matching" },
{ "name": "source/ai/ai_scenario_definitions.c", "index": 433, "status": "Matching" },
@@ -828,7 +828,7 @@
{ "name": "source/ai/actor_perception.c", "index": 453, "status": "NonMatching" },
{ "name": "source/ai/actor_moving.c", "index": 454, "status": "NonMatching" },
{ "name": "source/ai/actor_looking.c", "index": 455, "status": "NonMatching" },
{ "name": "source/ai/actor_firing_position.c", "index": 456, "status": "NonMatching" },
{ "name": "source/ai/actor_firing_position.c", "index": 456, "status": "Matching" },
{ "name": "source/ai/actor_definitions.c", "index": 457, "status": "Matching" },
{ "name": "source/ai/actor_combat.c", "index": 458, "status": "NonMatching" },
{ "name": "source/ai/actions.c", "index": 459, "status": "NonMatching" },
-48
View File
@@ -1,30 +1,6 @@
{
"version": 1,
"entries": [
{
"unit": "source/objects/objects",
"class": "candidate-only-comdat-owner",
"symbol": "_point_in_sphere, _real_random, _signed_angular_difference, _valid_real_vector3d, _valid_real_vector3d_axes2",
"reason": "All January target functions and owned data now match, but five candidate-only shared helpers conflict with hand-written no-duplicates providers in path_obstacles, action_charge, player_control and bored_camera. Exact target bytes and the corrected private symbols do not establish safe whole-object linkage.",
"evidence": "docs/object_matching_logs/claude_data_ownership_closeout_20260924.md",
"reopen": "Replace the conflicting hand-written providers with genuine shared-header uses, retain every exact caller, and prove compatible ordinary provider links in both orders before admitting the object. Do not credit the candidate-only copies."
},
{
"unit": "source/structures/leaf_map",
"class": "candidate-only-comdat-owner",
"symbol": "_distance3d",
"reason": "All 25 target functions and target-owned data sections are exact, including two newly closed callers. But the real shared-header distance3d use emits a candidate-only 64-byte SELECT_ANY copy whose instruction SHA differs from January's selected ActionVehicle NODUP copy. Ordinary LeafMap/ActionVehicle links fail with LNK2005 in both orders. A global source-body correction makes the copies identical but loses two inherited exact functions and stales a parked function, so it was rejected. The exact callers and verified data retain their separate credit; whole-object completion does not.",
"evidence": "docs/object_matching_logs/claude_object_closure_canonical_reconciliation_20260924.md#distance3d-provider-conflict",
"reopen": "Recover a genuine shared distance3d source/header spelling and inline schedule that yields January's selected COMDAT without any exact sibling loss or stale park; verify all real_math.h consumers and ordinary links. Do not suppress emission, force inline, or attribute candidate-only helper bytes to LeafMap."
},
{
"unit": "source/units/biped_limp_noodle",
"class": "candidate-only-comdat-owner",
"symbol": "_distance3d",
"reason": "All six target functions remain strict exact, but this Matching label also emitted the same candidate-only 64-byte SELECT_ANY distance3d copy that differs from January's selected ActionVehicle NODUP provider. Ordinary link compatibility is not established; the global real_math.h correction fails the whole-board no-regression and parked-function gates. Function credit remains; whole-object completion is held.",
"evidence": "docs/object_matching_logs/claude_object_closure_canonical_reconciliation_20260924.md#distance3d-provider-conflict",
"reopen": "Use the same authentic, board-wide no-regression provider repair as LeafMap and prove ordinary selected-provider links in both orders before restoring Matching."
},
{
"unit": "source/rasterizer/xbox/rasterizer_xbox_profile",
"class": "candidate-only-comdat-owner",
@@ -49,30 +25,6 @@
"evidence": "docs/object_matching_logs/hud_draw_obj_two_exact_leaves_integration_20260904.md#complete-owner-boundary",
"reopen": "Require authentic source topology or an ordinary base/base link with an authenticated compatible canonical selected helper before clearing this veto. Do not force inline, manually expand math, alter target/COMDAT metadata, invent a discard pass, or credit candidate-only helper bytes. All remaining HUD Draw functions/data/linkage/source checks must independently pass before whole-object Matching."
},
{
"unit": "source/interface/hud_unit",
"class": "candidate-only-comdat-owner",
"symbol": "_fast_ftol",
"reason": "The new source-authentic renderer naturally emits an unreferenced select-any32-byte fast_ftol copy absent from January HUD Unit. Its body equals January's selected Actor Combat helper, but that selected owner is no-duplicates and the prior ordinary XDK link test rejects the duplicate. Genuine initializer/data credit and the honest renderer park remain valid; identical helper bytes do not prove whole-object ownership.",
"evidence": "docs/object_matching_logs/hud_unit_obj_renderer_integration_20260904.md#honest-residual-and-complete-owner-boundary",
"reopen": "Remove only after authentic source topology preserves the January helper schedule without extra emission, or an ordinary base/base link proves a compatible authenticated canonical selected owner can coalesce the copy. Do not force inline, manually expand the helper, alter comparator/selection metadata, invent a discard pass, or count another object's selected bytes. The renderer must independently become strict and all other source/data/linkage checks must pass before Matching admission."
},
{
"unit": "source/interface/player_ui",
"class": "source-layout-incomplete",
"symbol": "struct playlist_profile",
"reason": "All42 target functions and target data are strict exact, but the public playlist_profile is only forward-declared by its owner header while Player UI defines a local partial view with an opaque0x47-byte middle misleadingly called unused. Those bytes hold real January variant state; the complete authentic January public layout is not yet recovered. Exact code/data alone cannot certify this source as a completed object. The reviewed ordinary typed-loop, enum/macro and game_variant storage cleanups remain valid and receive independent function credit.",
"evidence": "docs/object_matching_logs/player_ui_obj_owner_house_rule_reconciliation_20260904.md#remaining-source-boundary-and-reopen-condition",
"reopen": "Authenticate the full January playlist-profile layout and place shared declarations in their genuine owner without opaque fake padding or transplantation of the incompatible later152-byte format. Preserve inherited exact functions across affected callers and finish the documented natural select-any string ownership/coalescing audit before whole-object admission. Do not debit valid target-function progress for this separate source-layout veto."
},
{
"unit": "source/units/units",
"class": "candidate-only-comdat-owner",
"symbol": "_point_from_line3d",
"reason": "The rebuilt translation unit emits a 48-byte select-any _point_from_line3d COMDAT absent from the January units split, while January's selected action_charge owner is no-duplicates. All 189 January units functions remain strict exact, including the two authentic external calls from _unit_cause_continuous_melee_damage; the surplus owner is a separate whole-object admission failure under the explicit no-emission house rule.",
"evidence": "docs/object_matching_logs/shared_inline_helper_admission_audit_20260904.md#units-point_from_line3d-owner",
"reopen": "Remove only after source-authentic header/declaration topology preserves all 189 exact target functions and the two January external calls while preventing units.obj from emitting _point_from_line3d. Linker discard alone does not waive the explicit no-emission house rule; do not expand the two external calls or use forced inline/noinline, pragmas, assembly, or byte-shaping."
},
{
"unit": "source/effects/decals",
"class": "candidate-only-comdat-owner",
+15 -173
View File
@@ -128,11 +128,11 @@
"base": {
"size": 384,
"relocation_count": 27,
"normalized_sha256": "839937d5e05bba8840732fd3c229c8f0829b7b35c43bcf2391c459165baaf3c0"
"normalized_sha256": "bbb652e083aadc92e51e4445f93d49d41941e7c53f50cc1883658c355aa579cb"
},
"objdiff_percent": 88.40708
"objdiff_percent": 88.451324
},
"evidence": "Best currently integrated house-rule-compliant reconstruction from the 2026-09-07 PlayerProfile/S3TC reconciliation batch. Keep the genuine thread callback, mutex/file/checksum/metadata lifecycle and typed 512-byte file block, with initialized padding rather than serializing stack bytes. See docs/object_matching_logs/player_profile_s3tc_batch_reconciliation_20260907.md for donor provenance, raw-target limits, full-batch review and research pointers. Zero exact credit; retain for future evidence-driven work without source-pressure tuning."
"evidence": "Best currently integrated house-rule-compliant reconstruction from the 2026-09-07 PlayerProfile/S3TC reconciliation batch. Keep the genuine thread callback, mutex/file/checksum/metadata lifecycle and typed 512-byte file block, with initialized padding rather than serializing stack bytes. See docs/object_matching_logs/player_profile_s3tc_batch_reconciliation_20260907.md for donor provenance, raw-target limits, full-batch review and research pointers. Zero exact credit; retain for future evidence-driven work without source-pressure tuning. 2026-09-24 (fifty-objects lane batch 4b): re-baselined after the reviewed zero-credit packet; see docs/object_matching_logs/claude_fifty_objects_20260925.md."
},
{
"unit": "source/saved games/player_profile",
@@ -262,25 +262,6 @@
"objdiff_percent": 98.53933
}
},
{
"unit": "source/tag_files/files_windows",
"function": "_file_get_size",
"class": "unclassified",
"evidence": "Complete typed path/attribute lookup, low-size return and natural file_error call. Same padded size and relocation count; info-pointer lifetime/register differences remain. No proven narrow compiler recipe, allocation search, fake operations, raw offsets, assembly or forced inline. Reopen on authentic source or family API evidence. See docs/object_matching_logs/files_windows_obj_small_family_wave_20260904.md.",
"measurements": {
"target": {
"size": 224,
"relocation_count": 13,
"normalized_sha256": "3e237cfe3e76e06d034d1b64effb416e37dfb3694bfa56ca1d16cd16cfba958f"
},
"base": {
"size": 224,
"relocation_count": 13,
"normalized_sha256": "d26fde60720be6198f891f7f8e4688b9a5617339aa8b8f4d1d314522ece62a27"
},
"objdiff_percent": 85.75325
}
},
{
"unit": "source/effects/player_effects",
"function": "_player_effect_get_camera_effect_matrix",
@@ -436,25 +417,6 @@
"objdiff_percent": 99.43569
}
},
{
"unit": "source/objects/widgets/light_volumes",
"function": "_light_volume_render",
"class": "tu-context-optimization",
"evidence": "Fuzzy-parked under the current house rule after the complete source-history and worktree census, the authenticated HCEA renderer/interpolator/type records at commit 570c83fd9c365dad6f2a3e7041705d5b84c7847c, the January disassembly, and the recorded bounded legal-C experiment families were rechecked from canonical ec4ae894a. The retained typed C implements the complete marker, view-angle, distance-fade, object-function, sprite-count, exponent, color-interpolation, and rasterizer path without fake steering. January emits a 912-byte function with a 0xA4 frame; VC7 emits 928 bytes with a 0xA8 frame. Both have 47 relocation types and destinations in the same semantic order. The remaining structural boundary is January's immediate 1.0f distance-fade store interleaved with the marker delta and its compact x87-valued inlined pow1 joins, while the available compiler materializes an earlier distance-fade local and memory phis for the later exponent results. Statement-position, scope, helper-edge, conditional-expression, direct-consumption, loop-count/divisor, HCEA-topology, compiler-version, and donor-branch families either reproduce this fixed point or lose the proven frame/relocation topology. No volatile access, register hint, barrier, assembly, raw offset, representation cast, undefined behavior, fake dependency, or nonsensical branch is retained. Reopen only for authoritative January source/local records, a natural same-compiler donor that distinguishes the x87 lifetime, or a genuinely distinct authentic VC7/QFE compiler.",
"measurements": {
"target": {
"size": 912,
"relocation_count": 47,
"normalized_sha256": "2e43dbc111ad125565ead94aff803117a92a3bb3215d18254e28281e204d0031"
},
"base": {
"size": 928,
"relocation_count": 47,
"normalized_sha256": "a4e273c7939882fcf1dadf774289b21742f1f88e718136216c33eaf04d55a94f"
},
"objdiff_percent": 75.711266
}
},
{
"unit": "source/structures/structure_lens_flares",
"function": "_build_structure_lens_flares",
@@ -797,25 +759,6 @@
"objdiff_percent": 51.18261
}
},
{
"unit": "source/cache/cache_files_windows",
"function": "_cache_files_open_cache_files",
"class": "unclassified",
"evidence": "Fuzzy-parked under the current house rule with the full cache-file startup loop retained as ordinary typed C. January's 47-relocation call graph, file/API constants, assertions, diagnostic strings, cache-file layout, and the surrounding HaloCEA cache subsystem establish the behavior: delete the sentinel slot, open each cache file, preserve correctly sized existing files, rewrite stale files asynchronously, truncate them to the configured size, install the handle, validate cached headers against the DVD checksum/build, and clear invalid slots. Target and candidate share the 752-byte padded envelope and all 47 relocation identities; objdiff reports 83.333336 percent similarity. The residual is broad VC7 basic-block and register scheduling, especially cold CreateFile/setup-failure placement and the post-open validation path, without evidence for a unique source rewrite. No goto reshaping, raw offsets, volatile/register forcing beyond the real asynchronous completion flag, barrier, pragma, assembly, fake dependency, forced inline, or nonsensical success path is retained. Reopen for authoritative January source/local records or a natural same-compiler donor that explains the cold-block layout. Re-measured 2026-09-16: the exact static helper cached_map_block_on_async_request now snapshots the completion flag, reproducing January's double volatile load at this inlined wait tail (0x17a/0x17d); the body is otherwise unchanged and still non-exact (752 bytes / 47 relocations; alndiff 48 -> 36 differing target instructions; the CreateFileA-failure vassert placement remains).",
"measurements": {
"target": {
"size": 752,
"relocation_count": 47,
"normalized_sha256": "f1c88310c585856cc88840f5dfa06dfa579231bca2e1f57c06cf7d00803c7a3f"
},
"base": {
"size": 752,
"relocation_count": 47,
"normalized_sha256": "54553053bda7f8841b99fe7fffbc35e44c8f97490433028ff6256b33840d2abd"
},
"objdiff_percent": 85.32895
}
},
{
"unit": "source/rasterizer/xbox/rasterizer_xbox_lights",
"function": "_rasterizer_sun_glow_draw",
@@ -854,25 +797,6 @@
"objdiff_percent": 96.43883
}
},
{
"unit": "source/rasterizer/xbox/rasterizer_xbox_shadows",
"function": "__rasterizer_environment_shadow_model_draw",
"class": "instruction-scheduling",
"evidence": "Fuzzy-parked with the complete typed January model-shadow draw path reconstructed from target disassembly and relocations, January's shader/debug/statistics fields, and the independently corresponding HaloCEA shader-model behavior. The retained routine verifies model shaders and geometry, chooses culling and alpha-test texture state from named flags, evaluates the texture animation with the cached model parameters, uploads the three vertex constants, draws the complete static triangle buffer, and updates model-shadow statistics. Target and candidate both occupy 688 padded bytes with all 48 relocation types and destinations, and objdiff reports 97.95 percent similarity. The remaining difference is two dependency-independent scheduling transpositions around one fld/fmul pair and the neighboring constant-buffer address materialization; no behavior or call is absent. Bounded natural declaration, initialization, and include-set probes did not change that scheduler fixed point. No volatile/register forcing, barrier, pragma, assembly, fake dependency, raw offset, forced inline, or nonsensical control flow is retained. Reopen only for authoritative January source/local records or a natural compatible-compiler donor explaining those two independent instruction placements.",
"measurements": {
"target": {
"size": 688,
"relocation_count": 48,
"normalized_sha256": "366f4471c47176709d6d1469a6ef998e1b248296a375b8cb8e8192c734341c9c"
},
"base": {
"size": 688,
"relocation_count": 48,
"normalized_sha256": "c81ae7e554a892d0f30600ed846a2585dbd904d74b628c472219ed77a5fb6489"
},
"objdiff_percent": 97.948715
}
},
{
"unit": "source/hs/hs_runtime",
"function": "_hs_evaluate_begin_random",
@@ -1006,25 +930,6 @@
"objdiff_percent": 95.47282
}
},
{
"unit": "source/objects/damage",
"function": "_object_damage_body",
"class": "unclassified",
"evidence": "Fuzzy-parked under the current house rule after reconstructing the complete body-damage stage in ordinary typed C. January COFF is the byte and ABI authority; independent HCEA source and Stian's PC lift corroborate occupied-only damage, falling-damage difficulty handling, friendly resistance, headshots, regional damage and destruction, deathless-player child traversal, body thresholds, localized and area effects, and output multipliers. The named material fields preserve the authenticated 0x48 layout and exact +0x28/+0x2C/+0x3C offsets; region flags and damage categories use genuine enum constants, and typed tag/object access macros own all subsystem retrieval. The target occupies 1360 padded / 1347 meaningful bytes with 43 relocations; the retained house-rule candidate occupies 1392 padded / 1389 meaningful bytes with 44 relocations and objdiff reports 88.00236 percent similarity. The extra lookup/relocation is the honest typed unit_get boundary after a generic child object type check. Fable's 1360-byte 98.31-percent probe used a repeated raw object_get downcast to unit_datum; it is explicitly rejected under the typed-access house rule and receives no exact credit. The separately reconstructed private helpers damage_effect_new_at_location and object_destroy_region are strict exact under their exact-tier symbol aliases. No fake matching, raw offset, false prototype, volatile/register forcing, barrier, optimizer pragma, assembly, undefined behavior, nonsensical branch, or fake context is retained. Reopen for authoritative January source/local records or a natural owner-safe accessor arrangement that improves code generation without violating typed access.",
"measurements": {
"target": {
"size": 1360,
"relocation_count": 43,
"normalized_sha256": "15116626eee783a53894c24ec0e9cb3cf2ad2a5734d7c3de99a15e2df4aa460e"
},
"base": {
"size": 1392,
"relocation_count": 44,
"normalized_sha256": "9e29dfe9abf1237a6bea6282926123f9fa15d7140977f2cfa1dedd6c68960412"
},
"objdiff_percent": 88.00236
}
},
{
"unit": "source/physics/collision_bsp",
"function": "_collision_surface_test_sphere",
@@ -1101,25 +1006,6 @@
"objdiff_percent": 98.3871
}
},
{
"unit": "source/cache/xbox_texture_cache",
"function": "_texture_cache_steal_memory",
"class": "unclassified",
"evidence": "Fuzzy-parked under the current house rule after restoring the complete public texture-cache memory-steal path in ordinary typed C. The retained function uses the owning texture_cache.h prototype, signed long page arithmetic independently confirmed by January's cdq/and/add/sar sequence, named page/guard constants, the genuine operational xbox_texture_cache_globals fields, the real XDK XPhysicalProtect declaration and PAGE_READWRITE/PAGE_READONLY constants, and explicit return. January and later HCEA/Pastudan/Stian semantic lifts corroborate the two guards, LRU resize, protection sequence, stolen flag, and returned writable range. Target and candidate are both 224 padded / 209 meaningful bytes with the same 16 relocation offsets, types and identities; objdiff reports 96.95652 percent. The only aligned emitted-code difference is January retaining the returned guard pointer in ESI while VC7 selects EDI for the natural candidate, plus the corresponding pop/move order. The fail-closed classifier returns UNKNOWN, so no recipe or exact credit is claimed. No declaration-order search, register/volatile forcing, pragma, barrier, assembly, raw offset, fake dependency, forced inline, undefined behavior, extra helper, COMMON owner, or point_from_line3d COMDAT is retained. Reopen only for authoritative January source/local records or a natural compatible-compiler donor explaining this final pointer-register allocation.",
"measurements": {
"target": {
"size": 224,
"relocation_count": 16,
"normalized_sha256": "7ffbda42b22d2c8bae6f2604509e6b0e39ae275fe38c011febed6f55e1d4620e"
},
"base": {
"size": 224,
"relocation_count": 16,
"normalized_sha256": "37670ded23c6273a3a573265fe14a008f6abb5fc2799c821368c77f7710cf076"
},
"objdiff_percent": 96.95652
}
},
{
"unit": "source/bink/bink_playback",
"function": "_bink_query_analog_controller_buttons",
@@ -1238,28 +1124,6 @@
"objdiff_percent": 84.64924
}
},
{
"unit": "source/sound/sound_manager",
"function": "_update_channel_for_looping_sound",
"class": "unclassified",
"evidence": "Complete typed looping-channel update reconstructed from January code/relocations and HCEA semantics. It covers properties, cache transitions, linked permutations, definition changes and crossfades. January is 1,360 padded bytes with 61 relocations; canonical is 1,376 with 61. No unique source-level mechanism explains the residual, so it receives zero exact credit. No compiler steering, raw offset, fake dependency, undefined behavior or nonsensical branch is retained. See docs/object_matching_logs/sound_manager_obj_idle_channel_spine_20260908.md. 2026-09-15 (next-150K lane wave n3a): re-baselined at ZERO strict credit and strictly closer than 1376/61. Size and relocation count are now both January-exact (1360/61); the residual is [sha] only. The row stays parked and fuzzy.",
"measurements": {
"target": { "size": 1360, "relocation_count": 61, "normalized_sha256": "457d122269e7e06c8b4a557f80eef434e3eb925aa8aaaa73d9e69bce635a6d78" },
"base": { "size": 1360, "relocation_count": 61, "normalized_sha256": "cb7925e4a26b946436b50b7c0f3172fdc975c1e48f7f9780f0d6d1cc8bc12f10" },
"objdiff_percent": 99.19809
}
},
{
"unit": "source/sound/sound_manager",
"function": "_sound_set_definition_end",
"class": "unclassified",
"evidence": "Complete queued-end-definition transition retained under its corrected semantic target name. January and canonical are both 208 padded bytes with all nine relocations; the code hash remains different. It uses the now-exact bounded channel-summary builder and the typed instance-limit flow. No compiler steering, raw offset, fake dependency, undefined behavior or nonsensical branch is retained. Zero exact credit. See docs/object_matching_logs/sound_manager_obj_idle_channel_spine_20260908.md.",
"measurements": {
"target": { "size": 208, "relocation_count": 9, "normalized_sha256": "06f507e27039b2a39192271221e6c7b703aee4d5ae45becdac84f3543095ec13" },
"base": { "size": 208, "relocation_count": 9, "normalized_sha256": "4f5a03ed0532981c792521f1ce7cdcc47eef5a3ee2d038caf65f57b1987675d9" },
"objdiff_percent": 91.884056
}
},
{
"unit": "source/rasterizer/rasterizer_lights",
"function": "_rasterizer_lens_flares_draw",
@@ -1275,11 +1139,11 @@
"unit": "source/ai/ai",
"function": "_ai_test_line_of_sight",
"class": "instruction-scheduling",
"evidence": "Complete typed PVS, collision, fog, expanded-source, and expanded-target visibility classifier reconstructed from January code/relocations and HCEA semantics. It preserves January's external helper schedule without emitting point_from_line3d. The retained source initializes collision_fraction before the fog path so no execution can read an indeterminate value. January is 1,008 padded bytes and candidate is 1,040, both with 50 relocations. Remaining differences are compiler-local frame/register scheduling. No fake dependency, raw offset, forced inline, undefined read, or nonsensical logic is retained. Zero exact credit. See docs/object_matching_logs/ai_obj_large_visibility_spatial_packet_20260909.md.",
"evidence": "Complete typed PVS, collision, fog, expanded-source, and expanded-target visibility classifier reconstructed from January code/relocations and HCEA semantics. It preserves January's external helper schedule without emitting point_from_line3d. The retained source initializes collision_fraction before the fog path so no execution can read an indeterminate value. January is 1,008 padded bytes and candidate is 1,040, both with 50 relocations. Remaining differences are compiler-local frame/register scheduling. No fake dependency, raw offset, forced inline, undefined read, or nonsensical logic is retained. Zero exact credit. See docs/object_matching_logs/ai_obj_large_visibility_spatial_packet_20260909.md. Remeasured 2026-09-25 after the /Od-attested real_math.h distance3d body correction (claude_fifty_objects_20260925.md, batch 2); size and relocations unchanged.",
"measurements": {
"target": { "size": 1008, "relocation_count": 50, "normalized_sha256": "aecb662c1c4868e27cbd680dba829bdedc994fc2f91911172ad86ecdfdec9b86" },
"base": { "size": 1040, "relocation_count": 50, "normalized_sha256": "61a3ce7f829fcafe2bf34705e9d6e2a0bb2be0d80257c1921cc134edb6eb0488" },
"objdiff_percent": 97.97885
"base": { "size": 1040, "relocation_count": 50, "normalized_sha256": "c524fe19529bfd4a72e95be52a875925bcc929d347b37471eb16b75bd393da78" },
"objdiff_percent": 97.91843
}
},
{
@@ -1352,22 +1216,22 @@
"unit": "source/main/main",
"function": "_main_frame_rate_debug",
"class": "unclassified",
"evidence": "Complete natural frame-rate diagnostic state machine retained from the 2026-09-09 Main packet. It uses the authenticated Main state, project types, and ordinary file/time APIs. The candidate is 528 padded bytes with 51 relocations versus January's 544 bytes and 52 relocations; the remaining code-generation difference is explicitly fuzzy. No barrier, volatile carrier, fake dependency, raw address, or exact-only branch is retained. Zero exact credit. See docs/object_matching_logs/main_obj_natural_state_and_render_packet_20260909.md. 2026-09-22: two January-attested corrections landed. The sample-index wrap is a SIGNED modulo in January (movsx/and 0x80000007/jns fixup), so NUMBEROF (size_t) was wrong and the divisor is a signed 8; this closed the whole 16-byte size gap and the candidate is now 544 padded bytes, exactly January's size. The slow-frame threshold literal is 0.036000000000000004 (__real@3fa26e978d4fdf3c), one ULP above the decimal 0.036 we had, so January's source folds an expression rather than writing the decimal; spelled 1.08/TICKS_PER_SECOND, which reproduces the constant exactly. The remaining residual is one duplicated framerate_sample_index load that January's jump-threading emits on the debug-true edge (51 vs 52 relocations) plus the register assignment that follows from it.",
"evidence": "Complete natural frame-rate diagnostic state machine retained from the 2026-09-09 Main packet. It uses the authenticated Main state, project types, and ordinary file/time APIs. The candidate is 528 padded bytes with 51 relocations versus January's 544 bytes and 52 relocations; the remaining code-generation difference is explicitly fuzzy. No barrier, volatile carrier, fake dependency, raw address, or exact-only branch is retained. Zero exact credit. See docs/object_matching_logs/main_obj_natural_state_and_render_packet_20260909.md. 2026-09-22: two January-attested corrections landed. The sample-index wrap is a SIGNED modulo in January (movsx/and 0x80000007/jns fixup), so NUMBEROF (size_t) was wrong and the divisor is a signed 8; this closed the whole 16-byte size gap and the candidate is now 544 padded bytes, exactly January's size. The slow-frame threshold literal is 0.036000000000000004 (__real@3fa26e978d4fdf3c), one ULP above the decimal 0.036 we had, so January's source folds an expression rather than writing the decimal; spelled 1.08/TICKS_PER_SECOND, which reproduces the constant exactly. The remaining residual is one duplicated framerate_sample_index load that January's jump-threading emits on the debug-true edge (51 vs 52 relocations) plus the register assignment that follows from it. 2026-09-24 (fifty-objects lane batch 4b): re-baselined after the reviewed zero-credit packet; see docs/object_matching_logs/claude_fifty_objects_20260925.md.",
"measurements": {
"target": { "size": 544, "relocation_count": 52, "normalized_sha256": "17546b1f02410f30e6fc728300d275c40d65bf6cb6a35a096a8d735f96512d83" },
"base": { "size": 544, "relocation_count": 51, "normalized_sha256": "c2c8fd8f775a01fc893be42b6223733665e2bf7845312f06a1725aa626af419b" },
"objdiff_percent": 94.51923
"base": { "size": 544, "relocation_count": 51, "normalized_sha256": "3c132f904203944a7f5f7c3b746a7458964f073c1fc4374aba0988bb5f3db4ff" },
"objdiff_percent": 98.65385
}
},
{
"unit": "source/main/main",
"function": "_main_update_time",
"class": "unclassified",
"evidence": "Complete natural Main timing and vblank-throttle reconstruction retained from the 2026-09-09 packet. It uses named fields in the authenticated rasterizer/Main state and the genuine profiler APIs. Target and candidate are both 1,440 padded bytes, but January has 117 relocations and the candidate has 118 with different normalized code. The rejected compiler-barrier experiment is not retained. Zero exact credit. See docs/object_matching_logs/main_obj_natural_state_and_render_packet_20260909.md.",
"evidence": "Complete natural Main timing and vblank-throttle reconstruction retained from the 2026-09-09 packet. It uses named fields in the authenticated rasterizer/Main state and the genuine profiler APIs. Target and candidate are both 1,440 padded bytes, but January has 117 relocations and the candidate has 118 with different normalized code. The rejected compiler-barrier experiment is not retained. Zero exact credit. See docs/object_matching_logs/main_obj_natural_state_and_render_packet_20260909.md. 2026-09-24 (fifty-objects lane batch 4b): re-baselined after the reviewed zero-credit packet; see docs/object_matching_logs/claude_fifty_objects_20260925.md.",
"measurements": {
"target": { "size": 1440, "relocation_count": 117, "normalized_sha256": "b32677fb6c4cc6bcd664b98bfc5cc962b68bdf65228a31759b52574becdf200b" },
"base": { "size": 1440, "relocation_count": 118, "normalized_sha256": "f4d7bb79a5351f50f1d0000d2b51234c1eb7d2abc44116032c537fd5f47cf3f1" },
"objdiff_percent": 62.637814
"base": { "size": 1440, "relocation_count": 117, "normalized_sha256": "ad56536c5eb119b981a9d0b89feb8bc57be3d72a21b4fcdfac4b4939db97ccdc" },
"objdiff_percent": 98.986336
}
},
{
@@ -1427,17 +1291,6 @@
"objdiff_percent": 76.04444
}
},
{
"unit": "source/camera/editor_flying_camera",
"function": "_editor_camera_set_position_and_roll",
"class": "unclassified",
"evidence": "Complete January/HCEA-authenticated editor-camera reconstruction with the HCEX-attested file-static state split. It uses correctly typed Euler storage and copying instead of representation-pointer punning; the house-rule-compliant shape remains 464 versus 432 padded bytes and receives zero credit. Three x87 operand-load-order blocks remain (cross-product i term and two dot-product terms); no source-level lever is proven. See docs/object_matching_logs/editor_flying_camera_obj_opus5_150k_w3_20260914.md and docs/object_matching_logs/editor_flying_camera_static_split_recovery_20260923.md.",
"measurements": {
"target": { "size": 432, "relocation_count": 30, "normalized_sha256": "a209aa201db09ef8600e6aa329e259b552acb2fe89532ea3bc18f4c007912b82" },
"base": { "size": 464, "relocation_count": 30, "normalized_sha256": "2115d1bd30c95e6bd4c50d82e32480e2b8760bcae21ced47946bfef4706ee9fa" },
"objdiff_percent": 79.92618
}
},
{
"unit": "source/camera/observer",
"function": "_observer_update_positions",
@@ -1453,22 +1306,11 @@
"unit": "source/interface/first_person_weapons",
"function": "_first_person_weapon_update",
"class": "unclassified",
"evidence": "Complete HCEA-corroborated first-person weapon state machine retained from the 2026-09-12 update cluster. It has the real callers, typed accessors, and four-player January loop, but the current production inline schedule remains non-exact and emits one extra relocation; no forced-inline or source-pressure device is admitted. This body and its non-owned helper schedule receive zero credit. See docs/object_matching_logs/first_person_weapons_obj_update_cluster_reconciliation_20260912.md.",
"evidence": "Complete HCEA-corroborated first-person weapon state machine with the four-player January loop. Wave 2 of claude/fifty-objects-20260925 applied three /Od-attested corrections. First, a key-frame result no longer advances the state: January @0x13c jumps past next_state on _animation_key_frame, and /Od 0x61fb2e has an empty then-arm. Second, both animation updates call the TU wrapper animation_update_render_only (/Od 0x61fb22/0x61fc59); no other January object references the wrapper, so it was inlined here. Third, turning is a real_vector2d aggregate (/Od RTC descriptor turning, 8 bytes). With the null guards removed and magnitude3d inlined, the function is strictly exact (scratch/w/first_person_weapons/final_exact.c). January omits both null tests after count ? TAG_BLOCK_GET_ELEMENT(...) : NULL, and the later /Od build omits them too. That is an authentic unguarded dereference and is owner-gated. The retained defined-behaviour body keeps both guards and keeps magnitude3d out of line, so no helper COMDAT is emitted while the caller is non-exact. Zero credit. Reopen on an owner ruling admitting the unguarded dereference. See scratch/w/first_person_weapons/LEDGER.md.",
"measurements": {
"target": { "size": 1536, "relocation_count": 59, "normalized_sha256": "912d95683f14ff39699c4a1056af92441a20ceeec0584007512ce26b5d70478d" },
"base": { "size": 1520, "relocation_count": 60, "normalized_sha256": "a112cf32c0e3baf805bd69c440b5beadbe51394f6a7f3161f61d4c470ea88292" },
"objdiff_percent": 93.48387
}
},
{
"unit": "source/math/geometry",
"function": "_convex_hull2d_perimeter",
"class": "unclassified",
"evidence": "Complete natural convex-hull perimeter reconstruction retained from the reconciled Fable geometry packet. Target and candidate share a 96-byte extent with no relocations but have different normalized streams; earlier exact-looking expression/helper variants had unresolved provenance and are not used as credit. The retained ordinary project-type body receives zero credit. See docs/object_matching_logs/geometry_obj_fable_clean_pair_independent_intake_20260906.md.",
"measurements": {
"target": { "size": 96, "relocation_count": 0, "normalized_sha256": "71292feb41a8e5d57e328751240458cb0b9678237d26b7f8be4ba7abab7f4b0e" },
"base": { "size": 96, "relocation_count": 0, "normalized_sha256": "22c6d46416fece30ca43252116be4b7f30369b3c2df152ea46ef045b031d073d" },
"objdiff_percent": 91.5
"base": { "size": 1520, "relocation_count": 60, "normalized_sha256": "ba39884c90f2ef0afbac711db26225e2b41d4fc0719a2afbe0a600883e2bf8ff" },
"objdiff_percent": 95.21506
}
},
{
+23 -3
View File
@@ -92,7 +92,7 @@
"group": "camera-static-sections",
"allow_incomplete_unit": true,
"credit_raw_size": true,
"reason": "January and the rebuilt object have identical 124-byte private .bss and 52-byte .data payloads, flags, alignments, and owner storage. The nine .data relocations resolve to the same image destinations. HCEX names the separate file statics and their definition order; the January .bss offsets corroborate their exact layout. Objdiff undercredits the two sections after the authentic private-state split. Credit raw sizes because its report scores .bss as 124 bytes, not the 128-byte alignment-derived padded extent. The remaining camera code function stays residual.",
"reason": "January and the rebuilt object have identical 124-byte private .bss and 52-byte .data payloads, flags, alignments, and owner storage. The nine .data relocations resolve to the same image destinations. HCEX names the separate file statics and their definition order; the January .bss offsets corroborate their exact layout. Objdiff undercredits the two sections after the authentic private-state split. Credit raw sizes because its report scores .bss as 124 bytes, not the 128-byte alignment-derived padded extent.",
"members": [
{
"symbol": "_is_scripted",
@@ -118,13 +118,13 @@
"section": ".data", "size": 52, "padded_size": 52,
"flags": 3224371264, "relocation_count": 9,
"normalized_sha256": "22486dfe6b955caf557fcbca418621432912c2d3012afd4e6031fe22b880d07b",
"owner": { "value": 0, "type": 0, "storage": 2 }
"owner": { "value": 0, "type": 0, "storage": 3 }
},
"base": {
"section": ".data", "size": 52, "padded_size": 52,
"flags": 3224371264, "relocation_count": 9,
"normalized_sha256": "22486dfe6b955caf557fcbca418621432912c2d3012afd4e6031fe22b880d07b",
"owner": { "value": 0, "type": 0, "storage": 2 }
"owner": { "value": 0, "type": 0, "storage": 3 }
}
}
}
@@ -480,5 +480,25 @@
"relocation_count": 228,
"normalized_sha256": "c1040d667c392fd772fab34335ade456dac49ae864a8200c4560df63cec186fd"
}
},
{
"unit": "source/objects/objects",
"symbol": "?section@?1??objects_update@@9@9",
"reason": "objects_update's function-static section table: January and the rebuilt object have identical 1,530-byte .data, relocation site/type and independently resolved string destination; objdiff 3.3.1's '$'-literal relocation-name defect undercredits it despite exact COFF section comparison. Whole object admitted after the Layer 1 provider repairs (action_charge, player_control, bored_camera, path_obstacles, decals) replaced hand-written NODUP helper copies with the genuine real_math.h inlines: all 121 functions strict exact, 225 symbols match storage/offsets and cachebeta.pdb publics, 26 candidate-only helper COMDATs byte-identical to January's selected copies and pair-linking in both orders.",
"measurements": {
"size": 1530,
"relocation_count": 1,
"normalized_sha256": "e94bb79478c95dce11c4a1f720a07ccbaa3a8d18b84acbe5605472d6f8635b74"
}
},
{
"unit": "source/units/units",
"symbol": "_magic_base_animation_seat_index",
"reason": "units' writable .data section (magic_base_animation_seat_index, unit_update_section, unit_update's last_time and base_seat_labels): January and the rebuilt object have identical 1,564-byte payload, 7 relocation sites/types and independently resolved string destinations (5 of the 7 base_seat_labels strings are selected in other January objects and pair-link cleanly); objdiff 3.3.1's '$'-literal relocation-name defect undercredits it despite exact COFF section comparison. Whole object admitted: all 189 functions strict exact; the 25 code_ placeholders carry their first-party names (HCEX_Release.pdb units compiland, Sept-2001 map, January atlas) and all 50 file-statics carry static storage per cachebeta.pdb publics, so 321 symbols match storage/offsets; 97 candidate-only surplus definitions (31 helper COMDATs byte-identical to January's selected copies, SDK tables, literals) pair-link with their selected providers in both orders; _point_from_line3d is referenced out of line by January's units.obj (owner ruling 20260921 #1).",
"measurements": {
"size": 1564,
"relocation_count": 7,
"normalized_sha256": "6ed61c39a648905cf23b12b916e2adadfe97b6e5aa492c29a14c7265809ef1a7"
}
}
]
+1047 -1038
View File
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,542 @@
# Fifty-object campaign lane (2026-09-25)
- **Worktree:** `C:\halo-worktrees\claude-fifty-objects-20260925`
- **Branch:** `claude/fifty-objects-20260925`
- **Base:** canonical `jonas/exact-pilots` **e9e62b78** (`git ls-remote` tip; the local
`origin/jonas/exact-pilots` ref was stale at 5361eeae and was not used).
- **Scorer frozen:** objdiff-cli **3.3.1** (sha1 3130e428). A fresh worktree silently
re-downloads the pinned 3.6.0 on its first ninja run. Under 3.6.0, this tip's `progress`
fails on the 3.3.1-authored `editor_flying_camera` grouped data entry. That is a
scorer-only effect and none of this lane's numbers use it.
- **Commits:** local only, never pushed. Canonical was not edited.
## Frozen baseline (e9e62b78, objdiff 3.3.1, full ninja)
| Measure | Value |
|---|---|
| Halo objects complete | 361 / 468 (All 479 / 833) |
| Halo meaningful code | 1,564,286 / 1,770,166 (7,425 / 7,574 functions) |
| Halo data | 2,572,828 |
| Board strict functions (stable snapshot) | 7,596 / 8,245 |
| Parks | 96 active, 0 stale, 0 invalid |
| Admission audit | 14 candidates, 0 contradicted, 7 rejected, 0 revoked |
| fake_match_scan | 25 review leads |
| pytest | 1,157 passed, 5 skipped, 26 subtests |
## Method
Eight-lane agent workflow. Each worker owned one `.c` (or one audit or provider task) and
worked only in `scratch/w/<unit>/` through `gate.py --source`. Each worker had to state an
evidence-backed hypothesis before each probe, citing January bytes and relocations, the
later /Od+RTC build, HCEX.pdb, cachebeta.pdb publics, or a decoded compiler law. Every
proposed function gain or object completion then went to an independent adversarial
reviewer, who re-ran the gates, the strip tests and the house-rule review. The integrator
applies only reviewed packets and runs the full chain for every batch: full ninja,
progress, the rename-stable 8,245-function diff, parks, admission audit, fake scan,
pytest and `git diff --check`.
## Batch 1
Result: Halo objects **361 -> 363**. Meaningful code **+2,769** (1,567,055).
Functions **+5** (7,430). Data **+1,530** (2,574,358). Stable diff +5 / 2,816 padded,
**0 regressions**. Parks 94 / 0 / 0. Admission 15 / 0 / 7 / 0. Fake leads 25 (unchanged).
pytest 1,157 / 5 / 26.
### Objects admitted (2)
| Object | Work | Evidence |
|---|---|---|
| tag_files/files_windows | new code: `_file_get_size` (224 padded) | /Od 0x8aef00 single-exit `result` and /Od 0x8ae8f0 named `GetLastError()` local in static `file_error`. Both are load-bearing; strip tests and a name-count sweep (0..40, plus unused-declaration controls) are negative. A C2 priority trace shows the inlined helper's extra web raises the error block's weight, which flips the ESI/EBX colouring to January's. Caveat: in the later build `error_code` is also used by FormatMessage. object_audit PASS; 0 PDB disagreements; provider link PASS. Park retired. |
| objects/objects | ownership: provider repair plus data entry | All 121 functions were already exact. The object was held by five hand-written NODUP helper copies in January's selected providers. **Layer 1 provider repair** removes the `#define x x_inline` renames and hand copies so the genuine real_math.h inline is used: `real_random` (action_charge), `signed_angular_difference` + `interpolate_scalar` (player_control), `valid_real_vector3d_axes2` + `valid_real_normal3d` (bored_camera), `point_in_sphere` (path_obstacles), and in decals `plane3d_from_point_and_normal`, `plane3d_negate`, `projection_sign_from_vector3d` and `triple_product3d`. Every provider row is unchanged, and each emitted COMDAT is byte-identical to January's selected section. Pair links pass in both orders. A board link census moves 250 -> 260 of 288 code-exact units to link-clean with 0 regressions, including six already-Matching cameras, items and scenario, which carried latent LNK2005s. The '$'-defect data entry pins the 1,530-byte `objects_update` section table. The `objects` rejection is retired. |
### Function gains, objects still incomplete (4)
| Function | Bytes | Source fact | Object blocker |
|---|---:|---|---|
| dead_camera `_dead_camera_new` | 288 | `dead_camera_constants` is file-static (absent from cachebeta publics). A static const aggregate's load is hoisted above pointer stores, and the /Od build fixes the pitch-before-timer statement order and the single-store unit phi. `static` is load-bearing and independently PDB-proven. | `_dead_camera_update` reproduces January's uninitialised `next_unit_index` read (the /Od build shows the later fix). Owner ruling, section 4. |
| actor_perception `_actor_perception_refresh_danger_zone` | 1,472 | The TU-private `REAL_MATH_EXTERNAL_POINT_FROM_LINE3D` rename is removed. The knowledge function uses the single-result form. Storage classes follow cachebeta publics. `_point_from_line3d` is emitted under ruling 20260921 #1 (January references it out of line here); the copy is byte-identical and passes the provider link. | blockage view-copy (owner packet section 5); `distance3d` / `distance_squared2d` provider |
| actor_looking `_actor_look_idle_find_prop` | 608 | `cos`/`sin` are spelled through the genuine real_math.h `cosine()`/`sine()` helpers. The newly emitted `_cosine`/`_sine` copies are identical to January's selected actor_combat copies and pair-link in both orders. | `_actor_look_update` register/slot residual |
| xbox_texture_cache `_texture_cache_steal_memory` | 224 | A named `stolen_address` local, used twice. An unused-local control stays residual. | texture_cache header / prototype / bitmap-schema debt (review items a-e) |
### Zero-credit ownership corrections (verified; no bytes move)
- **profile:** `compare_profile_sections`, `header_strings` and `format_strings` are external
and `profile_globals` is static, per cachebeta publics.
- **transport_endpoint_winsock:** `connection_thread_list_maintenance` and
`transport_endpoint_globals` are file-static.
- **actor_firing_position:** 14 file-static functions; the evaluator tables and sort globals are external.
- **game_engine:** 12 functions made static. The three hand-inlined copies of
`game_engine_get_type` become calls, and the /Od-attested
`game_engine_press_start_to_begin()` call is restored in `nonplayer_post_rasterize`
(/Od 0x5a1600).
- **real_math:** the invented `real_math_constants` aggregate becomes four genuine file
statics (HCEX real_math compiland).
- **model_animations:** five keyframe helpers are static.
These four objects (actor_firing_position, game_engine, real_math, model_animations) were function-
and data-exact after batch 1. The `distance3d` provider (fixed by Layer 2) and, for real_math, the
actor_combat `cross_product2d` copy kept them from admission. Batch 3 admits actor_firing_position
and model_animations. game_engine and real_math stay held (see below).
## Layer 2 (931ed8dc): shared real_math.h packet (owner ruling pending on one part)
**Landed and verified:**
- real_math.h `distance2d`/`distance3d` are `return square_root(distance_squared*(a, b));`, as in
the /Od build (no local).
- `project_point2d` uses short mapping locals and one `?:` store. It is /Od-attested and equal to
January's selected copy.
- action_vehicle, path_obstacles and decals drop their `#define x x_inline` renames and NODUP
hand copies.
- object_lights and players use the /Od caller spellings.
- Every emitted helper copy is byte-identical to January's selected section. Every provider row is
unchanged. The adversarial review (research `w/review_layer2/LEDGER.md`) reproduced the board
sweep exactly.
**Pending owner ruling: the lock/unlock_global_random_seed prototype move.**
The move takes these two prototypes from random_math.h, a 2026-09-04 reconstruction header, into
real_math.h's existing RANDOM_MATH.C section. The evidence for that placement:
- real_math.h already has that section.
- The supplied genuine random_math.c includes real_math.h and not random_math.h.
- main.c shows C4013 for both functions without it.
But the move is also declared-name-count compensation. The corrected helper bodies remove 4
declared names from every real_math.h consumer, and the move adds 2 back. The bytes decide only
that count, not which prototypes move:
- **Strip test (R6 V1).** The landed header without the move loses `_bitmap_copy`,
`_rasterizer_frame_statistics_draw` and `_get_edge_vertex` (7,376 B, all exact at base). It also
changes `_collision_move_point` (already residual).
- **Count-only subset (R10 V5/V6).** Moving {lock, get_random_seed} or {unlock, get_random_seed}
gives the same result.
- **The evidence-equal full move (R6 V2)** takes lock, unlock, get_random_seed and
random_seed_debug_log, and regresses 4 other exact functions: `_bitmap_2d_alpha_bleed`,
`_decals_delete_permanent_from_cluster`, `_rasterizer_frame_statistics_draw` and
`__rasterizer_model_transparent_geometry_submit`.
- get_random_seed still raises C4013 in network_client_manager.c.
**If the owner rejects the move, reverting the two prototype lines alone is wrong,** because it
regresses the three functions above. Batch 3 admissions now depend on the Layer 2 bodies:
- leaf_map and biped_limp_noodle (their `_distance3d` rejections were retired);
- actor_firing_position, path and model_animations (distance3d provider).
The review's fallback diff (`w/review_layer2/fallback_revert_except_players.diff`) no longer
applies after batch 3. A rejection therefore means choosing between two costs:
- lose the three count-tied functions (7,376 B); or
- revert Layer 2 and revoke those admissions, `_convex_hull2d_perimeter` and
`_player_set_action_result`.
**Reopen criterion:** move get_random_seed and random_seed_debug_log too, and retire
random_math.h, once the other declared-name gaps in real_math.h are recovered. Its
nonuniform_cubic_spline bodies are known decompiler output (v9..v20 locals), so the header's
count is already known to be off.
## Batch 3 (b807f9f6 header prerequisites, then the admission batch)
Result: Halo objects **363 -> 375** (+12). Stable diff: 4 new strict functions, 2,784 padded
(2,765 meaningful), **0 regressions**. Parks 89 / 0 / 0. Admission 11 / 0 / 5 / 0.
- **Fake scan: 26 leads.** The new lead is first_person_weapons' empty key-frame then-arm. It is
kept because the /Od build has the empty-then `jne; jmp` and January's bytes have the `cmp ax,1`.
It sits in a zero-credit, still-parked function.
- pytest 1,157 / 5 / 26; `git diff --check` clean.
- **Data +200** (path_structure_bsp tables) **+1,564** (units '$'-defect entry).
- **Scorer-only (not counted):** marking objects and units complete makes objdiff credit
+147 code / +7 functions for their internal `$L` labels.
**Header prerequisite commit b807f9f6 (consumer sweep: full ninja, 0 moved rows):**
- **objects.h.** Both bounding-sphere inline assertions report `OBJECTS_H_FILE`. It defaults to
January's relative `..\objects\objects.h`; object_lights defines the absolute spelling first.
January's string census has the relative string defined by action_vehicle (and referenced by
render_objects), and the absolute one defined by object_lights. action_vehicle loses its NODUP
hand copy. render_objects' `lighting_storage` becomes HCEX's static local of
`object_get_cached_render_lighting`.
- **path.h.** The opaque `__unknown140A0[0x5C]` span is HCEX's `struct path_result result`. The
punning store in path.c is gone. The 53 path.h consumers show 0 section differences.
### Objects admitted (12)
| Object | Work | Evidence (research `w/<slug>/`) |
|---|---|---|
| units/units | ownership | 189/189 already exact. 25 `code_` placeholders get first-party names (January atlas exact tier, Sept-2001 maps, HCEX_Release). The worker's `unit_verify_inventory` was wrong: the reviewer found it is `unit_vectors_are_valid`, so the amended patch is used. 49 static flags from cachebeta publics. The '$'-defect data entry is `_magic_base_animation_seat_index` (1,564 B). The `_point_from_line3d` rejection is retired under ruling 20260921 #1. `units_ownership`, `review2_units` |
| physics/collision_debug | house-rule fix | The hand-expanded helpers become the /Od-attested `magnitude3d`/`arccosine` calls. `_collision_debug_cube_vertices` is static. 8 surplus COMDATs, 3 D3D tables and 11 literals are identical and pair-link. `collision_debug` |
| render/render_objects | provider packet (b807f9f6) | Already 22/22 and data 100%. The object is admitted once the bounding-sphere provider and the static-local storage are fixed. `bounding_sphere` |
| camera/editor_flying_camera | new code + storage | `_editor_camera_set_position_and_roll` (432) uses the /Od RTC locals in declaration order. The /Od-attested dead `left` pair is kept, and the in-source comment says it is never read. The reviewer ruled it admissible: the /Od build has the same RTC name and statement pair, the assert anchors show zero drift, the effect depends on content, and a count-only control (R4) is not exact. The first euler3d->euler2d view cast has per-site /Od pass-through, a byte-inert strip test and a strictly exact caller. `editor_camera_speed(_steps)` are static per cachebeta; the grouped entry is re-pinned. `editor_flying_camera`, `review2_editor_flying_camera` |
| rasterizer/xbox/rasterizer_xbox_shadows | new code + storage | `__rasterizer_environment_shadow_model_draw` (688). The model_draw bare block is load-bearing and independently attested: the camo precedent cfa20176 and the same scheduling signature at rasterizer_xbox_models +0x12bd. The gap statics have descriptive names (disclosed) and are split as the align-4 .bss and HCEX's loose statics suggest. `rasterizer_xbox_shadows` |
| cache/cache_files_windows | new code | `_cache_files_open_cache_files` (752). The trailing cleanup follows the CreateFile/resize idiom of /Od 0x857520. That is an analog from game_state_pc.c, disclosed as analog evidence. The plain spelling needs no decoration, and equivalent spellings also match. `cache_files_windows` |
| objects/widgets/light_volumes | new code + storage | `_light_volume_render` (912). This is the reviewer's amended st6b: its declaration order follows the /Od frame, and only `external_scale` (address reaches an out-of-line call) is block-scoped. `light_volumes`, `review2_light_volumes` |
| ai/actor_firing_position | admission | Already function- and data-exact. The batch-1 storage packet is verified against cachebeta publics. The two view casts are /Od-attested (0x45bd00, 0x456860). `review_admit2` |
| ai/path | admission + header | path.h HCEX field (b807f9f6). 32/32, audit PASS, 10/10 surplus identical. `review_admit2` |
| units/biped_limp_noodle | storage | The invented `biped_limp_noodle_globals` aggregate becomes HCEX's `features` and `last_positions`, as file-scope statics. Static locals were refuted: they put `last_positions` first in .bss. The `_distance3d` rejection is retired. Advisory, not added: move_relax returns early twice without popping a collision user (authentic; no BUG comment). `review_admit2` |
| structures/leaf_map | house-rule fix | `normalize_three_dee` calls `magnitude3d` + `scale_vector3d`, as /Od 0x89fc90 does. The `_distance3d` rejection is retired. `review_admit2` |
| models/model_animations | house-rule fix | `animation_get_root_velocity` calls `vector_from_points3d`, as /Od 0x708160 does. `review_admit2` |
### Verified zero-credit or data corrections landed with batch 3
- **path_structure_bsp:** HCEX-named file-static `quantized_pathfinding_surface_widths/heights`
tables, +200 data. The object still needs the actor_combat `cross_product2d` ruling.
- **hud_nav_points:** `hud_update_nav_point_local_player` and `nav_point_data` are static per
cachebeta. The bounding-sphere blocker is now resolved; only `_custom_render_nav_point` remains.
- **first_person_weapons patch A:**
- `first_person_weapon_message` is static, with its prototype moved from the header into the
TU's private block.
- A genuine key-frame fix: the old `||` advanced the state on key frames.
- The TU `animation_update_render_only` wrapper is used; `real_vector2d turning`;
`sound_definition_index`.
- The `_first_person_weapon_update` park is re-baselined (95.21506).
## Batch 4 (f7cd2e72 header packets, 6e3e2d35 admissions and storage)
Result: Halo objects **375 -> 378** (+3). Stable diff: 3 new strict functions, 2,928 padded
(2,911 meaningful), **0 regressions**. Parks 86 / 0 / 0. Admission 11 / 0 / 3 / 0. Fake scan 26
(unchanged). pytest 1,157 / 5 / 26. `git diff --check` clean.
### Objects admitted (3)
| Object | Work | Evidence (research `w/<slug>/`) |
|---|---|---|
| interface/player_ui | shared-header layout recovery | Two edits in the header commit f7cd2e72. (1) `game_engine.h`'s opaque `game_variant` view becomes the recovered January layout: universal/ctf/slayer/oddball/king/race variant members, with HCEX names only where January's offsets attest the field. The game engines, network managers and playlist_profile use the new member names. (2) `player_ui_get_edit_playlist_profile` returns `struct game_variant *`. The reviewer found two C4133 warnings in ui_widget_game_data_input_functions that the worker missed; the reviewer's patch 07 fixes them. Sweep: 46 header consumers, 0 moved rows. `player_ui`, `review3_player_ui` |
| sound/sound_manager | new code + shared header | `_update_channel_for_looping_sound` (1,360) and `_sound_set_definition_end` (208) are exact. The shared header `sound_definitions.h` now declares HCEX's `short old_range_index`, landed together with the /Od 0x877810 single-exit definition; this avoids the prototype/definition mismatch the owner rejected earlier. The redundant `channel_state == _sound_channel_queued &&` term is load-bearing and /Od-attested verbatim (0x89a5a1). 45 statics per cachebeta publics. Sweep: 18 consumers. `sound_manager`, `review3_sound_manager` |
| rasterizer/xbox/rasterizer_xbox_dynavobgeom | view removal + storage | Genuine shared declarations replace the caller-local debug-options, window and pixel-shader views. The unlit draw uses the /Od-attested `vector_from_points3d`/`dot_product3d` calls and HCEX's `forward`/`zero_plane` locals. The HCEX function-static `warned` latch is not public in cachebeta. `MAX()` reproduces the /Od `?:` store. Nine D3DINLINE wrappers are static. The surplus `real_*_to_pixel32` COMDATs and SDK tables are identical to January's selected copies and link in both orders. January's own object has UNDEF references to them, so its TU emitted them. `dynavobgeom`, `review3_rasterizer_xbox_dynavobgeom` |
### Function gains and zero-credit packets landed in batch 4
- **damage:** `_object_damage_body` is exact (1,360; reviewer patch B+). Patch A would newly emit
the `collision_test_line`/`real_random` header COMDATs from a non-exact caller, so it is held (P1
class).
- **players:**
- Storage for three rows.
- COMMON tentative definitions of `players_globals`/`team_data`. January's `linker_common`
records 154-156 are players.c's, and HCEX has them as DataIsGlobal.
- **hs:**
- 839 January-static storage flags.
- The /Od-attested static helper `hs_enumerate_scenario_data` replaces 11 hand expansions (the
reviewer's amendment; its PDB storage disagreement goes to 0).
- The worker's `real_random_range` cleanup is held (P1 class).
- **hs_scenario_definitions:** first-party `static byte_swap_script_syntax_data` (the reviewer's
fallback). The 9-name data split stays held.
- **player_profile:** HCEX `profile_color_table`; the write-thread park is re-baselined
(88.40708 -> 88.451324).
- **main:**
- `main_exit` in the /Od 0x6aff90 shape, and 25 statics.
- January-resolved corrections: strict `>`, dead `requested_rate` store dropped, and
`best_interval` as the `" des %d"` argument.
- Parks re-baselined: `_main_update_time` 62.637814 -> 98.986336, `_main_frame_rate_debug`
94.51923 -> 98.65385.
### Process incidents in wave 3 (repaired, no tracked change)
Both happened inside reviewer agents, and both were repaired byte-exact before integration:
- A broken `&&` chain ran `rm -rf .git` in the worktree root. The reviewer restored the
worktree pointer file.
- `tools/campaign/volatile_scan.py --help` ran its hard-coded scan, because the script has no
argparse. The scan rewrote `source/hs/hs.c` to LF; the reviewer restored it from backup.
The integrator verified that the tree was clean and hs.c all-CRLF before batch 4. The worker
brief now forbids destructive commands outside a worker's slug and running unread repo scripts.
## Batch 5 (466698b8 hud_unit, cdc8ebd3 hygiene)
Result: Halo objects **378 -> 379** (+1). The stable diff is unchanged (0 moved rows) and there are
**0 regressions**. Parks 86 / 0 / 0. Admission 11 / 0 / 2 / 0. pytest 1,157 / 5 / 26.
| Object | Work | Evidence |
|---|---|---|
| interface/hud_unit | header inline + house-rule fix | January's 26 stack-sentinel sites across hud_draw, hud_unit, hud_nav_points, hud_weapon and motion_sensor share one inlined shape. Both asserts at each site carry one line literal, so it is a macro. January's 48-byte `_check_stack_buffer` has no relocations anywhere. The Sept-2001 map flags the helper as an inline COMDAT (`i hud_draw.obj`). `hud_draw.h` now carries the unchanged body as a header `__inline` plus the sentinel macro (`match_assert_stack_frame`; descriptive name, since neither /Od nor HCEX has the sentinel). That removes hud_unit's invented gotos. hud_nav_points and hud_weapon use it too; their callers are exact. motion_sensor keeps its copy, because its sentinel callers are residual (rule i). Also: the reviewer-amended names and storage, and retirement of the `_fast_ftol` rejection (identical copy; pair links pass both ways). `hud_stack_check`, `review4_hud_unit`, `review4_hud_draw` |
**Disclosed, pending owner ruling (P7).** The packet drops rasterizer_xbox_models' `#include
"interface/hud_draw.h"`:
- The include is genuinely dead: it was added by project commit 635bd83d, and the file uses no name
from the header. Removing it is raw- and warning-inert.
- It is load-bearing only as declared-name shielding. The inline adds about 9 names, which would
flip `__rasterizer_model_transparent_geometry_submit` (1,296 B).
- It is not the held add-declarations class (Layer 2, main header_swap), but it is a count effect
and is disclosed as one.
- If the owner rejects it, revert 466698b8. That loses the hud_unit admission; nothing else was
exact because of it.
Zero-credit hygiene in cdc8ebd3:
- geometry drops its NODUP `plane2d_distance_to_point` copy, which removes a latent
collision_bsp<->geometry LNK2005;
- collision_bsp `collision_bsp_usage_times` is static;
- rasterizer_lights P1: HCEX file statics with January's .bss layout;
- motion_sensor .bss symbol layout is fixed;
- rasterizer_xbox_models: five D3D wrapper rows are static.
Wave 4 found no new strict function. Every remaining residual in those lanes falls into one of
three groups:
- the authentic-bug owner class: crosshairs_draw, hud_update_weapon_local_player,
motion_sensor_update, rasterizer_lights_reset_for_new_map;
- the P1 class, where a new header COMDAT would come from a non-exact caller;
- scheduler/allocator ties where the stop rule fired (evidence in `results/wave4/`).
## Running tallies (vs frozen base e9e62b78, objdiff 3.3.1; at cdc8ebd3)
| Tally | Value |
|---|---|
| 1. Net newly COMPLETE Halo objects | **18** (361 -> 379); checkpoints: 10 at b9a8d587 |
| 2. New strict functions | **14** (stable diff; 0 regressions) |
| 3. New meaningful exact bytes | **8,775** |
| 4. New padded exact bytes | **8,864** |
| 5. New verified data bytes / admission-only closures | **3,294** data (1,530 + 1,564 + 200); 12 admission-only closures (objects, units, render_objects, actor_firing_position, path, biped_limp_noodle, leaf_map, model_animations, collision_debug, player_ui, rasterizer_xbox_dynavobgeom, hud_unit) |
| 6. Fuzzy improvements at zero credit | `_main_update_time` 62.64 -> 98.99; `_main_frame_rate_debug` 94.52 -> 98.65; `_player_profile_write_thread_proc@4` 88.41 -> 88.45; first_person_weapons A (95.21506); plus the FUZZY_IMPROVED rows in the research results |
| Scorer-only effects | +147 code / +7 functions (`$L` label credit from status flips). NOT taken: objdiff 3.6.0 would credit hs's 54,780 data bytes and actions' 2,404 at zero source cost |
| Regressions / revocations | 0 / 0 |
Contingent on the Layer 2 ruling: `_convex_hull2d_perimeter` (96), `_player_set_action_result`
(240), and the leaf_map, biped_limp_noodle, actor_firing_position, path and model_animations
admissions (see the Layer 2 section). Contingent on the P7 ruling: the hud_unit admission (see batch 5).
## Held for owner rulings (not landed; evidence preserved)
Every item's evidence is under `research/fifty_objects_20260925/` (`w/<slug>/` for worker and
review ledgers and patches, `results/wave*/` for the structured results).
| Item | Would unlock | Ruling needed | Reopen criterion | Evidence |
|---|---|---|---|---|
| actor_combat P1: remove the NODUP `cross_product2d` hand copy and restore the /Od-attested call in `_actor_aim_grenade` | **5 objects**: path_obstacle_avoidance, path_smoothing, real_math, collision_features, path_structure_bsp | Admit a January-owned header COMDAT, and a byte-inert /Od-attested view cast, whose caller is not yet strictly exact. The edit is byte-inert on all 81 sections (NODUP -> ANY only). | Owner admits P1, or `_actor_aim_grenade` goes strict-exact with the /Od helper calls | `w/cross_product2d/` |
| Layer 2 prototype move (landed) | keeps 3 count-tied functions | name-count compensation in a genuine owner header | see the Layer 2 section | `w/review_layer2/` |
| game_engine_king | 1 object | reproduces January's uninitialised `[ebp-4]` read in `_find_next_hill` (authentic bug class) | owner admits the BUG-commented read | `w/king/`, `results/wave2/REVIEW__source_game_game_engine_king.md` |
| glow `_get_particle_world_position` | 1 object | two authentic uninitialised array reads (also in the /Od build); ruling 20260920 may already allow them | owner admits the BUG comments | `w/glow/` |
| first_person_weapons patch B | 1 object (34/34) | authentic unguarded NULL dereference (January, /Od, HCEA 2011) | owner admits the BUG comment | `w/first_person_weapons/owner_gated_exact.patch` |
| dead_camera `_dead_camera_update` | 1 object | authentic uninitialised `next_unit_index` read | owner admits the BUG comment | batch-1 ledger |
| object_lights | 1 object | a surplus `_object_get_bounding_sphere` copy that differs from the selected copy only in its January-proven per-TU `__FILE__` literal; also needs data_gap's packet | owner ruling | `w/bounding_sphere/`, `w/data_gap/` |
| rasterizer_xbox | 1 object | a .bss split that adds 9 descriptive (non-first-party) static names | owner policy ruling | `w/rasterizer_xbox_audit/` |
| rasterizer_xbox_hardware_geometry | 1 object | choose `IDirect3DResource8_MoveResourceMemory` or `D3DVertexBuffer_MoveResourceMemory` for the no-op wrapper at 0x158460 (byte-identical) | owner picks A or B, or first-party evidence | `w/hardware_geometry/` |
| network_connection connect form | 2 functions | already under owner review; the reviewer recommends rejecting it (`success = TRUE;` steering) | owner packet | `w/network_connection/` |
| breakable_surfaces B1 | 1 object | the genuine `real_local_random` body adds one declared name board-wide and flips two fragile ties | declared-name gap recovery, or owner ruling | `w/breakable_surfaces_audit2/` |
| actions grouped data entry | +2,404 data | tools change to `semantic_progress.py` for grouped extents (scorer change; not taken in this lane) | owner or integrator accepts the verifier fix, or the scorer moves to 3.6.0 | `w/actions/verifier_combined_extent.diff` |
| render_debug | 1 object | an invented `render_debug_globals_definition` aggregate whose separate-static names are unrecoverable, plus six consumer-local `extern boolean debug_*` with no definer | owner ruling or new naming evidence; the helper/vehicle patch (a)-(c) is ready at zero credit | `w/review_admit2/patches/render_debug_genuine_helpers_and_vehicle.patch` |
| bitmap_drawing | 1 object | the unnamed 16-byte static stays held under the 2026-09-20 ruling | first-party name and type | `results/wave1/` |
| hud_unit | 1 object (+ hud_weapon/motion_sensor/hud_nav_points hygiene) | the stack-sentinel code is a hand-copied `check_stack_buffer` with two invented gotos (held class); January's asserts share one line (a macro), and the Sept-2001 map tags the helpers as inlines | a `hud_draw.h` `__inline check_stack_buffer` packet (wave 4), or an owner ruling; the reviewer-amended 01R + 02-04 are ready | `w/review3_hud_unit/` |
| rasterizer_xbox_profile | 1 object | invented TU-private aggregates (pad members; loose statics need new descriptive .bss names = held class), a /Od-attested `%s`-with-NONE crash path (BUG class), three load-bearing `volatile`s | owner rulings on names, BUG and volatile | `w/rasterizer_xbox_profile/`, `w/review3_rasterizer_xbox_profile/` |
| hs_scenario_definitions data split | 1 object | 9 file-static data names with no first-party attestation (1 invented) | owner ruling on descriptive names | `w/hs_scenario_definitions/`, `w/review3_hs_scenario_definitions/` |
| models .bss | 1 object | the only exact form is file-scope `= {0}` statics in reverse use order, contradicting the first-party static-local form; 3 of 4 names are lost | owner ruling or first-party names | `w/models_bss/` |
| transport_endpoint_set_winsock `net_startup_debug();` | storage only | a call needed only to make VC7 emit the static; its arity contradicts the 2001 `@net_startup_debug@4` evidence | owner ruling (+ signature) | `w/transport_endpoint_set_winsock/`, reviewer slug |
| player_profile R1-R3 | 3 functions | authentic defects: uninitialised serialised block, file left open, close after delete (BUG comments ready) | owner ruling (authentic-bug class) | `w/player_profile/production_owner_gated.patch` |
| main header_swap / main_crash | 1 function | declared-name-count compensation (Layer 2 class) / authentic NULL write | owner ruling | `w/review3_main/` |
| damage patch A / hs real_random_range cleanup | 0 (house-clean) | new header COMDAT emitted from a non-exact caller (P1 class) | same as P1 | `w/damage/`, `w/review3_storage_packet_/` |
## Research material
`research/fifty_objects_20260925/` is regenerated from the ignored `scratch/` by
`scratch/campaign/curate.py`. It holds every worker and reviewer ledger, the patches, and the
best non-exact candidates as `*.vs_tree.diff` against this tree. `.obj` files, split copies and
symbols.json copies are excluded, and the folder is exempt from EOL and whitespace conversion so
the patches stay byte-exact.
## Final handoff
- **Branch:** `claude/fifty-objects-20260925`. Local only; never pushed; canonical never edited.
- **Base:** e9e62b78, the `git ls-remote` tip of `jonas/exact-pilots` at lane start.
- **Tip:** the commit that adds this section. The last code commit is cdc8ebd3.
- **Scorer:** objdiff-cli **3.3.1** (sha1 3130e428) for every number. Not upgraded; 3.6.0 figures appear only where marked scorer-only.
### Commit order
| Commit | Content |
|---|---|
| bf937aac | Batch 1: files_windows, objects; 5 exact functions; ownership corrections |
| 931ed8dc | Layer 2: real_math.h helper bodies, provider hand-copy removal (**lock/unlock prototype move pending owner ruling**) |
| b807f9f6 | Header prerequisites: objects.h `OBJECTS_H_FILE`, path.h HCEX field |
| b9a8d587 | Batch 3: 12 objects, 4 exact functions |
| 954eebd2 | Research evidence (waves 1-2) |
| f7cd2e72 | Batch 4a: player_ui (game_engine.h/player_ui.h), sound_manager (sound_definitions.h) |
| 6e3e2d35 | Batch 4b: dynavobgeom; damage `_object_damage_body`; storage packets |
| 16542e46 | Ledger + wave-3 evidence |
| 466698b8 | Batch 5a: hud_unit via the hud_draw.h `check_stack_buffer` inline (**P7 pending owner ruling**) |
| cdc8ebd3 | Batch 5b: zero-credit storage and provider hygiene |
| eb33baec | Ledger: batch 5 |
| (this commit) | Owner queue, final handoff, wave-4/5 evidence |
Changed paths, excluding `research/`, as of eb33baec: 75 files, +3,717 / -4,304 lines. This commit adds the owner-queue doc.
- **config (5):** config.json, object_admission_rejections.json, parked.json,
semantic_data_matches.json, symbols.json.
- **Shared headers (10):** real_math.h, random_math.h, objects.h, path.h, game_engine.h,
player_ui.h, sound_definitions.h, hud_draw.h, first_person_weapons.h, light_volumes.h.
- **Source (59 .c files)** and **docs (2):** this ledger and the owner queue.
### Target vs result
**Target: 50 new complete Halo objects. Verified: 18** (361 -> 379). The evidence cannot support
50 without owner rulings. The measured owner queue
(`claude_fifty_objects_20260925_owner_queue.md`) adds 16 more on an all-yes, excluding the two
items canonical already superseded. That gives 34, still short of 50. Every remaining
self-landable residual in the pool is a documented tie, where the stop rule fired, or an
owner-gated form.
| # | Object | Kind of work |
|---:|---|---|
| 1 | tag_files/files_windows | new code (file_get_size) |
| 2 | objects/objects | provider repair (Layer 1) + data entry |
| 3 | units/units | ownership: first-party names, storage, data entry |
| 4 | physics/collision_debug | house-rule fix (/Od helper calls) + storage |
| 5 | render/render_objects | provider packet (objects.h) + static-local storage |
| 6 | camera/editor_flying_camera | new code + storage |
| 7 | rasterizer/xbox/rasterizer_xbox_shadows | new code + storage |
| 8 | cache/cache_files_windows | new code |
| 9 | objects/widgets/light_volumes | new code + storage |
| 10 | ai/actor_firing_position | admission (storage packet verified) |
| 11 | ai/path | admission + path.h HCEX field |
| 12 | units/biped_limp_noodle | storage (HCEX file statics) |
| 13 | structures/leaf_map | house-rule fix (/Od helper calls) |
| 14 | models/model_animations | house-rule fix (/Od helper call) |
| 15 | interface/player_ui | shared-header layout recovery (game_variant) |
| 16 | sound/sound_manager | new code + shared header |
| 17 | rasterizer/xbox/rasterizer_xbox_dynavobgeom | view removal + storage (**see policy conflict below**) |
| 18 | interface/hud_unit | header inline + house-rule fix (**P7 disclosure**) |
Checkpoints: 10 objects at b9a8d587. 20/30/40/50 were not reached. No 10,000-meaningful-byte
checkpoint was reached either: the total is 8,775.
### Tallies (vs e9e62b78, objdiff 3.3.1)
| Tally | Value |
|---|---|
| 1. Net newly complete Halo objects | **18** |
| 2. New strict functions | **14** (stable 8,245-function diff; 0 regressions) |
| 3. New meaningful exact bytes | **8,775** |
| 4. New padded exact bytes | **8,864** |
| 5. New verified data bytes / admission-only closures | **3,294** / **12** |
| 6. Fuzzy improvements at zero credit | `_main_update_time` 62.64 -> 98.99, `_main_frame_rate_debug` 94.52 -> 98.65, `_player_profile_write_thread_proc@4` 88.41 -> 88.45, first_person_weapons A 95.21506; more in `research/.../results/` |
| Scorer-only (not counted) | +147 code / +7 functions (`$L` label credit on status flips); objdiff 3.6.0 would add hs 54,780 and actions 2,404 data bytes at zero source cost |
| Regressions / revocations | **0 / 0** |
**Contingent credits:**
- Layer 2 ruling: `_convex_hull2d_perimeter`, `_player_set_action_result`, and the leaf_map,
biped_limp_noodle, actor_firing_position, path and model_animations admissions.
- P7 ruling: the hud_unit admission.
### House-rule review (source, headers, COMDATs, providers)
**Source.**
- Every landed packet passed an independent adversarial reviewer. Each reviewer checked house
rules 1-22 and the strip test, ran /Od declaration-order checks for any new scope, and verified
storage claims against cachebeta publics.
- Where a reviewer issued an amendment, the amendment is what landed:
- units (unit_vectors_are_valid);
- light_volumes (/Od declaration order);
- dynavobgeom (MAX);
- hs (`hs_enumerate_scenario_data`, no cleanup COMDAT);
- main (`main_exit` /Od shape);
- damage (B+, not A);
- player_ui (patch 07);
- hud_unit (01RS; P6 dropped).
- The fake-match lead count went 25 -> 26. The new lead is the /Od-attested empty then-arm in
the still-parked first_person_weapons update.
**Headers.** Every shared-header edit landed with a full ninja and stable diff, with 0 regressions:
- real_math.h/random_math.h;
- objects.h, path.h;
- game_engine.h, player_ui.h, sound_definitions.h;
- hud_draw.h;
- first_person_weapons.h.
Count-sensitive edits are disclosed (Layer 2 move, P7). No filler declarations landed.
**COMDATs.** Newly emitted header-inline COMDATs come only from strictly exact callers (rule i).
Each is identical to January's selected copy and passes provider links in both orders.
**Providers.** Four sets of NODUP hand copies were removed:
- Layers 1 and 2: 17 helpers;
- action_vehicle `object_get_bounding_sphere`;
- geometry `plane2d_distance_to_point`;
- hud_draw `check_stack_buffer`.
Known remaining NODUP conflicts:
- actor_combat `cross_product2d` (owner item P1);
- effects `real_local_random`;
- player_control `limit2d`;
- decals `plane2d_from_points`;
- items `object_get_type`.
**Disclosed judgement calls:**
- the Layer 2 prototype move and P7 (count effects, pending rulings);
- editor_flying_camera's /Od-attested dead `left` pair;
- the descriptive names of rasterizer_xbox_shadows' gap statics and of the
`match_assert_stack_frame` macro;
- cache_files_windows' analog evidence;
- rasterizer_lights' load-bearing `= {0}`. Canonical a595bbc2 later landed the same idiom for
models.
### Held and rejected items
Every held item has an apply-ready packet, a ruling question and measured unlocks in
`claude_fifty_objects_20260925_owner_queue.md`. The items with no object at stake are in the
held-items table above. Evidence for each is under `research/fifty_objects_20260925/`.
Items that are not to be retried without new evidence:
- the brief's do-not-reopen list (decals/physics, path_obstacles parenthesis, aim_grenade goto, hud_draw pragma, s3tc, render_sprite, incompatible providers, flags `x * height`);
- periodic_functions' DAG tie (~150 respellings);
- the residuals whose stop rule fired, listed in `results/wave3` and `results/wave4`.
### Process incidents
Both happened in wave 3, inside reviewer agents, and both were repaired byte-exact before
integration:
- `rm -rf .git` in the worktree root;
- a hs.c rewrite by `volatile_scan.py`.
No tracked change resulted. The worker brief now forbids both patterns.
### Expected integration conflicts (trial `git merge-tree` against canonical dbcea3d6)
Canonical gained 9 commits after e9e62b78. The two lanes' new Matching sets are disjoint:
canonical added models, hs_scenario_definitions and hs_globals_external. A resolved merge
therefore expects **382** Halo objects, subject to a full gate. Five files conflict:
| File | Resolution guidance |
|---|---|
| `source/ai/path_structure_bsp.c` | Both lanes added the same HCEX tables (canonical e208c87b). Take canonical's spelling `static real const ...[8]`, drop this lane's comment, and re-gate (bytes expected identical). |
| `source/hs/hs_scenario_definitions.c` | Take canonical's separate data definitions (Matching upstream). This lane's static `byte_swap_script_syntax_data` rename (6e3e2d35) is subsumed if canonical has it; keep canonical's symbols.json rows. |
| `source/rasterizer/xbox/rasterizer_xbox_dynavobgeom.c` | Both lanes replaced the local views with genuine owner types. This lane also has the /Od helper calls, HCEX locals, the `warned` latch and MAX, and admits the object. Canonical 641e1466 keeps a narrowed rejection instead. **Policy conflict, needs an owner decision:** see below. |
| `config/symbols.json` | Line-level overlaps on the dynavobgeom wrapper rows, the path_structure_bsp tables and the hs rows. Take the union; the rows are identical where both edited. Never re-serialise. |
| `config/object_admission_rejections.json` | Take the union of retirements. This lane retired objects, leaf_map, biped_limp_noodle, units, the hud_unit `_fast_ftol` rejection and dynavobgeom. Canonical retired hs_scenario_definitions, hs_globals_external and models. Canonical's new narrowed dynavobgeom entry follows the policy decision below. |
Canonical also changed `tools/campaign/gate.py`, `stable_verdicts.py` and `verdicts.py`, which
now identify functions by COFF type 0x20, so fastcall functions count. Re-take the stable
snapshot after merging before comparing function counts.
**Dynavobgeom and SDK-table policy conflict.** Canonical 641e1466 declined to admit dynavobgeom
for two reasons:
- the unreconciled `global_window_parameters` extern;
- selected-provider links "not a complete ordinary program link" for the three stock D3D SDK
tables every D3D8.h TU emits.
This lane admitted objects under the brief's criterion: identical to January's selected copy,
plus pair links passing in both orders. A census on this tree (built objects vs build/split) finds:
- **55 objects canonical already lists as Matching emit the same three surplus D3D SDK tables.**
- **9 of this lane's 18 admissions emit them too:** files_windows, objects, units,
collision_debug, rasterizer_xbox_shadows, cache_files_windows, biped_limp_noodle,
sound_manager and rasterizer_xbox_dynavobgeom.
- Seven Matching objects declare `global_window_parameters` the same consumer-local way as
dynavobgeom.
Two outcomes:
- If the owner applies canonical's stricter standard uniformly, those 64 objects need revisiting,
not only this lane's.
- If the owner keeps the brief's standard, canonical's narrowed dynavobgeom rejection should be
retired at reconciliation.
This conflict was found at handoff, after all admissions landed.
@@ -0,0 +1,502 @@
# Fifty-object lane: owner ruling queue (measured at cdc8ebd3)
Every item below is a packet that is ready to apply to cdc8ebd3 and has been measured. The worker ledger for each item recorded its gate, whole-object audit, provider-link and data measurements. None of these packets has landed. Each item is one yes/no (or pick) question. The "Would complete" column lists every object that becomes COMPLETE on a yes. The integrator then applies the packet in APPLY_ORDER and runs the full batch gate: ninja, the stable 8,245-function diff, parks, admission, fake scan, pytest and diff-check.
If every item were a yes, **18** more Halo objects would complete (379 -> 397; +36 over the lane base, before any regression the full gate might show).
Packets and evidence: `research/fifty_objects_20260925/w/owner_queue/<lane>/` (patches/, APPLY_ORDER, RULING.md, MEASURED.md, LEDGER.md). The integrator kit is in `research/fifty_objects_20260925/integrator_kit/`.
**Canonical moved during the lane.** `jonas/exact-pilots` is now at dbcea3d6, 9 commits past this lane's base e9e62b78. It independently admitted models (a595bbc2, file-scope `= { 0 }` statics, the same form as item "models .bss" option P1), hs_scenario_definitions (c5fe0f40/d99a8658, separate data definitions) and hs_globals_external (3da50d63). **The models and hs_scenario_definitions items below are therefore SUPERSEDED by canonical**: take canonical's version at reconciliation and drop this lane's packets. Excluding them, a yes on every remaining item completes 16 objects.
| # | Item | Would complete | Functions gained | Apply-ready |
|---:|---|---|---|---|
| 1 | actor_combat P1 | ai/path_obstacle_avoidance, ai/path_smoothing, math/real_math, physics/collision_features, ai/path_structure_bsp | - | yes |
| 2 | rasterizer_xbox .bss split with 9 descriptive static names (whole-object admission) | rasterizer/xbox/rasterizer_xbox | - | yes |
| 3 | hs_scenario_definitions data split with 9 descriptive static names (whole-object admission, rejection retirement) **(SUPERSEDED by canonical)** | hs/hs_scenario_definitions | - | yes |
| 4 | object_lights | objects/object_lights | - | yes |
| 5 | render_debug | render/render_debug | - | yes |
| 6 | game_engine admission pick | game/game_engine | - | yes |
| 7 | rasterizer_xbox_hardware_geometry | rasterizer/xbox/rasterizer_xbox_hardware_geometry | - | yes |
| 8 | models .bss **(SUPERSEDED by canonical)** | models/models | - | yes |
| 9 | dead_camera _dead_camera_update | camera/dead_camera | _dead_camera_update | yes |
| 10 | saved_game_files _saved_game_files_enumerate_available_to_local_player_index | saved games/saved_game_files | _saved_game_files_enumerate_available_to_local_player_index | yes |
| 11 | first_person_weapons patch B (authentic unguarded NULL dereference of first_person_weapon_animations; rebased onto cdc8ebd3 after patch A landed in b9a8d587) | interface/first_person_weapons | _first_person_weapon_update | yes |
| 12 | player_profile R1-R3 (uninitialised serialised 512-byte block; seek failure skips file_close; close(&file, NONE) after delete-while-open), rebased onto cdc8ebd3 after zero-credit packet A landed in 6e3e2d35; R3 BUG comment amended | saved games/player_profile | _player_profile_create_default_profiles_on_disk, _player_profile_write_thread_proc@4, _player_profile_new | yes |
| 13 | game_engine_king | game/game_engine_king | - | yes |
| 14 | glow _get_particle_world_position | objects/widgets/glow | _get_particle_world_position | yes |
Items landed but pending a ruling, which are not in the table above:
- **Layer 2** (931ed8dc): the lock/unlock_global_random_seed prototype move.
- **P7** (466698b8): the dead hud_draw.h include in rasterizer_xbox_models.
Both are described in the main ledger, with their revert costs.
Held items with no object at stake are listed in the main ledger's held-items table:
- main header_swap and main_crash;
- transport `net_startup_debug();`;
- the damage A and hs cleanup COMDATs;
- actions grouped data;
- breakable_surfaces B1;
- network_connection;
- rasterizer_xbox_profile;
- bitmap_drawing.
## 1. actor_combat P1: cross_product2d (remove the NODUP hand copy and restore the /Od-attested call in _actor_aim_grenade)
- **Question:** Should patch 01 be admitted while _actor_aim_grenade is still not strict exact (576 bytes vs January's 544)? Patch 01 deletes actor_combat.c's hand-written NODUP cross_product2d copy and its #define/#undef cross_product2d_inline rename, so the genuine real_math.h __inline emits January's actor_combat-owned _cross_product2d section. It also spells the /Od-attested call as cross_product2d((real_vector2d const *)&actor->input.facing_vector, &aim_vector2d) > 0.0f. All 88 actor_combat sections stay byte-identical; the only change is the _cross_product2d COMDAT selection, NODUPLICATES -> ANY. If YES, patches 02 and 03 (or 03b) also apply, and 5 objects are admitted (Halo 379 -> 384).
- **Would complete:** source/ai/path_obstacle_avoidance; source/ai/path_smoothing; source/math/real_math; source/physics/collision_features; source/ai/path_structure_bsp
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/p1_cross_product2d`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** 1. /Od fn 0x44ebb0 is _actor_aim_grenade (from its RTC names). At 0x44ed38-0x44ed45 it pushes &horizontal_aim_vector and actor+0x140 (facing_vector, a real_vector3d), then calls cross_product2d (fn 0x455250). That is a real call with the 3D facing vector as the 2D operand, and it is the only cross_product2d caller in the actor_combat range.
2. January's own bytes bind &facing_vector once (lea ecx,[edi+0x174] at 0xe4) and read both inlined 2D products through that register. This is the pointer-parameter signature. h.j*f.i-h.i*f.j at 0x105-0x120 is the only 2D cross product in the whole object.
3. January selected actor_combat's _cross_product2d. The genuine header inline emits a byte-identical copy, so P1 adds NO surplus. It removes a Rule-6 hand copy of the same class as the 17 removed by the Layer 1/2 repairs.
4. The only known EXACT diagnostic of aim_grenade (G1) is built on the /Od helper-call list, with _cross_product2d coming from the header.
5. The same cast expression is already landed in Matching objects: action_obey:2593 and :1259, and action_vehicle:395.
6. Status-quo cost: 10 objects carry an LNK2005 on _cross_product2d, and 5 of them are otherwise complete.
**Evidence against.** 1. The caller _actor_aim_grenade is not strict exact (576 vs 544, the held declaration-scope/goto residual). Both law (i) and the view-cast rule require a strictly exact caller, and byte exactness cannot tell the call apart from the hand expansion at this site.
2. The /Od build is a later revision; January's scope for horizontal_aim_vector differed from it (frame 0x24 vs 0x28).
3. P1 restores only cross_product2d. The adjacent /Od-attested dot_product2d(&facing,&h) stays hand-expanded, because restoring it would newly emit a _dot_product2d COMDAT from the non-exact caller.
4. Precedent risk: admitting a byte-inert header-COMDAT respelling in a non-exact caller. The proposal limits this to sections the same object already owns in January's split, with no new surplus.
**Still blocked / integrator notes.** Nothing in the packet is blocked. The integrator still owes the full batch gate: ninja, stable_verdicts sweep, parks, admission audit, fake scan and pytest. Workers may not run these, but since only actor_combat.c changes and it is byte-identical apart from the COMDAT selection, a zero-regression result is expected.
actor_combat itself stays NonMatching: aim_grenade and plan_grenade_trajectory are residual, and it has 4 pre-existing _actor_combat_*_inline externals with no January provider.
These objects lose the cross_product2d failure but remain incomplete:
- decals: 2 residuals; patch 03b optionally retires its now-premise-less _cross_product2d rejection
- collision_bsp: 3 residuals
- actor_looking: _actor_look_update
- geometry: 3 residuals plus a _plane2d_from_points conflict
- bipeds: 9 residuals plus an _object_get_type conflict
**Measured.** All measured at cdc8ebd3; the tree was clean before and after.
Patches: the wave-2 patch applies unchanged, because actor_combat.c did not change between 931ed8dc and cdc8ebd3. The patched file is byte-identical to wave-2 p1.c. git apply --check is clean for 01, 02 and 03 each, for 01+02+03 together, and for 01+02+03b together. A sandbox apply on the cdc8ebd3 blobs gives 497 -> 502 Matching statuses and 8 -> 7 rejections.
actor_combat gate --all: production, P1 and P1-strip all give 32 EXACT plus the same 2 residuals (_actor_aim_grenade 576/544 sha e9bba5b0; parked _actor_combat_plan_grenade_trajectory 288/5 sha ff9a7477, which equals the park base). A keyed raw-COFF diff over 88 sections finds exactly 1 difference between production and P1: _cross_product2d NODUP -> ANY. P1 against P1-strip: 0 differences, so the view cast is byte-inert. object_audit output is identical for production and P1: FAIL(2), the two residuals only, 0 of 60 symbols differ. /W3 warnings are the same 17 for P1; the strip adds only C4133. The fake scan finds 0 leads in either version.
The five objects (tree source, no change; each gate object is keyed-identical to build/base):
- path_obstacle_avoidance: 24/24 EXACT, audit PASS (52 symbols, 0 differ), PDB 0, surplus 0 of 11 not identical
- path_smoothing: 6/6, PASS (15), PDB 0, 0 of 10
- real_math: 84/84, PASS (134), PDB 0, 0 of 35
- collision_features: 20/20, PASS (30), PDB 0, 0 of 29; _projection_from_vector3d is IDENTICAL to January's decals copy and links PASS, so its rejection is stale
- path_structure_bsp: 6/6, PASS (13), PDB 0, 0 of 10; the HCEX tables are already landed and the split is current
provider_link against production actor_combat: each of the five is FAIL(1), on _cross_product2d only. Against P1 (provlink2 --subst=source/ai/actor_combat:P1 obj, both orders): all PASS, every row. objdiff 3.3.1 report shows all five at 100% code, functions and data. The admission audit, emulated with the five marked complete and the rejection from patch 03 removed, reports 0 contradicted and 0 revoked.
Board census: there are 11 _cross_product2d definers. All 10 non-actor_combat copies are SELECT_ANY and section_infos_equal to January's copy. All 10 FAIL the pair link against production and PASS against P1. After P1:
- actor_looking, decals and collision_bsp have no link failures left, but still have function residuals (15/16, 31/33, 27/30).
- geometry still fails on _plane2d_from_points and has 27/30 functions.
- bipeds still fails on _object_get_type and has 42/51 functions.
No symbols.json, header, parked.json or semantic_*.json change is needed.
## 2. rasterizer_xbox .bss split with 9 descriptive static names (whole-object admission)
- **Question:** Should rasterizer_xbox be admitted as Matching? The invented rasterizer_xbox_d3d_globals aggregate and its 23 #define redirects become 23 '= 0' file statics. 14 of the names are bare in January's IDirect3D*() strings. The 9 below are not first-party: - node_matrix_constants (+0x000): INVENTED. - bitmap_dimensions_non_blocking (+0x840): HCEX-convention stem (HCEX static-local 'dimensions'; the HCEX scope form is refuted for January's .bss order). - bitmap_dimensions (+0x844): HCEX-convention stem, same as above. - global_d3d_texture_render_primary[2] (+0x84C): convention stem (January global_d3d_{texture,surface}_<target> pattern plus the HCEX/Od _rasterizer_target_render_primary enum). - global_d3d_surface_render_primary (+0x854): convention stem; January attests the sibling _render_primary_z. - global_d3d_texture_render_secondary_z (+0x860): convention stem (render_secondary and the _z suffix are attested). - global_d3d_surface_render_secondary_z (+0x868): convention stem, same as above. - global_d3d_texture_render_primary_copy (+0x8A0): INVENTED '_copy' suffix. - global_d3d_surface_render_primary_copy (+0x8A4): INVENTED '_copy' suffix. Yes = apply patches 01-05. No = nothing lands; the object stays NonMatching with object_audit FAIL(22).
- **Would complete:** source/rasterizer/xbox/rasterizer_xbox
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/names_xbox_hs_scen`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** - Fake-stringification: January's strings name 14 of the globals bare, and no string contains 'rasterizer_xbox_d3d_globals', so the aggregate is invented.
- '= 0' declaration order reproduces January's .bss exactly. The strip test fails the audit on 23 rows, so the initialisers are layout-attested.
- There is no cachebeta public in 0x45E028..0x45E8CF, so every name is static.
- Every other audit row is first-party: HCEX static-local srcblend/destblend/blendop_table; the filthy_bitmap_defaults_initialize rename (atlas, 2001 map, HCEX); d3d_palette strings; January's D3DDevice_ flicker relocations.
- Counter-precedents: rasterizer_xbox_shadows was admitted with 2 disclosed descriptive gap statics; collision_debug's descriptive split landed for function credit; eb33baec landed the descriptive macro name match_assert_stack_frame.
**Evidence against.** - The 2026-09-15 owner ruling prefers no descriptive or invented static names in symbols.json; it held the env_fog (21) and models (13) .bss splits.
- The 2026-09-24 canonical held path_obstacle_avoidance because its 'BSS relabel lacks authenticated private identities'.
- 3 of the 9 names are invented outright (node_matrix_constants and the two _copy names).
- The packet's block comment says the error strings name 'these globals' bare, which overstates it for the 9. RULING.md proposes an optional byte-inert disclosure comment; it is not in the patch.
**Still blocked / integrator notes.** Only the owner's policy ruling on the 9 names. There is no technical blocker.
Rebase note: a NEW required config change at cdc8ebd3. 05_parked.json.patch re-baselines __rasterizer_model_draw, because models.c lost its hud_draw.h include and patch 02's prototype removal now flips a declaration-count tie in that parked fuzzy body. It has no strict effect and needs no source change.
Independent and not name-gated: the reviewer-approved rasterizer_xbox_debug static-wrapper symbols patch (object_audit FAIL(8) -> PASS, gate 18/18, split radius 1) is included under patches/independent/.
**Measured.** At cdc8ebd3 (HEAD later moved to eb33baec, a docs-only commit; patches re-checked clean there).
Apply checks:
- All 4 wave-2 patches apply as-is. I regenerated them with zero offsets as 01-04.
- The post-image .c equals the worker's cand_L.c, and the header equals the worker's header.
Split and gate:
- csplit with the patched config: of 833 objects, only rasterizer_xbox.obj changes. The control split equals build/split.
- gate, candidate plus patched header (shadow; the #error control proves it is used) vs the emulated split: 95/95 EXACT, and the _point_from_line3d guard passes.
Audits:
- object_audit (split override): PASS. 188 sections ok (.bss 2230 8-align, blend .rdata 108 4-align, .data 6) and 216/216 symbols. Production is FAIL(22).
- provider_link: PASS 15/15 in both orders.
- Storage vs cachebeta: 216/0 disagreements (production has 25).
- /W3 warnings identical to production; fake_match_scan 0.
Data: objdiff 3.3.1 identical for production and candidate: data 9048/9048, code 91/95 raw with the same 4 pre-existing under-credits.
Header blast radius (7 includers):
- 6 objects are byte-identical.
- rasterizer_xbox_models: the PARKED __rasterizer_model_draw moves (sha 89b0d7ea -> cbfa8585 at the same 5168 bytes / 348 relocs; objdiff 95.08453 -> 95.07629; strict status unchanged at 12/2).
- tools.parked_functions reports STALE 1 without a re-baseline, and active 2 / stale 0 with the new 05_parked.json.patch.
Names are load-bearing: reverting only the .bss rows gives gate 86/95 (9 reloc-identity residuals) and audit FAIL(15).
Combined application with item 2 (either route) is clean, and the post-images are cmp-identical when applied outside the repo.
## 3. hs_scenario_definitions data split with 9 descriptive static names (whole-object admission, rejection retirement) (SUPERSEDED by canonical; see top)
- **Question:** Should hs_scenario_definitions be admitted as Matching? The invented wrapper structs, #pragma pack, 11 __declspec(align(4)) and the field_table_alignment padding member become 18 separate January data definitions. This retires the source-layout-incomplete rejection and adds 9 non-first-party static names: - hs_scripts_block_fields (+0x018): HCEX-convention stem; <block>_fields plus January's public block name. - hs_globals_block_fields (+0x090): HCEX-convention stem. - hs_references_block_reference (+0x110): INVENTED; no convention exists for a TU-local any-tag tag_reference_definition. - hs_references_block_fields (+0x11C): HCEX-convention stem. - hs_source_files_block_fields (+0x17C): HCEX-convention stem. - data_array_header_bs_codes (+0x1CC): HCEX-convention stem; <name>_bs_codes plus January's own string 'data_array_header'. - data_array_header_bs_definition (+0x204): HCEX-convention stem. - syntax_node_bs_codes (+0x218): HCEX-convention stem; stem is January's string 'syntax_node'. - syntax_node_bs_definition (+0x240): HCEX-convention stem. Yes = route A (A01-A04) or route C (C01-C03 + A02-A04; C adds HCEX's MAXIMUM_HS_*_PER_SCENARIO enum to the shared header). No = nothing lands; the rejection stays; data stays 484/1112.
- **Would complete:** source/hs/hs_scenario_definitions
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/names_xbox_hs_scen`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** - It retires every objection the rejection cites: invented aggregates, pack/align directives and the padding member.
- .data alignment 8 arises naturally from the two field arrays of 64 bytes or more; the 0x8C..0x90 gap is VC7's per-object alignment pad.
- The candidate's .rdata literal order equals January's. This attests the block-before-fields definition order, and January's leaf_map shows the same idiom.
- The data credit is native, with no semantic_data_matches entry.
- 8 of the 9 names come mechanically from January's own names (public block names, byte_swap_definition name strings) via HCEX-attested Bungie conventions: 10 *_block_fields and 23 *_bs_definition in HCEX.
- Route C's enum is HCEX's exact enum, and January's hs_compile assert text attests it.
**Evidence against.** - The 2026-09-15 owner ruling prefers no descriptive or invented static names in symbols.json.
- hs_globals_external was rejected because a later build does not authenticate its names; here HCEX has no hs_scenario_definitions compiland at all, so no build authenticates these names.
- hs_references_block_reference is invented.
- The packet has no disclosure comment for the names; RULING.md offers an optional byte-inert one.
- Two new C4090 warnings, caused by the reconstructed tag_groups.h 'char **names' member type.
**Still blocked / integrator notes.** Only the owner's policy ruling on the 9 names; there is no technical blocker. The owner may rename any single name, for example #3, as a pure rename in A01+A02.
The function rename (the reviewer fallback) already landed in 6e3e2d35, so no fallback remains. A no leaves the object at the current rejection with 484/1112 data.
**Measured.** Rebase at cdc8ebd3:
- The worker's A, B and C2 no longer applied, because 6e3e2d35 landed the static byte_swap_script_syntax_data rename.
- A01 and C03 are the worker's final post-images (cand1.c/cand2.c, CRLF) diffed against the tree; they carry only the data split.
- A02 is the 9 inserted static rows only.
- D is split into A03 (config.json) and A04 (rejection removal).
- C1 is reproduced as C01 (header) + C02 (hs.c).
- Every patch passes git apply --check, alone and per route, and together with item 1. Post-images applied outside the repo are cmp-identical.
Split: csplit with the edited config changes only hs_scenario_definitions.obj (833 compared).
Gate, audit and storage:
- gate vs the emulated split: EXACT 224 _byte_swap_script_syntax_data 1/1 for both routes (route C with the shadow header and an #error control), guard passed.
- object_audit: PASS for both routes. 23 sections ok, including .data 628/628 with align 4/4 (January's 8-byte alignment), and 40/40 symbols. Production is FAIL(1) on alignment.
- Storage: 40/0; none of the 9 names is a cachebeta public.
- provider_link: nothing to link, PASS.
Data:
- objdiff 3.3.1: 1112/1112 native, +628 data bytes (production 484/1112, .data 92.773094).
- Without the 9 symbols.json rows, .data is 48.931007%, so the rows are load-bearing.
Route C consumer sweep (re-run because hs.c changed heavily in 6e3e2d35):
- hs 447/1 (1410 sections, 0 differ), hs_compile 61/3 (0 differ), hs_runtime 64/2 (0 section diffs; 64 $L names shift only).
- objdiff per-function maps are identical to build/base.
Other checks: /W3 adds 2 C4090 (byte-inert, disclosed); fake_match_scan 0. No parks involved.
## 4. object_lights: the surplus _object_get_bounding_sphere copy that carries its per-TU __FILE__ literal, plus data_gap's packet (4 statics, the should_render_lights call, the _lights_section entry)
- **Question:** Should we admit object_lights' surplus _object_get_bounding_sphere COMDAT as it is? It matches January's selected action_vehicle copy in size (144), normalized bytes, all 9 relocation sites/types and 7 of 9 targets. Only its two __FILE__ DIR32 targets differ: they point to "c:\halo\source\objects\objects.h", which is January object_lights' own 33-byte .rdata literal, instead of "..\objects\objects.h". Rule 6 as written ('byte-identical to the selected provider') cannot be met by any source, because object_audit requires that January-owned literal and VC7 emits it only when this COMDAT references it. YES: apply 01, 02, 03_YES, 04_YES and object_lights becomes Matching. NO: 01 and 02 can still land at zero credit, and optionally 03_NO_ALT (+1,541 data under allow_incomplete_unit).
- **Would complete:** source/objects/object_lights
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/objlights_render_debug`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** The January string census gives one owner per spelling. The c:\ objects.h literal is defined only in object_lights, and no relocation anywhere references it. The ..\ literal is defined by action_vehicle and imported by render_objects. _object_get_bounding_sphere is defined by action_vehicle and imported (UNDEF) by the other 8 users, object_lights included. NEW: of 9,420 January literals across all 833 split objects, exactly one has no referrer, and it is this literal, so its only possible referrer is object_lights' own discarded COMDAT copy. The /Od build asserts in 0x443f50 (object_get_bounding_sphere) with "D:\P4\Halo1\source\objects/objects.h", which shows the header used plain __FILE__. Link order is consistent: the image is in descending module order and action_vehicle (module 461) precedes object_lights (152), so a pick-first link keeps the ..\ copy January selected. The P2 control (a single-spelling header) loses the January-owned .rdata: the audit reports it MISSING and data credit drops by 1,100.
**Evidence against.** The rule 6 text is not met literally, so this is a new exception class: an authentic non-identical surplus COMDAT, proven by an orphan January literal. A whole-program relink that put object_lights first would select the c:\ copy. That cannot happen with January's link order and changes no January-owned section.
**Still blocked / integrator notes.** Nothing, once the owner answers yes. Advisory: object_lights also carries 5 consumer-local externs with no definer: debug_lights, debug_object_lights, light_data, light_cluster_partition, debug_rasterizer_light_count. They are the same class as render_debug (e). Precedent does not treat that class as a blocker (render_objects and scenario are Matching with such externs). Optional patch 07 resolves them as tentative definitions: byte-inert, and links clean against January's linker_common in both orders.
**Measured.** Every patch applies at cdc8ebd3. `git apply --check` is clean for each patch alone and for the YES set, the YES+(e) set, the NO set and 01-08 together. The patches are LF; applied one at a time in a throwaway repo with core.autocrlf=true, they reproduce the measured trees byte for byte. The data_gap object_lights.patch rebased unchanged (every hunk offset +4 by the landed OBJECTS_H_FILE define). symbols.json needs 4 in-place "static": true rows; the edited copy is ./symbols.json. Control: csplit with an unmodified config copy reproduced build/split 833/833. With the 4 rows (cfg_OL), only object_lights.obj changes. object_lights is 43/43 EXACT against the emulated split. object_audit PASS: 71 January symbols, 0 differ. The production object against the same split FAILs on exactly the 4 storage rows. pdb_storage: 0 disagreements. The other object_lights_rendering.h includers (render 13/13, render_objects 22/22) are objcmp-IDENTICAL to build/base. surplus: 12 candidate-only COMDATs, 11 identical; the only different one is _object_get_bounding_sphere (only the DIR32 rows at +37/+76 differ). provider_link PASS in both orders for all 33 surplus names. Mini objdiff 3.3.1: code 12294/12294, 43/43, data 1104/2645. Running tools.semantic_progress.apply_semantic_data_matches on the entry credits +1541 (2645/2645) for both the YES entry (unit complete) and the NO_ALT entry (allow_incomplete_unit). The negative control fails closed as expected. fake_match_scan: 0 leads. /W3: only the pre-existing C4133 and C4244 warnings. The full tree built from the patch files only (treeFINAL) gives the same rows: action_vehicle 17/17 and every includer identical.
## 5. render_debug: helper/vehicle patch (a)-(c), plus the held (d) invented render_debug_globals_definition aggregate and (e) six consumer-local extern boolean debug_*
- **Question:** (d) How should render_debug's 29,708-byte cache state be spelled? D1: seven separate zero-initialised file statics in the aggregate's member order, with descriptive names render_debug_cache_{strings,entries,game_time,entry_count,string_offset,entry_overflow_reported,string_overflow_reported}. This needs a symbols.json rename plus 6 static rows (lab/D1_OPTION_*.patch). D0: admit the existing aggregate, with its two opaque pad arrays, as it is. Or HOLD. Either yes, with 05 (+06) and 08, makes render_debug Matching. (e) is answerable without a ruling: January's definer is render_debug.c itself (tentative definitions the linker pooled into COMMON). The integrator only needs to confirm optional patch 06, bare `boolean debug_bsp;` etc., the same form as the objects.c precedent.
- **Would complete:** source/render/render_debug
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/objlights_render_debug`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** (e): The six are January linker_common COMMON records 50-55 (1 byte each), referenced only by render_debug.obj and hs_globals_external.obj. Pool oracle over all 242 records: 102 of 105 single-referencer records follow module order. hs_globals_external references 81 pool records and is the position-admissible owner of none of them. The six sit between render_objects' cluster and render's _render, and render_debug (module 87) is the only referencer in that range. The 2001 Bungie linker maps show the six exist exactly when render_debug.obj is linked: 08-15 beta has 5 toggles and no render_debug_structure_decals; 09-25 beta adds debug_permanent_decals together with its only user, render_debug_structure_decals; 09-25 retail has no render_debug.obj and none of the six, while render_objects/render records and other hs toggles remain. The later /Od build interleaves the six byte by byte with render_debug's own cache variables (0xf0afb0-0xf0afc0), which only one object's .bss can do. (d): January's short/gap/short/gap/short/bool/bool layout equals separate `= 0` statics; the aggregate needs pad members (the law (j) signature of an invented aggregate). In the /Od build, hs toggles sit inside the cache-variable span, so it was not a struct there.
**Evidence against.** (d): No first-party names exist anywhere (cachebeta publics, HCEX.pdb, the 2001 maps, the atlas), so D1 adds 7 descriptive .bss names, an already-held class. The zero initialisers are layout-evidenced, not text-attested. (e): The 2026-09-22 canonical reconciliation excluded a 116-definition COMMON wave because pool adjacency cannot prove TU ownership (rasterizer_frame_statistics counterexample). This packet uses per-symbol evidence (map presence/absence and /Od interleaving), but the /Od build is a later revision, so the integrator should confirm the evidence standard. Advisory: render_debug has 6 C4013 implicit declarations (ai_debug_render and collision_debug_render lack their header includes; 4 callees have no header prototype at all). Production has them too; render.c is Matching with 2 C4013.
**Still blocked / integrator notes.** (d) needs the owner's choice between D1 and D0 (the naming class). Without it render_debug stays NonMatching, though 05 and 06 can land at zero credit. The C4013 debt is a reviewer advisory that needs owner-header edits with a declaration-count sweep; it was not touched here.
**Measured.** The review_admit2 (a)-(c) patch (05) applies unchanged at cdc8ebd3; render_debug.c and vehicle_datum.h have not changed since 931ed8dc. render_debug: 36/36 EXACT, object_audit PASS (63/0), pdb_storage 0. surplus: 12 COMDATs, 0 not identical; the new _point_from_line3d is identical to action_charge's selected copy, and its callers are strictly exact. provider_link PASS in both orders. vehicle_datum.h is included only by actor_moving (31/36), physics (13/17) and vehicles (37/39), all objcmp-IDENTICAL to build/base; no header includes it. Mini objdiff 3.3.1 matches production: code 8235/9448, 34/36 on the known hidden_exact under-credit rows, data 30236/30236. (e) patch 06: 36/36, objcmp IDENTICAL; the only change is 6 external symbols UNDEF(0) -> COMMON(1). It links with January's linker_common.obj and with base hs_globals_external in both orders: 0 LNK2005/LNK1169, all six resolved (production leaves 12 unresolved lines). (d) lab (never lands): zero-initialised separate statics reproduce January's .bss exactly (0/0x400/0x7400/0x7404/0x7408/0x740A/0x740B, 29708 B). The whole-TU D1 lab is 36/36 and passes object_audit (69/0) against a lab split. Uninitialised statics give name-hash order and FAIL the audit, so the initialisers are load-bearing and layout-proven. 08 (status flip) applies cleanly. The treeFINAL rebuild from patch files gives the same rows. fake_match_scan: 0 leads.
## 6. game_engine admission pick: the motion-sensor copy in game_engine_player_get_custom_motion_sensor_positions. (A) the in-loop alias `struct netgame_goal *goal = &global_goal[goal_index];` vs (B) F07's /Od-attested `positions[count] = *(real_point2d *)&global_goal[goal_index].position;`. Each is packaged as a complete admission packet: the review4 house-clean (F01R/F01RB, F02, F03aR, F03b, F04) plus the status flip. There is also a variant without the one-consumer focused headers.
- **Question:** Q1: game_engine is 180/180 strict exact and passes the whole-object audit either way, and A and B give byte-identical objects. Do you admit (A) the in-loop alias, which the /Od frame at 0x5a20e0 does not have and which is load-bearing, or (B) the /Od-attested view copy, which is load-bearing and so fails the byte-inert condition of your rule-24 narrow admission? Or neither, and game_engine stays held? Q2 (only if Q1 is yes): the 2026-09-21 'do not invent a one-consumer header' ruling applies to F02. Do you take F02 as is (4 new headers), or the variant_no_single_consumer_headers packet? The variant puts the hudg, itmc and netgame layouts back in game_engine.c, their only consumer, and is byte-identical in all 447 TUs. It keeps hud_nav_points.h (one non-owner consumer), because moving the nav-point trio into the genuine hud.h breaks _populate_statistic_buffer through a declared-name-count tie. Q3 (disclosure): admitting game_engine also admits the pre-existing debug_player_color anonymous aggregate with `word pad` members, and the descriptive .bss names _game_engine_teleport_message_ticks and _game_engine_teleport_flash_fade_function. HCEX has these values as static locals of game_engine_update_teleporter. Do you accept them, or are they the held invented-aggregate / descriptive-static class?
- **Would complete:** source/game/game_engine
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/pick_game_engine`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** For B: /Od 0x5a20e0 does one 8-byte aggregate copy. The index is computed once (shl ecx,5) and x and y move as an integer mov pair; this build uses integer movs for struct copies (get_goal_position at 0x59d5b9) and movss for float scalars. The frame is only count, player and goal_index. real_point2d is a layout prefix of real_point3d, and HCEX's real_point3d has no 2D member. The caller is strictly exact. January's assert strings stringify Bungie view casts in actors, actor_looking, actor_moving and observer. For A: plain C, no cast. The same alias idiom is first-party in this TU: /Od goal_matches_player at 0x5b23c0 keeps a goal pointer slot [ebp-8] = &global_goal[i].
**Evidence against.** Against A: the /Od frame has no goal pointer slot and copies an aggregate rather than components. The alias is load-bearing (strip -> RESIDUAL). That makes it a non-/Od-attested, load-bearing named local: an owner-gated class, which review3 listed as blocking OBJECT_COMPLETE. Against B: rule-24 narrow admission requires a byte-inert cast, and this one is load-bearing: its strip gives the same RESIDUAL. Ruling 2 of 2026-09-21 keeps rule 24 strict for new punning casts. Both options also inherit the Q3 construct, which the owner has not ruled on: an anonymous debug_player_color aggregate with word pad members. HCEX has those values as static locals red/green/blue/duration of game_engine_update_teleporter. The 2020 /Od build puts unrelated globals between debug_player_color and the flash values.
**Still blocked / integrator notes.** Needs the owner's pick for Q1 (or hold). The focused-header ruling (Q2) must be settled: F02 as is, or the TU-local variant, which still needs hud_nav_points.h. The debug_player_color / descriptive-static disclosure (Q3) must be settled too. Whoever lands this must re-sweep it together with any concurrent owner-queue packet that edits game_engine.h, player_control.h, hud_messaging.h, sound_classes.h, hud.h or game_engine_king.c. F06 (optional, not packaged) edits game_engine_king.c, a separately held item, and does not fit the NP variant. Full ninja plus stable_verdicts remain the official gate. Nit, not changed: the dead local local_player_count shadows the public function local_player_count().
**Measured.** All at cdc8ebd3; the tree was clean before and after. git apply --check is clean for all 19 patch files alone, and for each packet's full set in one call: optionA, optionB, NP-alias and NP-view-copy. F07 alone does not --check at cdc8ebd3, so optionB ships a standalone F01RB (= F01R + F07, byte-checked). Rebuilding each packet from a HEAD export gives exactly the shadow that was measured. Compiles use the production cflags, cwd = shadow root, and coff_compare.section_infos_equal: optionA, optionB and both NP variants are 180/180 EXACT with 0/302 sections changed vs build/base; the control is 180/180. The optionA and optionB objects are identical: same order and no content difference. Full 447-TU sweep with canonical COFF: control head vs build/base is 447/447 SAME. optionA and optionB each give 446 SAME plus game_engine. game_engine differs only in three ways, all EXACT: always_invis is emitted later; the csplit alias `_global_goal+0x400` replaces `_global_variant+0` at 2 relocations; and $L numbering changes. NP vs optionA: 447/447 SAME. The 37 game_engine.h consumers all fall inside the sweep. Admission battery (every candidate): object_audit PASS with text identical to production; pdb_storage 0/269 disagreements; surplus 11 COMDATs, all identical; provider_link PASS, both orders, 39 surplus externals. objdiff 3.3.1 mini project: 27922/32397 code, 173/180 functions, 3792/3792 data, 0 deltas vs prod. audit_semantic_matches accepts 180/180, with the same 7 semantic-coff-only functions as production. tools/audit_object_admission already lists game_engine as a candidate with 0/0 gaps, so the status flip will not be revoked: Halo objects 379 -> 380. /W3: no new warnings. The C4013 console_printf warning and two long->short warnings are gone. Strip test (lab): with neither the alias nor the cast, the function is RESIDUAL, because the induction-variable base shifts to position.y (+4). Both spellings are load-bearing. Ruling variants: the three single-consumer layouts TU-local (vTUnp) are byte-identical everywhere. The genuine broad headers all fail. item_definitions.h moves hud_weapon's _render_weapon_hud. scenario_definitions.h plus the F06 siblings is a strict regression in ai_communication (_ai_communication_update_speech_timers) and makes the parked _ai_test_ballistic_line_of_fire stale. hud.h costs game_engine.c 23 names and flips _populate_statistic_buffer (count oracle, lab only: K=8-12 flips it). hudg cannot move without rewriting 7 HUD TUs.
## 7. rasterizer_xbox_hardware_geometry: spelling of the no-op MoveResourceMemory wrapper at 0x158460 (A = IDirect3DResource8_MoveResourceMemory vs B = D3DVertexBuffer_MoveResourceMemory)
- **Question:** January emits two static no-op wrappers: 0x158450 = _D3DResource_MoveResourceMemory@8, and 0x158460 = one directly called MoveResourceMemory forwarder. Which spelling should rasterizer_vertex_buffer_new use for that one call? A: IDirect3DResource8_MoveResourceMemory((D3DResource *)d3d_vertex_buffer, D3DMEM_VIDEO), with 0x158460 named _IDirect3DResource8_MoveResourceMemory@8. B: D3DVertexBuffer_MoveResourceMemory(d3d_vertex_buffer, D3DMEM_VIDEO), with 0x158460 named _D3DVertexBuffer_MoveResourceMemory@8. The argument and the position after the vertex-buffer Unlock block are byte-inert, and a 3-line comment in the source says so. Either A or B completes the object; if you reject both, the hg0 fallback is zero-credit hygiene only.
- **Would complete:** source/rasterizer/xbox/rasterizer_xbox_hardware_geometry
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/pick_hwgeom_models/patches`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** Both options: January's emitted static wrappers prove the call exists (static-COMDAT law, whole-image ret-8 census). The d3d8.h emission order excludes every other forwarder spelling. A only (style census, not byte proof): all 33 of January's Halo-side resource lib calls go through IDirect3D*8_ compat wrappers, with 0 native resource-level spellings. January emits _IDirect3DResource8_Release@4 in progress_bar, and the tree has IDirect3DResource8_Release((D3DResource *)...) at progress_bar.c:1812. This TU already uses typed compat names. B: needs no cast.
**Evidence against.** No first-party evidence names the spelling. The /Od build is the PC DX9 port, HCEX links no hardware_geometry code, and the October 2001 beta has the same pair without names. D3DMEM_VIDEO and the call position are chosen and byte-inert; the comment discloses that. A needs an XDK-convention (D3DResource *) cast; it is byte-inert, since B produces the same bytes without it.
**Still blocked / integrator notes.** Nothing, once the owner picks A or B. If neither, the object stays NonMatching: the two _code_ placeholder stubs remain (house rule 7 names; external where January is static).
**Measured.** Rebase: the source file is unchanged at cdc8ebd3 (blob c2bb9095) and symbols.json rows 5813-5825 are unchanged. The wave-2 patches apply clean. Only the symbols patch index line was refreshed, and a config.json status flip (NonMatching -> Matching) was added. No real change was needed. csplit: split_ctl == build/split (833/833); split_hgA and split_hgB each change only rasterizer_xbox_hardware_geometry.obj. A vs split_hgA: gate --all 17/17 EXACT (_point_from_line3d guard passed); object_audit PASS (25 symbols, 0 differ); pdb storage 0 disagreements (production has 13); 0 candidate-only code COMDATs; provider_link PASS in both orders (6 pre-existing rows: 3 pooled literals + 3 SDK tables); fake_match_scan 0. B vs split_hgB: identical results. cand_A and cand_B have byte-identical Halo sections (vertex_buffer_new 5442bf32, triangle_buffer_new e90e066b) and both 0x158460 bodies are sha 2a2f950f; only the name differs. objdiff 3.3.1 mini report: 898/898 code, 584/584 data for production, A and B, so there is no credit change. hg0 fallback vs split_hg0: 17/17, audit PASS, link PASS, but pdb shows 2 disagreements (the external _code_00158450/460 stubs), so it is not admissible. verify_apply.py: every packet passes git apply --check in the repo and sequential scratch application, and the post-images equal the measured files byte for byte. This also holds stacked with each models packet.
## 8. models .bss: file-scope `= {0}` statics in January (reverse-use) order (P1; variant P2 with fv as the HCEX static local) vs the first-party in-block static-local form (SL) (SUPERSEDED by canonical; see top)
- **Question:** Do you admit models.obj with load-bearing `= {0}` initialisers on the render_model defaults, declared in January's .bss order? There are two forms. P1: four file-scope statics fv, change_colors, effect, region, each `= {0}`. P2: default_function_values as an uninitialised static local at render_model function scope (as HCEX records it), plus the other three as `= {0}` file statics. Both contradict the later first-party form: HCEX -lines and the /Od build declare all four uninitialised inside their if (!param) blocks. The strip test fails: without `= {0}` the layout falls back to hash order. YES (P1 or P2) completes models and retires the bss-symbol-layout-mismatch rejection. NO means landing models_C1 (zero-credit storage fix) or models_optSL (first-party form, measured FAIL(4)); the object then stays blocked until first-party names for the three other defaults are found.
- **Would complete:** source/models/models
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/pick_hwgeom_models/patches`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** Measured VC7 law: uninitialised statics are laid out in decorated-name hash order, then `= {0}` statics in declaration order. P1 and P2 are the only name-independent exact forms. Project precedents for a load-bearing `= {0}` that fixes .bss layout: rasterizer_lights (cdc8ebd3), and the player_ui player1_profile_path `= { 0 }` (f7cd2e72); source/ has 69 column-0 static `= {0}` definitions. P1 adds no new names, and program semantics are unchanged. P2 keeps HCEX's first-party static-local storage for default_function_values.
**Evidence against.** HCEX.pdb and HCEX_Release.pdb record default_function_values as a Static Local of render_model, not a file static. HCEX -lines (the +2-line gaps at 124/128..154/158) and the /Od profile-scope sizes show all four declared inside their if-blocks, uninitialised. The P1/P2 declaration order is the exact reverse of the use and in-block order, with no first-party support. The strip test fails (the redundant-zero-initialiser class). The standing rejection's reopen clause forbids matching-only constructs. Three of the four names are third-party (halocea).
**Still blocked / integrator notes.** If the owner says NO: models stays NonMatching. The first-party SL form gives offsets 32/48/96/0 vs January's 0/16/64/104, because January's order depends on the decorated-name hash of three lost names, and hash-steering names are forbidden. Not built in this wave (it would be a new form): P2 with fv inside its if-block, which by the law should lay out identically.
**Measured.** Rebase: models.c/h are unchanged since 954eebd2. The wave-3 C1 patches apply clean and reproduce new/models.{c,h}; OWNER_OPTION_P1 reproduces new/models_owner_option_P1.c. No real change was needed. The packets were rebuilt as standalone per-file patches, because a stacked models.c pair fails `git apply --check` in-repo under core.autocrlf. SL and P2 are the wave-3 lab L1 and P2 forms rebased on C1 (the diff vs the labs is the C1 hunks only). csplit: split_mC1, split_mSL and split_mP2 each change only models.obj. Gate --all is 12/12 EXACT for every candidate; render_model is identical in all four (2144 B, sha bb666c87, 98 relocs). object_audit: production FAIL(4) (offsets 72/88/32/0); C1 FAIL(4); P1 PASS 30/0 (0/16/64/104); P2 PASS 30/0 (0/16/64/104); SL FAIL(4) (32/48/96/0). pdb storage is 0 for C1, P1, P2 and SL (production has 2). surplus_identity: _negate_vector3d and _set_real_point3d are IDENTICAL and pre-existing. provider_link PASS (11 rows) and fake scan 0 for every candidate. models.h shared-header sweep (shadow header, #error control failed every compile, so the shadow was used): first_person_weapons 33/34 (same residual), model_animations 32/32, objects 121/121, render_objects 22/22, render_sky 1/1, all IDENTICAL between the current and C1 headers. objdiff 3.3.1 mini report: 5038/5038 code and 2368/2368 data for every candidate (credit-neutral). The combined hwgeom+models symbols.json changes are independent (csplit), and verify_apply.py passes ALL OK.
## 9. dead_camera _dead_camera_update: authentic uninitialised next_unit_index read (source/camera/dead_camera.c)
- **Question:** Can source/camera/dead_camera.c declare `long next_unit_index;` with no initialiser and no else arm, under the original-bug policy? That deletes the current defined stand-in `else { next_unit_index = (long)result; }` and its comment. When no next player is found, the test then reads the indeterminate local. The methodology-template BUG comment in RULING.md would be added. YES/NO.
- **Would complete:** source/camera/dead_camera (4/4 strict, 6/6 sections, 6/6 symbols, PDB storage 0, surplus identical, provider_link PASS 19/19, data 100%)
- **Functions gained:** _dead_camera_update (1,248 padded / 1,235 meaningful)
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/ub_dead_camera_saved_game`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** - January +0x176 `mov eax,[ebp+0x10]` is the fallback of the phi, and the slot is never written before it. Production's defined stand-in differs by exactly this one instruction (`mov eax,esi`).
- n1 20260915: every defined initialiser costs an extra store that VC7 never elides.
- The later /Od build (0x522c90) adds `= NONE` (mov [ebp-8],-1) at the top of the block with no else arm, i.e. the fix was an initialiser. The HCEA donor also uses -1.
- /W4 C4701 on the candidate.
- Landed precedent: ai_communication.c:2527 (7a1d0d91) leaves cause_point uninitialised under the same BUG template.
- The whole-object audit is clean.
**Evidence against.** - Earlier decisions rejected this exact read: dead_camera_obj_codex_checkpoint ('Recreating the uninitialized read is forbidden'), legacy_residual_donor_truth_audit_20260913 (rejected the 1,235-byte donor), and opus5_150k_w1 (skipped as UB). They were taken under briefs that forbade UB, not as owner rulings under the original-bug policy.
- The read is formally undefined C. The current stand-in is defined and has the same January runtime value.
- There is no _RTC_UninitUse attestation, because the /Od build contains the fix.
- House rules 20 and 21 and the source-credibility gate allow only the narrow original-bug exception.
**Still blocked / integrator notes.** Nothing technical. Only the owner ruling is outstanding. The integrator must run a full ninja and the stable sweep (workers may not run ninja), then flip config.json:743 to Matching. There is no park to retire.
**Measured.** Patch 01 (CRLF form, plus an index_lf form) passes `git apply --check`, alone and together with 02/03, in worktree and --cached modes, at both cdc8ebd3 and eb33baec. Applied outside the repo, it reproduces landing.c byte for byte.
The code change is the same as the held owner_update_bug.patch. Only the comment moves to the methodology BUG template, and it is byte-inert: 26/26 sections and all symbols are identical to the held form.
- Gate --all: production 3/1/0; candidate EXACT 4/4 (new 288, update 1248, next_player 176, has_allies 112).
- head.obj equals build/base. The candidate differs only in the _dead_camera_update section.
- object_audit against the current split: PASS. No symbols.json change is needed; the _dead_camera_constants static landed in batch 1 and the split already has storage 3.
- pdb_storage: 0 disagreements.
- surplus_identity: 8 COMDATs, all identical to January's selected copies. All are already emitted by production.
- provider_link: PASS 19/19 in both orders. The wave-1 _valid_real_vector3d_axes2 FAIL is gone.
- Data: .rdata 48/48. fake_match_scan: 0 leads. /W3: clean.
- /W4: exactly one new warning, C4701 on next_unit_index (the compiler confirms the disclosed path).
- January bytes: [ebp+0x10] is only read before +0x176, so January deterministically uses the caller's result pointer.
## 10. saved_game_files _saved_game_files_enumerate_available_to_local_player_index: indeterminate count stored on saved-game-files mutex failure (source/saved games/saved_game_files.c)
- **Question:** Can number_of_available_profiles be declared without an initialiser and zeroed only after count_enumerated_profiles_in_mapfile(), inside the successful general-mutex branch? On mutex failure, *number_of_profiles then receives an indeterminate value (January stores the low word of the caller's player_index). The held BUG comment would be added, and this reverses the 20260904/20260908 rejection recorded in the parked.json park. YES/NO. (Patch 02 is a zero-credit storage fix that needs no ruling.)
- **Would complete:** source/saved games/saved_game_files: strictly exact 46/46 and audit-exact against the emulated split. Clean completion is CONDITIONAL: 3 PDB-storage disagreements remain unless the lab helper-storage follow-up is reviewed and landed, or the owner accepts the three functions as external.
- **Functions gained:** _saved_game_files_enumerate_available_to_local_player_index (368 padded / 359 meaningful)
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/ub_dead_camera_saved_game`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** - January bytes show the count zeroed only after the count call, inside the success branch. The failure tail stores the untouched player_index home.
- The /Od 0x8646c0 build zeroes the accumulator twice: at the declaration (the later fix) and after the count, which is January's position.
- Every defined alternative was already refuted from the bytes (checkpoint 20260908, n1 20260915, Lane D 20260922). The only defined source that matches would reuse player_index as the counter, which contradicts /Od and makes no sense as code.
- /W4 C4701.
- ai_communication.c:2527 precedent.
- Clean gate, audit and link results. Header blast radius 0.
**Evidence against.** - The config/parked.json park explicitly records 'reproducing that undefined behavior is rejected even though it makes the section strict exact'. The same position appears in claude_fable_opus_reconciliation_checkpoint_20260908 and saved_game_files_obj_opus_reconciliation_20260904.
- The indeterminate value escapes through an out-parameter the caller can see, which is harder to defend than dead_camera's internal read.
- Rule 17 (combine declaration and initialisation) is locally reversed. /Od attests only the late zeroing position.
- There is no _RTC_UninitUse attestation, because the /Od build contains the fix.
**Still blocked / integrator notes.** 1. The owner ruling on 03. Patch 02 can land without a ruling.
2. Whole-object COMPLETE under brief section 4, step 3: 3 PDB-storage disagreements remain (_saved_game_files_take_mapfile_mutex, _saved_game_files_release_mapfile_mutex, _saved_game_files_enumerate_default_files).
- /Od attests the mapfile-helper calls at the 6 hand-expanded sites.
- enumerate_default_files has no /Od call-site attestation.
- The fix is a real source change, preserved as lab/LAB_ONLY_saved_game_files_helper_storage_on_top_of_03.diff. It needs its own review. Alternatively, the owner accepts the three as external.
3. Do not flip config.json:398 to Matching on the ruling alone.
4. The integrator must run a full ninja and the stable sweep, then retire the enumerate_available park via tools.campaign.unpark.
**Measured.** Source is unchanged since wave 1. Patches 02 (storage; its +/- lines equal the held production_storage.patch) and 03 (the held production_bug.patch rebased onto 02) pass --check in both EOL forms, alone and together, at cdc8ebd3 and eb33baec. Applied sequentially outside the repo, they reproduce the candidate .c, the header and symbols.json byte for byte.
- Gate --all: production 45/1; 02 only 45/1 (storage flip only); 03 only 46/46; 02+03 46/46. Shadow headers were verified with an #error probe.
- csplit emulation: the control run reproduces 833/833 split objects. The edited symbols.json (line 7376, in place) changes only saved_game_files.obj, and only the storage of _enumerate_memory_units.
- object_audit, candidate against the emulated split: PASS (152 symbols, 0 differ).
- surplus: 0 code COMDATs. provider_link: PASS 11/11 in both orders. Data 100% (.bss 540, .data 8, .rdata 5608).
- Header blast radius on 6 includers (game, player_ui, ui_widget, virtual_keyboard, player_profile, playlist_profile): identical gate rows, sections and symbols.
- fake_match_scan: 0 leads. /W3: unchanged at 33. /W4: one new C4701 on number_of_available_profiles.
- January bytes re-verified: +0x91 spill, +0x94 xor edi, and the +0x156 `mov dx,[ebp+8]` failure tail.
- pdb_storage: 3 disagreements remain (take/release_mapfile_mutex, enumerate_default_files). The wave-1 'proven external' dismissal is refuted by law (h).
- LAB variant (static helpers called at the hand-expanded sites): 46/46, sections identical, PDB storage 0, audit PASS against a lab split, link PASS, blast radius 0.
## 11. first_person_weapons patch B (authentic unguarded NULL dereference of first_person_weapon_animations; rebased onto cdc8ebd3 after patch A landed in b9a8d587)
- **Question:** May _first_person_weapon_update read first_person_weapon_animations->animations.count without a NULL test at two sites (moving animation and overcharged jitter)? The pointer comes from `count ? TAG_BLOCK_GET_ELEMENT(...,0,...) : NULL`. The patch also drops REAL_MATH_EXTERNAL_{SQUARE_ROOT,MAGNITUDE_SQUARED3D,MAGNITUDE3D}, so magnitude3d inlines as in January and the TU emits three header __inline COMDATs from a strictly exact caller. The rule-6 exception conditions hold.
- **Would complete:** source/interface/first_person_weapons
- **Functions gained:** _first_person_weapon_update (1,533 meaningful / 1,536 padded)
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/ub_fpw_player_profile`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** January bytes: `xor eax,eax; jmp`, then `cmp dword [eax+0x10],3/0xf` with no test, at both sites. The later /Od build (0x61fcea and 0x61fd99) also reads without a NULL test. The HCEA 2011 decompiled source (first_person_weapon_update.c, around lines 138-145) has the same unguarded read. With the guards restored, the only difference from January is 4 inserted instructions, so no defined spelling can match. The path is latent: switch_weapons commits weapon_index only when first_person_weapon_animations.count is nonzero. BUG-commented authentic defects already exist in 18 source files, including uninitialised reads.
**Evidence against.** The source dereferences a pointer that the statement before it may set to NULL, so house rule 21 ('undefined/nonsensical logic') applies literally even though the path is unreachable. The defined-behaviour body is already in the tree at zero credit (park 95.21506). Refusing costs one function.
**Still blocked / integrator notes.** Nothing technical. The item needs only the owner's yes on the authentic-defect class. The integrator must then run the full ninja build and the stable sweep; only this TU changes.
**Measured.** Patches 01_fpw_B_source, 02_fpw_B_retire_park and 03_fpw_B_status_matching pass git apply --check at cdc8ebd3 (and on eb33baec, a docs-only commit that landed during the run). Tree: gate 33/1. With B: gate --all 34 exact / 0 residual / 0 unwritten, _point_from_line3d guard passed. No split regeneration is needed: a control csplit from cdc8ebd3 config matches build/split on all 833 objects. object_audit against the emulated split PASSES, with 45 January symbols and 0 differences. The only new surplus is _magnitude3d, _magnitude_squared3d and _square_root. All three are IDENTICAL to January's selected copies (action_charge, action_alert) and have 0 NODUP definers; provider_link passes both orders for all 12 surplus symbols. pdb_storage: 0 disagreements. Data 536/536. objdiff 3.3.1 against the emulated split: 29/34, with update at 100 (tree 28/34, update 95.21506). The other 5 functions are objdiff 3.3.1 false negatives already credited by semantic_matches.json or the accepted_ledger, and all 5 are gate-EXACT, so the strict view is 34/34. fake_match_scan finds the same single authenticated lead as the tree. /W3 shows 0 TU-local warnings; the one extra line is the existing real_math.h(810) warning. No header is touched, so there is no blast radius. January bytes confirm both unguarded reads (0x23e/0x255 and 0x28d/0x2a5). The BUG comment's claim that the path is unreachable was checked in switch_weapons.
## 12. player_profile R1-R3 (uninitialised serialised 512-byte block; seek failure skips file_close; close(&file, NONE) after delete-while-open), rebased onto cdc8ebd3 after zero-credit packet A landed in 6e3e2d35; R3 BUG comment amended
- **Question:** Does the owner reverse the 2026-09-09 saved-game rejection and admit all three January failure-path defects with BUG comments? R1: the `union player_profile_block block;` in write_thread_proc and create_default has no initialiser, so 444 stack bytes are written to disk. R2: create_default folds file_set_position into the open condition, so a failed seek leaks the file. R3: player_profile_new deletes the file while it is still open, sets the index to NONE, then calls saved_game_file_close(&profile_file, NONE), which fails its assertions. All three are required for the object.
- **Would complete:** source/saved games/player_profile
- **Functions gained:** _player_profile_create_default_profiles_on_disk (275/288); _player_profile_write_thread_proc@4 (349/352); _player_profile_new (456/464)
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/ub_fpw_player_profile`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** Since the 2026-09-09 rejection, all three shapes have first-party /Od attestation. R1: 0x8596d0 and 0x85acf0 have no initialiser on block. R2: 0x85ae02 has the seek inside the open condition. R3: 0x85d1fa sets NONE, then 0x85d20c runs the shared close. HCEX.pdb gives the union player_profile_block type and the profile_file/full_path local names. No defined spelling can reproduce the bytes; earlier waves measured the alternatives. BUG-commented authentic defects already exist in the tree, for example the uninitialised cause_point read in ai_communication (7a1d0d91).
**Evidence against.** The owner already rejected exactly these three shapes on 2026-09-09. R1 discloses 444 stack bytes into a saved file. The /Od build attests only the missing initialiser: in the later format the block is fully overwritten, so the disclosure is January-format-specific. R3 passes NONE to an asserted API and deletes a file that is still open. R2 leaks a handle on seek failure. House rule 21 applies.
**Still blocked / integrator notes.** Nothing technical. The object needs yes on all three of R1, R2 and R3; with a partial yes it stays incomplete (R1 blocks write_thread and create_default, R2 blocks create_default, R3 blocks new). Disclosed non-blockers: read and get_from_path keep `byte block[512]`, although HCEX types read's local as the union; the globals type names stay descriptive unless optional 04a is used. The integrator must then run the full ninja build and the stable sweep.
**Measured.** The old owner_gated_over_production.patch still applies at cdc8ebd3 and reproduces final_og.c exactly. The R3 BUG comment was amended to state the delete-while-open; this is comment-only and changes 0 of 60 sections. Patches 04_pp_R1R3_source, 05_pp_retire_parks and 06_pp_status_matching pass git apply --check, individually and together with 01-03. Tree: gate 19/3. Candidate: gate --all 22 exact / 0 residual / 0 unwritten, guard passed, with no split regeneration needed. object_audit PASSES, with 51 January symbols and 0 differences. The surplus list is identical to the tree's (no new surplus, 0 code COMDATs), and provider_link passes both orders for all 8 surplus symbols. pdb_storage: 0 disagreements. Data 1644/1644. objdiff 3.3.1: 100.0, code 4119/4119, 22/22 (tree 95.34539). fake_match_scan: 0 leads. /W3 is identical to the tree, with 0 TU-local warnings. No header is touched. January bytes were re-read: R3 at 0x151 delete, 0x159 `or esi,-1`, 0x164 close(&file, esi); R2 at 0xa3 `je 0xec`, which skips file_close; R1 has no fill before file_write(0x200). There is also an optional variant with the byte-inert HCEX type names advised by reviewer 3: optional/04a, then 04b, applied as two separate git apply commands. It gates 22/22 and all 60 sections are identical.
## 13. game_engine_king: _find_next_hill keeps January's uninitialised no-candidate return (next_hill), plus the cachebeta storage fix, for whole-object admission
- **Question:** Do you admit game_engine_king.obj as a complete object when _find_next_hill keeps January's uninitialised no-candidate return, disclosed by the policy-form 'BUG (preserved for exact matching)' comment? If yes, which spelling: 01, the review2 packet's early-return form, or 01A, the /Od 0x5bd630 single-exit form (`next_hill = king_engine_hills[hill_index]; break;` then one `return next_hill;`)? The two produce byte-identical objects. A NO applies only if_declined/king_source_storage_only.patch + 02 at zero credit, but it leaves open whether the function credit production already takes for this read (_find_next_hill, 112 B, since 2026-09-02) should stand.
- **Would complete:** source/game/game_engine_king
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/ub_king_glow/king`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** January bytes: at +03, push ecx reserves [ebp-4], which is never written; at +52, mov eax,[ebp-4] returns it on the loop-exhausted path.
The later first-party /Od build (0x5bd630) sets its RTC init flag only on the found path, then calls _RTC_UninitUse with the string "next_hill". So the local's name and the read are both in the later source. The function is single-exit there.
Playbook 'Original bugs' gate: January proves the read, an independent build corroborates it, and every defined form measured is 28/29 (5 at 931ed8dc, 1 at cdc8ebd3).
The function and the whole object reach strict exact with a clean section-4 audit.
Owner precedents (2026-09-20):
- ai_script.c is Matching and carries an owner-admitted read of an unset stack slot: a missing vsprintf argument makes it read the saved-EDI home (ai_script.c:2765).
- _actor_emotion_update: an out-of-bounds read admitted at 8e4309dd.
The read has been in production since 2026-09-02; the ruling certifies it, it adds no behaviour.
**Evidence against.** Brief sections 2 and 7 make this class owner-gated, and dead_camera is still held on the same class.
On 2026-09-20 the owner excluded the uninitialised-read donors _dead_camera_update and _compare_profile_sections (astra rejected-hypotheses, item 8). Those were not /Od-authenticated, but the class is the same.
King would be the first Matching object with a load-bearing C4700.
In the packet form (01), next_hill is never assigned, so a reviewer could read it as a local whose only role is the match. The /Od single-exit form (01A) answers this: it is byte-identical and raises no C4700.
Behaviour: on a map with one distinct hill and moving hills enabled, January stores garbage in king_globals.hill_id. The corrected build keeps the current hill.
**Still blocked / integrator notes.** Only the owner ruling. On a YES the integrator still has to run the full ninja, the stable function diff, parks, the admission audit and pytest. The header edit reaches only king.c. Nothing technical is open.
**Measured.** Tree: HEAD eb33baec, which is cdc8ebd3 plus one ledger-only commit; clean before and after.
Rebase: the review2 OWNER_GATED source patch applied unchanged. The result is review2's owner_bug_comment.c plus exactly the two f7cd2e72 game_variant renames. The semantic_data_matches patch failed (b9a8d587 had appended entries), so I regenerated it. All patches are regenerated as canonical git patches 01-05, plus alt/01A and if_declined/.
Apply checks: git apply --check passes on each patch, on 01-05 combined, and on king + glow combined. --whitespace=error-all is clean. Trial-applying onto tip copies reproduces king/new byte-for-byte (CRLF), and the JSON parses.
Gates (shadow patched header; a #error probe proved the patched header is the one used):
- 01: 29/29 EXACT against build/split and against the emulated split (csplit with the edited symbols.json). The emulated split differs from build/split only in game_engine_king.obj.
- 01A and storage-only: also 29/29. Their objects have 59 sections, 0 section differences and 0 symbol-row differences against 01.
- Only warning: C4700 next_hill. The /Od single-exit form raises none.
- Defined result in the /Od shape (next_hill = hill_id): 28/29, so the read stays load-bearing.
Audit and linkage:
- object_audit against the emulated split: PASS, 39/39 January symbols, .data 136/136 and .bss 560/560. The controls FAIL(23) on storage, as expected.
- pdb_storage: 0 disagreements (production has 23).
- Surplus: 22 sections, all identical to January's copies. provider_link: 22 rows, PASS in both orders. 0 NODUP definers.
- 0 UNDEF references to the 23 re-storaged names in build/base (621 objects) or build/split (833).
- The header is included only by king.c. fake_match_scan: 0 leads.
- The _king_engine data entry measures 136 / 23 / 4009f5b6... with target == base in 8 of 8 combinations.
Emulated chain (objdiff 3.3.1; the control mini row equals the production row):
- Halo complete units 379 -> 380; complete_code +4,865; complete_data +876; matched_data +136.
- The data credit validates without allow_incomplete_unit. The .bss ownership snapshot validates.
- Revoked []; parks 86/0/0; admission 11/0/2/0 with 0 contradicted.
- With glow also applied: 381.
## 14. glow _get_particle_world_position: two uninitialised stack-array reads (traversal_left_vectors never built on the 2- and 3-marker paths; traversal_time_indices[1] never set on 3-marker interval 1)
- **Question:** Do you admit glow.obj as a complete object when get_particle_world_position reproduces January's two uninitialised stack-array reads? The first is that the 2- and 3-marker paths never build traversal_left_vectors, which spline 3 reads. The second is that 3-marker interval 1 writes its midpoint knot into [2], so [1] is read unset. Each carries a policy-form BUG comment. These are in-bounds reads of automatic arrays with no struct-layout dependency, so do you also waive the compile-time layout assertion that the 2026-09-20 ruling attached to _actor_emotion_update? On a NO nothing is applied and the current defined-C park (2528/55, 49.07%) stays; review2 rejected the only defined fallback.
- **Would complete:** source/objects/widgets/glow
- **Functions gained:** _get_particle_world_position (1,744 padded / 1,729 objdiff bytes)
- **Packet:** `research/fifty_objects_20260925/w/owner_queue/ub_king_glow/glow`
- **Apply-ready at cdc8ebd3:** yes
**Evidence for.** Both defects are target-proven by January's control flow. The later first-party /Od build (0x798de0) contains both verbatim:
- Its RTC descriptor names traversal_left_vectors [ebp-0xa8], which only the default loop's cross_product3d writes.
- In case 1, both the time_indices stores (0x799533 and 0x799639) go to [2].
HCEX.pdb gives the same local names.
The bounded defined search fails: n3a v3 is +592 B, P8 has 71 REAL regions, and this wave's single-defect repairs are each 24/1.
The function and the object become strict exact with a clean section-4 audit.
Owner precedents (2026-09-20): Matching ai_script.c has an admitted unset-stack-slot read, and _actor_emotion_update's out-of-bounds read was admitted at 8e4309dd.
The reads have no layout dependency, so no assertion is meaningful.
**Evidence against.** The class is owner-gated (brief sections 2 and 7), dead_camera is held on the same class, and the owner refused the 2026-09-20 uninitialised donors (dead_camera, compare_profile_sections).
The behaviour is real and visible: glows with 2 or 3 markers use stack garbage for the left offset (v*cosine), and on 3-marker interval 1 the second spline knot is garbage.
The packet adds point<->vector view casts. They meet the brief's rule (/Od-attested per site, compatible layout, byte-inert strip, strictly exact caller, disclosed) but still need to be admitted.
Byte-inert nits: NUMBEROF vs the /Od signed literal 4; the locals sine/cosine shadow the real_math.h inlines; the BUG comment's range is end-exclusive (correct as written).
**Still blocked / integrator notes.** Only the owner ruling, including the layout-assertion waiver. On a YES, 01, 02 and 03 must land together, followed by the integrator's full ninja, stable diff, parks, admission audit and pytest. Only glow.c changes; no header.
**Measured.** Rebase:
- glow.c/.h are untouched since e9e62b78. The glow.c and parked.json parts of wave-2 production.patch apply unchanged.
- The rebased glow.c == scratch/w/glow/cand_final.c byte-for-byte.
- The config.json hunk failed (b9a8d587 flipped light_volumes.c two lines above), so I regenerated it as a one-line status flip.
- Patches 01-03 are canonical git patches. git apply --check passes on each, on 01-03 combined, and on king + glow combined. --whitespace=error-all is clean. Trial-applying reproduces glow/new byte-for-byte.
Gates (gate.py --all --forbid-emitted-symbol _point_from_line3d):
- Packet: 25/25 EXACT; _get_particle_world_position EXACT 1744.
- Tip control: 24/1, [size 2528!=1744, relocs 55!=43, sha].
- 0 TU-local /W3 warnings.
- objcmp tip vs packet: 1 of 56 sections differs (this function); 0 symbol-row differences.
Strip tests at cdc8ebd3:
- Knot repaired only: 24/1 [sha].
- Left vectors = {0} only: 24/1 [size 1776!=1744, sha]. Each defect is therefore independently load-bearing.
- index < 4: 25/25 (byte-inert).
- review2's no-casts variant: 25/25, but raises C4133, so the casts are type correctness, not decoration.
Audit and linkage:
- object_audit: PASS, 36/36 symbols; the surplus list is identical to production's.
- Surplus: 20 sections, all identical to January's copies. provider_link: 20 rows, PASS in both orders. 0 NODUP definers.
- pdb_storage: 0 disagreements. Data .rdata 392/392; no data entry is needed.
- January bytes re-read: left vectors live at [ebp-0xc4, ebp-0x94). They are written only by the default loop (+0x21b/+0x23e/+0x259) and read by the spline-3 leas (+0x637..+0x64c). Case 1 jumps to +0x5c6, which stores [ebp-8]; +0x5d2 then reads [ebp-0xc] unwritten.
- /Od RTC names confirmed.
Emulated chain:
- Halo complete units 379 -> 380; complete_code +6,987; complete_data +392; matched_code +1,729; matched functions +1.
- Parks 86 -> 85 (0 stale or invalid); revoked []; admission 11/0/2/0.
- Applying 01 without 02 fails progress with 'parked-function evidence is stale or invalid'.
- With king also applied: 381.
@@ -0,0 +1,76 @@
# Claude fifty-object lane: canonical reconciliation
The donor is `claude/fifty-objects-20260925` at `4e84c212`, based on
`e9e62b78`. The receiving canonical base is `jonas/exact-pilots` at
`dbcea3d6`. This integration used the existing objdiff 3.3.1 binary on
both sides; `configure.py`'s 3.6.0 pin was not used to re-score the lane.
## Measured result against current canonical
| Measure | Before | Reconciled | Net |
| --- | ---: | ---: | ---: |
| Halo objects marked Matching | 364 / 468 | 381 / 468 | +17 |
| Strict-exact functions, stable 8,252-row sweep | 7,600 | 7,614 | +14, 0 lost |
| Newly exact meaningful function bytes | | | +8,775 |
| Newly exact padded function bytes | | | +8,864 |
| Halo credited code bytes, including status/scorer effects | 1,564,286 | 1,573,109 | +8,823 |
| Halo credited data bytes | 2,580,532 | 2,583,826 | +3,294 |
| Valid active parks | 96 | 86 | 0 stale, 0 invalid |
The meaningful/padded function totals are the 14 gained section identities
from the stable sweep cross-checked against `semantic_report.json`. The
net credited-code delta is **not** substituted for newly exact function
credit: object-status/scorer effects account for its extra 48 bytes.
## Conflict and policy resolution
- `path_structure_bsp.c`: retained canonical's explicit eight-element
`real const` arrays. The second table's authenticated symbol entry is
present once in `symbols.json`.
- `hs_scenario_definitions.c`: retained canonical's separately defined,
already-Matching data and private `hs_syntax_data_byte_swap` name. The
source pointer and `symbols.json` name agree.
- `symbols.json`: kept the union of nonoverlapping donor names and canonical
data rows; no JSON reserialization or descending-address insertion.
- `object_admission_rejections.json`: removed only rejections resolved by
the merged source/provider repairs or by canonical's earlier independent
work. The dynavobgeom rejection remains.
- `rasterizer_xbox_dynavobgeom.c`: retained the genuine type/layout assertions
and the donor's source improvements, but did **not** mark the object Matching.
Its 17 target functions and owned data remain exact. The stock SDK-table
coalescing criterion conflicts with canonical's existing ruling, and
`global_window_parameters` still has consumer-local declarations. No
whole-object credit is granted pending a uniform policy/owner audit.
- Layer 2's two random-seed lock prototypes remain in `real_math.h`'s
`RANDOM_MATH.C` section. The supplied first-party `random_math.c` includes
`real_math.h`, not the reconstructed `random_math.h`, and `main.c` needs
these declarations. Their simultaneous declaration-count effect is
disclosed; byte evidence alone does not identify the exact original
header placement. The newly moved no-argument prototypes were reformatted
with `void` on its own line. A future full random-math header consolidation
must retain the same whole-board no-regression gate.
- P7's removal of the unused `hud_draw.h` include from
`rasterizer_xbox_models.c` remains. The include has no direct object-byte
effect; its declaration-count interaction is disclosed, and the removal
is an ordinary dependency cleanup rather than a filler declaration.
The donor's held owner queue and fuzzy experiments remain in
`research/fifty_objects_20260925/`. No January-bug candidates or the
five-object actor-combat provider packet were silently admitted.
## Gates
- Full Ninja build: passed with objdiff 3.3.1.
- Stable exact-function diff: 14 gained, zero regressions.
- Parks: 86 active, zero stale/invalid.
- Admission audit: zero contradicted, zero revoked; dynavobgeom is explicitly
rejected, not silently admitted.
- `python -m pytest -q tools`: 1,159 passed, 5 skipped, 26 subtests.
- Production-path `git diff --check` (`config source docs tools`): passed.
- Fake-match scan: one new **zero-credit** lead in the parked
`first_person_weapon_update` body, an empty then-arm attested in the
donor's `/Od` evidence. No new lead in newly credited exact bodies.
The donor's raw research tree contains captured patch/diff examples with
intentional trailing whitespace. It is retained as evidence, not production
source, and is excluded from the production-path whitespace assertion.
@@ -0,0 +1,2 @@
# evidence copies are kept byte-for-byte (patches must still apply) and are not style-checked
* -text -whitespace
@@ -0,0 +1,23 @@
Files over 409600 bytes left in scratch (not copied):
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\bounding_sphere\final\action_vehicle.c (diff vs source/render/render_objects.c is 60811 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\bounding_sphere\final\object_lights.c (diff vs source/render/render_objects.c is 103329 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\collisions\final\matrix_math.c (diff vs source/physics/collisions.c is 76828 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\game_engine_clean\final\source\game\game_engine.c (diff vs source/game/game_engine.c is 80693 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\game_engine_clean\final\source\interface\hud_nav_points.c (diff vs source/game/game_engine.c is 229480 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\game_engine_finish\final\source\game\game_engine.c (diff vs source/game/game_engine.c is 155153 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\game_engine_finish\final\source\interface\hud_nav_points.c (diff vs source/game/game_engine.c is 229482 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\path_audit\lab\cand_av_dist.c (diff vs source/ai/path.c is 76483 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\path_audit\lab\cand_av_ogbs.c (diff vs source/render/render_objects.c is 61655 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\path_audit\lab\cand_decals_pp2d.c (diff vs source/render/render_debug.c is 129147 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\path_audit\lab\cand_ol_nolocal.c (diff vs source/ai/path.c is 118294 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\action_charge\cand1.c (diff vs source/objects/objects.c is 174687 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\action_vehicle\cand1.c (diff vs source/structures/leaf_map.c is 64043 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\bored_camera\cand1.c (diff vs source/objects/objects.c is 151552 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\object_lights\cand1.c (diff vs source/structures/leaf_map.c is 106196 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\path_obstacles\cand2.c (diff vs source/objects/objects.c is 162670 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\player_control\cand1.c (diff vs source/objects/objects.c is 197248 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\players\cand1.c (diff vs source/math/geometry.c is 158783 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\rasterizer_xbox_profile\candidate_files\source\rasterizer\rasterizer.c (diff vs source/rasterizer/xbox/rasterizer_xbox_profile.c is 76410 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\sound_manager\final\sound_definitions.c (diff vs source/sound/sound_manager.c is 107261 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\weapons\final\action_charge.c (diff vs source/items/weapons.c is 130496 bytes; wrong mapping or whole-file rewrite)
C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\weapons\final\object_types.c (diff vs source/items/weapons.c is 140633 bytes; wrong mapping or whole-file rewrite)
@@ -0,0 +1,230 @@
# Worker brief - Halo January 50-object campaign (lane claude/fifty-objects-20260925)
Worktree (shared by all workers): `C:\halo-worktrees\claude-fifty-objects-20260925`
Base: canonical jonas/exact-pilots `e9e62b78`. Scorer frozen: objdiff **3.3.1** (never replace it).
## 0. Hard rules for workers (violations corrupt everyone's work)
- **Never edit any tracked file** (source/, config/, tools/, docs/, headers, symbols.json,
parked.json, config.json). Never run `ninja`, `configure.py`, `git add/commit/checkout/reset/stash`.
The integrator applies every change. You work ONLY under `scratch/w/<your-unit-slug>/`.
- Compile candidates with `python tools/campaign/gate.py source/<unit> --source scratch/w/<slug>/cand.c --all --out scratch/w/<slug>/cand.obj`
(PID-isolated; safe in parallel). `--source` may be anywhere; the unit's directory is on /I.
- The January target object is `build/split/<unit>.obj`; the current production build is
`build/base/<unit>.obj`. Treat both as read-only.
- Analyze leaked/first-party executables as DATA only. Never execute them.
- Append findings to `scratch/w/<slug>/LEDGER.md` AS YOU GO (a cut-off run keeps nothing else).
## 1. What counts
Strict exact = `coff_compare.section_infos_equal` (size, normalized bytes, relocation count,
type, address, target, addend). gate.py prints `EXACT` rows. Fuzzy %, instruction counts or a
coincidental SHA are not exactness. An object is COMPLETE only when every January-owned function
AND data/BSS section, every symbol (name, storage class, section, offset), and all surplus
emissions (COMDAT helpers, literals, SDK tables) are admissible - see section 4.
Never count: scorer-version effects, status edits, already-identical bytes, fuzzy gains.
## 2. House rules (all changed source) - docs/campaign_house_rules.md is authoritative
void on its own line for empty params; one parameter per line; explicit `return;` / valid
return values; typed tag/object access macros; prototypes in genuine owner headers (never a
consumer-local prototype for another file's function); `real` not `float`; cseries.h macros
(TEST_FLAG/SET_FLAG/FLAG/PIN/MIN/MAX/NUMBEROF...) over hand bit twiddling; correct enum constants
(also in switch tables); combine declaration+initialisation where natural; authentic or
descriptive names (never code_/bss_+address); assertions via the lazy project macros
(match_assert/match_vassert/... with the right predicate sense; see docs/assertions.md).
Rule 6: never hand-expand or hand-copy point_from_line3d or any header helper. A shared-header
`__inline` may emit a COMDAT ONLY if the copy is byte-identical to January's selected provider,
the caller is strictly exact, the full sweep has zero regressions and ordinary provider links
pass in BOTH orders (scratch/tools/provider_link.py). No blanket COMDAT permission.
Rejected as fake matching (the STRIP TEST: remove the decoration; if the plain spelling no longer
matches, the decoration IS the fake): decoration-only parentheses, redundant zero stores without
independent evidence, filler declarations/dummy locals (they are the name-count ORACLE, never a
landing), do{}while(FALSE) or one-trip loops to buy inline budget, volatile/aliasing tricks,
pragmas, forced inline, inline asm as a general lever, invented UB. A new 3D-to-2D (or other
representation) view cast needs per-site first-party /Od attestation, compatible prefix layout,
a byte-inert strip test, a strictly exact caller and ledger disclosure. Reproducing an authentic
January bug needs target+layout proof, a BUG comment and owner approval - report, don't land.
Held items you must NOT retry: path_obstacles bare-parenthesis match, actor_aim_grenade invented
goto, hud_draw optimizer pragma, s3tc live out-of-bounds behaviour, render_sprite memory
corruption, flags `x * height` with x just set to zero, any incompatible helper provider.
decals.c / physics.c are closed for this lane unless you hold NEW evidence for the specific
load/slot/x87 residuals recorded in docs/object_matching_logs/claude_decals_physics_20260924.md.
## 3. Evidence sources and tools (use these before guessing)
- January bytes/relocs: `python tools/campaign/tinfo.py source/<unit> [--fn F] [--data]`,
`python tools/campaign/alndiff.py source/<unit> <fn> --ours-object <obj>` (aligned diff; never
trust gate.py --disas index pairing), `python tools/campaign/relocdiff.py source/<unit> <fn> [obj]`,
`python tools/campaign/real_regions.py source/<unit> <fn> --ours-object <obj>`.
- Lane helpers in scratch/tools/: `sbs.py` (side-by-side range), `dis_range.py`,
`reloc_segments.py` (instruction census between relocation anchors), `slot_census.py` and
`slot_refs.py` (frame slots), `leafswaps.py` (classify alndiff blocks LEAF/SLOT/REG/OTHER).
- Later first-party **/Od+/RTC build** (statement order, real calls vs expressions, named locals as
frame slots, RTC descriptors naming every address-taken aggregate, assert LINE numbers):
`python scratch/tools/odbuild.py str "<string>"`, `... callers 0xADDR`, `... fn 0xADDR --out f.txt`.
odbuild misses aligned-frame prologues (`push ebx; mov ebx,esp; and esp,-8`): then use
`python scratch/tools/od_linear.py 0xSTART 0xEND out.txt`. It is a LATER revision - evidence for
names/types/shape only; every fact must be double-attested against January's own bytes.
- **HCEX.pdb** (2011 PPC SHIP build; real names/types of params and stack locals, file statics):
`"C:/Users/isabe/Documents/Codex/2026-07-13/i-w/research/tools/DIA2Dump/x64/Release/DIA2Dump.exe" -sym <function> "C:/Users/isabe/Documents/Codex/2026-07-13/i-w/research/hcea_jun2011_prototype/payload/Halo CE Anniversary (Jun 24 2011)/HCEX.pdb"`
(enregistered scalars are absent; layouts/offsets do not transfer).
- cachebeta.pdb publics (January storage truth: public = external, absent = file-static):
`scratch/tools/cachebeta_publics.txt`, checked by `python scratch/tools/pdb_storage.py source/<unit>`.
- Halo CE symbol atlas / Marathon source / Bungie fragments: names and idioms, not byte proof.
- Ghidra 12.1.2 headless: `C:/Users/isabe/Documents/Codex/halo-tools/apps/ghidra_12.1.2_PUBLIC`
(support/analyzeHeadless.bat); IDA Free 9.4 is GUI-only. Record exactly what a tool established;
a decompiler rendering is not original source.
- Existing ledgers: `docs/object_matching_logs/*<unit>*`, parks in `config/parked.json`
(`python -B -m tools.parked_functions`), rejections in `config/object_admission_rejections.json`.
READ THE NEGATIVE EXPERIMENTS before probing; do not repeat recorded inert spellings.
## 4. Whole-object admission audit (for any object you claim complete)
1. `python tools/campaign/gate.py source/<unit> --source <cand> --all --out <obj>`: every row EXACT.
2. `python scratch/tools/object_audit.py source/<unit> <obj>`: every January-owned section and
symbol (storage, section, offset) matches; lists candidate-only surplus.
3. `python scratch/tools/pdb_storage.py source/<unit>` (on the production build; for a candidate
reason from its symbols): storage vs cachebeta publics.
4. Surplus: `python scratch/tools/surplus_identity.py source/<unit>` and
`python scratch/tools/provider_link.py source/<unit> <obj>` - every surplus COMDAT must be
byte-identical to January's selected copy and pair-link (no LNK2005/LNK1169) in both orders.
Known conflicts: NODUP hand copies (`python scratch/tools/nodup_census.py _sym ...`).
5. Data: `python tools/campaign/tinfo.py source/<unit> --data` vs `--base`; objdiff 3.3.1 has a
known '$'-literal relocation-name defect - a data section that is byte+relocation identical
under coff_compare but <100% in build/report.json is a scorer defect, handled by a pinned
config/semantic_data_matches.json entry (integrator applies; single-section entries only).
6. Check config/object_admission_rejections.json and parks for the unit.
## 5. Decoded compiler laws (use as hypothesis generators; each has a ledger)
- Frame-size mismatch (`sub esp,N`, see scratch/baseline/frame_census.txt) is STRUCTURAL: a
different set/scope/type of homed locals, never a tie. Slot sharing is gated by declaration
SCOPE (an object may merge into another's slot only if neither scope encloses the other);
address-taken locals conflict until the end of their declaration scope. Below 0x80 total the
frame order is size-ascending + per-reference bubble-up; above 0x80 a quicksort on
(1000*refs)/size. A partly homed aggregate costs its whole extent.
- Single-exit law: January usually returns ONCE at the bottom (result/valid flag), not early
returns; count return statements. Fixing it on a static cascades to callers (private ABI).
- /Od readout first: named locals (count user-area frame slots, not only RTC aggregates), statement
order, which helpers are real calls, loop-counter scoping, argument order of helper calls.
- Inline gating: per call SITE, callee cost vs block-weight budget; each extra if/&&/|| level on
the path lowers the budget; an early return costs one step for the rest of the function; a
`= {0}` aggregate initialiser and a match_assert in the helper raise cost. Measure the boundary
with lab copies of the call placed along the path (lab only).
- M8: x87 operand order of inlined helpers follows IL-local ordinals mod 8 of their parameters;
helper ARGUMENT order matters, bare source operand order of `a*b` is inert.
- Store/call order: a store through a pointer cannot cross a call, so its position relative to a
call IS January's statement order. Post-increment inside a call argument vs a separate
statement changes whether the increment precedes the call.
- LAW Z: "January pins a zero register and we don't" is a SOURCE defect (a surplus long-lived
value/alias, declaration order of zero-initialised locals), not a tie.
- Accessor binding: `&accessor(...)->field` inline folds destructively; binding to a local gives
the non-destructive lea - read the bytes per site.
- VC7 unrolls small constant-trip for/while but not do..while; NUMBEROF is size_t (unsigned
compare); `and 80000007h` proves a signed divisor; a float literal 1 ULP off proves a folded
expression.
- Warnings as detectors: `CL /Zs /W3` C4013 implicit declarations push real args as doubles.
- Name-count / declaration-count ties: dummy declarations flip distant ties - use ONLY as an
oracle to locate genuinely missing January declarations; never land fillers.
- Methodology stop rule: after ~5 evidence-based source shapes with only an interchangeable
register / dependency-free order left, stop, preserve the best credible candidate at zero
credit, record reopen criteria.
## 6. Reporting
Your final answer must be the structured result requested by the orchestrator. Keep full
evidence in `scratch/w/<slug>/` (candidate .c, gate rows before/after, alndiff excerpts,
LEDGER.md with every probe: hypothesis -> evidence -> change -> measured result).
## 7. Wave-2 state and new findings (base commit 931ed8dc)
- LANDED: Layer 1 + Layer 2 helper-provider repairs. These hand-written NODUP copies are GONE
(the genuine real_math.h inline now emits byte-identical SELECT_ANY copies): real_random,
signed_angular_difference, interpolate_scalar, valid_real_vector3d_axes2, valid_real_normal3d,
point_in_sphere, rotate_vector2d, project_point3d, point_in_circle, distance_squared2d/3d,
distance2d/3d, negate_vector2d, plane3d_from_point_and_normal, plane3d_negate,
projection_sign_from_vector3d, triple_product3d, project_point2d. real_math.h's distance2d/3d
and project_point2d bodies now equal January's selected copies. Remaining known conflicts:
actor_combat `_cross_product2d` (NODUP hand copy; its genuine use is in the held aim_grenade),
action_vehicle `_object_get_bounding_sphere` (assert-string differs: "..\objects\objects.h"),
effects `_real_local_random`, player_control `limit2d`, plus any found by provider_link.py.
- Owner-gated classes (report with evidence, never self-land): reproducing authentic January
uninitialised reads / NULL dereferences; new representation view casts or aggregate view
copies that are load-bearing (not byte-inert); header edits that regress declaration-count
canaries or keep an extern prototype over a static definition; dead or unattested named
locals whose only role is the match (they fail the strip test); non-/Od-attested staging temps.
- Decoded in wave 1: (a) C2 compiles functions in definition order except a function calling a
not-yet-defined TU function is deferred to the end - recovers January's definition order from
emission order; (b) allocator block weight = number of webs referenced in the block, so an
inlined helper's named local can flip caller colouring; (c) a load from a FILE-STATIC const
aggregate whose address is never taken is hoisted above pointer stores, an external one is not
- check cachebeta publics for any const global in an emission-order residual.
- Git Bash rewrites `gate.py --cflag /X` into a Windows path; prefix with MSYS_NO_PATHCONV=1.
- Wave-1 worker dirs scratch/w/<unit>/ still exist; resume from their LEDGER.md when re-tasked.
## 8. Wave-3 state (base commit 954eebd2; Halo objects 375)
- LANDED since 931ed8dc: b807f9f6 objects.h OBJECTS_H_FILE (bounding-sphere inline assertions
report January's per-TU __FILE__; default "..\objects\objects.h", object_lights defines the
absolute spelling first) + action_vehicle bounding-sphere hand copy removed; path.h HCEX field
`struct path_result result`. b9a8d587 admitted units, collision_debug, render_objects,
editor_flying_camera, rasterizer_xbox_shadows, cache_files_windows, light_volumes,
actor_firing_position, path, biped_limp_noodle, leaf_map, model_animations.
- `_object_get_bounding_sphere` is NO LONGER a provider conflict for non-objects/ units.
- Held owner items (do not self-land; do not re-probe unless you have NEW evidence): actor_combat
P1 cross_product2d (w/cross_product2d), king/glow/fpw-B/dead_camera authentic-bug class,
object_lights __FILE__ surplus, rasterizer_xbox .bss names, hardware_geometry A/B wrapper,
network_connection connect form, breakable_surfaces real_local_random count, actions grouped
data verifier, render_debug invented aggregate, bitmap_drawing unnamed static.
- Wave-2 laws: (d) reviewers REJECT scope/block groupings that contradict the /Od frame's
DECLARATION order even when exact - check /Od declaration order before proposing any block;
(e) the escape law: only a local whose address reaches an out-of-line call needs a scope;
(f) /Od RTC names + same statement pair + zero assert-anchor drift authenticated a dead
statement pair (editor_flying_camera) - a dead statement WITHOUT /Od attestation is a filler;
(g) caller-local struct views with opaque spans, consumer-local prototypes of other files'
functions, hand-expanded header helpers and `float` for `real` all block admission even when
every byte matches - scan for them before claiming OBJECT_COMPLETE_CANDIDATE.
- Every earlier wave's ledgers are also in research/fifty_objects_20260925/ (tracked copies).
## 9. Wave-4 state and INCIDENT RULES (base commit 16542e46; Halo objects 378)
INCIDENTS in wave 3 (both repaired): a reviewer's broken `&&` chain ran `rm -rf .git` in the
worktree ROOT (deleting the worktree pointer), and `tools/campaign/volatile_scan.py --help` (no
argparse) ran a hard-coded scan that REWROTE source/hs/hs.c. Therefore:
- NEVER run rm/mv/cp/sed -i/redirects whose target is not under YOUR scratch/w/<slug>/. Always use
absolute slug paths for destructive commands; never `cd` then `rm -rf` in one chain.
- NEVER touch `.git`, build/, config/, source/ or tools/ files (read only).
- Before running ANY repo script you have not run before, READ its source; do not assume --help
is safe. If a script writes files, copy it into your slug and point its outputs there.
- If you see the tree modified by someone else (git status not clean), STOP and report it.
LANDED since 954eebd2: f7cd2e72 player_ui (game_engine.h game_variant layout with HCEX member
names + member renames across game engines; player_ui.h) and sound_manager (sound_definitions.h
short old_range_index). Batch 4b: dynavobgeom admission, damage _object_damage_body, players
storage + COMMON definitions, player_profile/main zero-credit packets, hs storage (839 statics +
hs_enumerate_scenario_data helper), hs_scenario_definitions static rename.
Wave-3 laws: (h) an unreferenced non-public STATIC function is still emitted if the source USES
it (148 Halo examples) - storage cannot be inferred from "unreferenced"; (i) a newly emitted header
__inline COMDAT is admissible only if its caller is strictly exact (the damage patch A and hs cleanup
were rejected on this; P1 class); (j) VC7 /O2 pads every file-scope static to 4 bytes, so pad members
in a TU-private struct are the signature of an INVENTED aggregate.
## 10. Wave-5: OWNER-QUEUE VERIFICATION (base commit cdc8ebd3; Halo objects 379)
Nothing in this wave lands. Each lane takes one or two HELD owner items (ledger held-items table)
and makes them APPLY-READY on the current tip: rebase every patch onto cdc8ebd3 (the tree moved:
f7cd2e72 renamed game_variant members in game_engine*.c incl. king; b807f9f6 objects.h; 466698b8
hud_draw.h), re-measure, and write everything under scratch/w/owner_queue/<item>/:
- patches/ (git apply --check clean at cdc8ebd3, in apply order, plus an APPLY_ORDER.txt),
- the edited symbols.json copy if needed (in-place edits, based on cdc8ebd3),
- MEASURED.md: gate --all (every January function of every affected object), object_audit against
an emulated split, provider_link both orders, data status, and the exact list of objects that
become COMPLETE if the owner says yes (and those that still would not, with why),
- RULING.md: the precise yes/no question for the owner, the first-party evidence for and against,
the BUG/disclosure comments exactly as they would land, and the house-rule classes touched.
Obey sections 0 and 9 (incident rules) strictly.
@@ -0,0 +1,71 @@
"""Integrator admission helper: flip config.json statuses, append semantic data entries, drop rejections.
python scratch/campaign/admit.py --match source/x/y.c [...] [--unmatch source/..c]
[--data-entry entry.json ...] [--drop-rejection <unit> <symbol-prefix>] ...
All edits are textual and in place (no JSON re-serialisation of config.json / rejections).
"""
import json
import re
import sys
args = sys.argv[1:]
match, unmatch, entries, drops = [], [], [], []
i = 0
while i < len(args):
a = args[i]
if a == '--match':
match.append(args[i + 1]); i += 2
elif a == '--unmatch':
unmatch.append(args[i + 1]); i += 2
elif a == '--data-entry':
entries.append(args[i + 1]); i += 2
elif a == '--drop-rejection':
drops.append((args[i + 1], args[i + 2])); i += 3
else:
sys.exit('bad arg ' + a)
def edit_status(names, new):
p = 'config/config.json'
raw = open(p, 'rb').read().decode('utf-8')
for n in names:
pat = re.compile(r'(\{ "name": "%s", "index": \d+, "status": ")(\w+)(" \})' % re.escape(n))
hits = pat.findall(raw)
if len(hits) != 1:
sys.exit('status: %d hits for %s' % (len(hits), n))
raw = pat.sub(lambda m: m.group(1) + new + m.group(3), raw)
print('status', n, hits[0][1], '->', new)
open(p, 'wb').write(raw.encode('utf-8'))
if match:
edit_status(match, 'Matching')
if unmatch:
edit_status(unmatch, 'NonMatching')
if entries:
p = 'config/semantic_data_matches.json'
raw = open(p, 'rb').read().decode('utf-8')
nl = '\r\n' if '\r\n' in raw else '\n'
body = raw.rstrip()
assert body.endswith(']'), 'unexpected end'
body = body[:-1].rstrip()
for e in entries:
obj = json.load(open(e))
txt = json.dumps(obj, indent=2).replace('\n', nl)
txt = nl.join(' ' + l for l in txt.split(nl))
body += ',' + nl + txt
print('data entry', obj['unit'], obj.get('symbol'))
open(p, 'wb').write((body + nl + ']' + nl).encode('utf-8'))
json.load(open(p))
if drops:
p = 'config/object_admission_rejections.json'
d = json.load(open(p))
before = len(d['entries'])
for unit, sym in drops:
d['entries'] = [e for e in d['entries'] if not (e['unit'] == unit and e['symbol'].startswith(sym))]
print('rejections', before, '->', len(d['entries']))
raw = open(p, 'rb').read().decode('utf-8')
nl = '\r\n' if '\r\n' in raw else '\n'
open(p, 'wb').write((json.dumps(d, indent=2, ensure_ascii=False).replace('\n', nl) + nl).encode('utf-8'))
@@ -0,0 +1,35 @@
"""Run the whole-object admission battery on production build/base for a list of units.
python scratch/campaign/audit_battery.py <unit> [<unit> ...] (units like ai/path)
Prints per unit: gate totals, object_audit verdict (+ failing lines), PDB disagreements,
surplus identity, provider-link verdict (+ failing symbols), report data %.
"""
import json
import subprocess
import sys
rep = {u['name']: u for u in json.load(open('build/report.json'))['units']}
def run(cmd):
r = subprocess.run(cmd, capture_output=True, text=True)
return (r.stdout + r.stderr).strip().splitlines()
for u in sys.argv[1:]:
unit = 'source/' + u
g = run([sys.executable, 'tools/campaign/gate.py', unit, '--all'])
oa = run([sys.executable, 'scratch/tools/object_audit.py', unit])
pdb = run([sys.executable, 'scratch/tools/pdb_storage.py', unit])
si = run([sys.executable, 'scratch/tools/surplus_identity.py', unit])
pl = run([sys.executable, 'scratch/tools/provider_link.py', unit])
m = rep.get(unit, {}).get('measures', {})
print('=' * 20, u)
print(' gate :', g[-1] if g else '?')
bad = [l for l in oa if 'DIFF' in l or 'MISSING' in l or 'storage' in l.lower() and '!=' in l]
print(' audit :', oa[-1] if oa else '?', '|', ' ; '.join(x.strip() for x in bad[:6]))
print(' pdb :', pdb[-1] if pdb else '?')
print(' surplus:', si[-1] if si else '?', '|', ' ; '.join(x.strip() for x in si if 'DIFFERENT' in x)[:300])
fails = [l.split()[0] for l in pl if l.rstrip().endswith('FAIL') or 'NO JANUARY PROVIDER' in l]
print(' link :', pl[-1] if pl else '?', fails)
print(' data : %s/%s' % (m.get('matched_data'), m.get('total_data')))
@@ -0,0 +1,88 @@
"""Integrator batch gate: run the full verification chain and summarise against the frozen baseline.
python scratch/campaign/batch_gate.py <label>
Steps: guard objdiff version (3.3.1) and CL path, ninja (full; re-patch build.ninja if configure
re-ran), parse progress, stable_verdicts snapshot+diff vs scratch/baseline/stable.json, parks,
admission audit, fake-match scan (lead count vs baseline), pytest, git diff --check.
Writes scratch/campaign/gate_<label>/ with every raw log.
"""
import json
import os
import re
import subprocess
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.chdir(ROOT)
label = sys.argv[1]
out = os.path.join('scratch', 'campaign', 'gate_' + label)
os.makedirs(out, exist_ok=True)
def run(name, cmd, timeout=3600):
r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, shell=isinstance(cmd, str))
open(os.path.join(out, name + '.log'), 'w', encoding='utf-8').write(r.stdout + '\n' + r.stderr)
return r
def objdiff_version():
return run('objdiff_version', [os.path.join('build', 'tools', 'objdiff-cli.exe'), '--version']).stdout.strip()
summary = {}
v = objdiff_version()
summary['objdiff'] = v
if '3.3.1' not in v:
print('ABORT: objdiff is not 3.3.1:', v)
sys.exit(2)
# ninja; if configure re-ran, re-patch the CL path and rebuild
r = run('ninja', ['ninja'])
if r.returncode != 0 and 'The system cannot find the file specified' in (r.stdout + r.stderr):
run('ninja_manifest', ['ninja', 'build.ninja'])
run('patch', [sys.executable, 'scratch/patch_ninja.py'])
r = run('ninja', ['ninja'])
bn = open('build.ninja').read()
if 'command = xbox/bin/vc7/CL.Exe' in bn:
run('patch', [sys.executable, 'scratch/patch_ninja.py'])
r = run('ninja', ['ninja'])
summary['ninja_exit'] = r.returncode
v2 = objdiff_version()
summary['objdiff_after'] = v2
log = r.stdout
m = re.search(r'halobetacache: ([\d.]+)% matched, ([\d.]+)% linked \((\d+) / (\d+) files\)\s+Code: (\d+) / (\d+) bytes \((\d+) / (\d+) functions\)\s+Data: (\d+) / (\d+) bytes', log)
if m:
summary['halo'] = {'objects': int(m.group(3)), 'of': int(m.group(4)), 'code': int(m.group(5)),
'code_total': int(m.group(6)), 'functions': int(m.group(7)), 'data': int(m.group(9))}
m = re.search(r'All: [\d.]+% matched, [\d.]+% linked \((\d+) / (\d+) files\)', log)
if m:
summary['all_objects'] = int(m.group(1))
run('snapshot', [sys.executable, '-m', 'tools.campaign.stable_verdicts', 'snapshot', os.path.join(out, 'stable.json')])
d = run('stable_diff', [sys.executable, '-m', 'tools.campaign.stable_verdicts', 'diff', 'scratch/baseline/stable.json', os.path.join(out, 'stable.json')])
summary['stable_diff_exit'] = d.returncode
summary['stable_diff_tail'] = d.stdout.strip().splitlines()[-6:]
p = run('parks', [sys.executable, '-B', '-m', 'tools.parked_functions'])
try:
summary['parks'] = json.loads(p.stdout)['summary']
except Exception:
summary['parks'] = p.stdout[-400:]
a = run('admission', [sys.executable, 'tools/audit_object_admission.py'])
try:
aj = json.loads(a.stdout)
summary['admission'] = aj['summary']
summary['admission_contradicted'] = [c.get('unit') for c in aj.get('contradicted', [])]
summary['admission_revoked'] = [c.get('unit') for c in aj.get('revoked', [])]
except Exception:
summary['admission'] = a.stdout[-400:]
f = run('fake_scan', [sys.executable, 'tools/fake_match_scan.py'])
m = re.search(r'(\d+) review lead', f.stdout)
summary['fake_leads'] = int(m.group(1)) if m else f.stdout[-200:]
t = run('pytest', [sys.executable, '-m', 'pytest', '-q', 'tools', '--basetemp=' + os.path.join(ROOT, 'scratch', 'pytest_tmp')])
summary['pytest'] = t.stdout.strip().splitlines()[-1] if t.stdout.strip() else t.stderr[-200:]
g = run('diff_check', ['git', 'diff', '--check'])
summary['diff_check'] = 'clean' if not g.stdout.strip() else g.stdout[:400]
json.dump(summary, open(os.path.join(out, 'summary.json'), 'w'), indent=1)
print(json.dumps(summary, indent=1))
@@ -0,0 +1,96 @@
"""Copy curated worker evidence from ignored scratch/ into tracked research/fifty_objects_20260925/.
python scratch/campaign/curate.py
Copies, per scratch/w/<slug>/: every *.md, *.patch and *.diff (outside split/ and config/ copies),
plus a diff against the tree for every .c candidate that wave results name in candidate_files; and every wave result/review record under scratch/campaign/wave*/units/.
Skips any single file over 400 KB (listed in SKIPPED.txt). .obj, split and symbols.json copies are
never copied. Idempotent: re-running overwrites with the current scratch contents.
"""
import glob
import json
import os
import shutil
import subprocess
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.chdir(ROOT)
DEST = os.path.join('research', 'fifty_objects_20260925')
LIMIT = 400 * 1024
skipped = []
copied = 0
def want_dir(rel):
parts = rel.replace('\\', '/').split('/')
return not any(p in ('split', 'config', '__pycache__') or p.startswith('split_') for p in parts)
def copy(src, dst):
global copied
if os.path.getsize(src) > LIMIT:
skipped.append('%s (%d bytes)' % (src, os.path.getsize(src)))
return
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copyfile(src, dst)
copied += 1
for slug in sorted(os.listdir(os.path.join('scratch', 'w'))):
base = os.path.join('scratch', 'w', slug)
if not os.path.isdir(base):
continue
for dirpath, dirnames, filenames in os.walk(base):
rel = os.path.relpath(dirpath, base)
if not want_dir(rel):
dirnames[:] = []
continue
for f in filenames:
ext = os.path.splitext(f)[1].lower()
if ext in ('.md', '.patch', '.diff') or f.startswith('APPLY_ORDER') or f == 'SHA256SUMS.txt':
copy(os.path.join(dirpath, f), os.path.join(DEST, 'w', slug, rel, f))
# named candidate sources are stored as diffs against the tree's version of the unit's .c
named = {}
for res in glob.glob(os.path.join('scratch', 'campaign', 'wave*', 'result.json')):
data = json.load(open(res, encoding='utf-8'))
for r in data.get('results', []):
for u in (r or {}).get('units', []) or []:
src = (u.get('unit') or '').split(' ')[0]
src = src if src.endswith('.c') else src + '.c'
for c in u.get('candidate_files', []) or []:
if c.lower().endswith('.c'):
named[os.path.normpath(c)] = src
for c, src in sorted(named.items()):
rel = os.path.relpath(c, os.path.join(ROOT, 'scratch', 'w'))
if rel.startswith('..') or not os.path.exists(c):
continue
dst = os.path.join(DEST, 'w', rel[:-2] + '.vs_tree.diff')
if os.path.exists(src):
d = subprocess.run(['git', 'diff', '--no-index', '--ignore-cr-at-eol', src, c], capture_output=True, text=True,
encoding='utf-8', errors='replace').stdout
if d and len(d) > 60000:
skipped.append('%s (diff vs %s is %d bytes; wrong mapping or whole-file rewrite)' % (c, src, len(d)))
elif d:
os.makedirs(os.path.dirname(dst), exist_ok=True)
open(dst, 'w', encoding='utf-8', newline='\n').write(d)
copied += 1
else:
copy(c, os.path.join(DEST, 'w', rel))
for rec in glob.glob(os.path.join('scratch', 'campaign', 'wave*', 'units', '*.md')):
wave = rec.split(os.sep)[-3]
copy(rec, os.path.join(DEST, 'results', wave, os.path.basename(rec)))
# the integrator kit that applies these packets
for f in ('batch_gate.py', 'admit.py', 'sym_edit.py', 'sym_ops_from_copy.py', 'rebaseline_parks.py', 'audit_battery.py', 'curate.py', 'WORKER_BRIEF.md'):
copy(os.path.join('scratch', 'campaign', f), os.path.join(DEST, 'integrator_kit', f))
copy(os.path.join('scratch', 'patch_ninja.py'), os.path.join(DEST, 'integrator_kit', 'patch_ninja.py'))
with open(os.path.join(DEST, '.gitattributes'), 'w', newline='\n') as fh:
fh.write('# evidence copies are kept byte-for-byte (patches must still apply) and are not style-checked\n'
'* -text -whitespace\n')
with open(os.path.join(DEST, 'SKIPPED.txt'), 'w', encoding='utf-8', newline='\n') as fh:
fh.write('Files over %d bytes left in scratch (not copied):\n' % LIMIT)
for s in skipped:
fh.write(s + '\n')
print('copied', copied, 'skipped', len(skipped))
@@ -0,0 +1,13 @@
"""Patch build.ninja's cl rule to the absolute backslash CL path (run after every configure)."""
import os
BS = chr(92)
root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
cl = BS.join([root.replace('/', BS), 'xbox', 'bin', 'vc7', 'CL.Exe'])
p = os.path.join(root, 'build.ninja')
s = open(p).read()
old = 'command = xbox/bin/vc7/CL.Exe'
n = s.count(old)
s = s.replace(old, 'command = ' + cl)
open(p, 'w').write(s)
print('patched', n, cl)
@@ -0,0 +1,84 @@
"""Re-baseline named stale parks to the current build's base measurements (textual, in place).
python scratch/campaign/rebaseline_parks.py "<unit>::<function>" [...] [--note "text"]
Runs tools.parked_functions validation against build/report.json, and for each NAMED key that is
stale only in its base block / objdiff_percent (never target), rewrites those lines in
config/parked.json. With --note, appends the sentence to that entry's evidence string.
Any other stale or invalid entry is reported and left alone.
"""
import json
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from tools.parked_functions import validate_parked_functions # noqa: E402
root = Path(__file__).resolve().parents[2]
args = sys.argv[1:]
note = None
if '--note' in args:
i = args.index('--note')
note = args[i + 1]
del args[i:i + 2]
wanted = set(args)
res = validate_parked_functions(root, root / 'build' / 'report.json', root / 'objdiff.json',
root / 'config' / 'parked.json')
path = root / 'config' / 'parked.json'
raw = path.read_bytes().decode('utf-8')
nl = '\r\n' if '\r\n' in raw else '\n'
lines = raw.split(nl)
done = []
for item in res['stale']:
key = '%s::%s' % (item['unit'], item['function'])
if key not in wanted:
print('stale (not named, left alone):', key, item.get('differences'))
continue
if any(d['measurement'].startswith('target') for d in item['differences']):
sys.exit('ABORT: target measurement changed for ' + key)
fn_pat = '"function": %s,' % json.dumps(item['function'])
unit_pat = '"unit": %s,' % json.dumps(item['unit'])
hits = [k for k, l in enumerate(lines) if l.strip() == fn_pat and lines[k - 1].strip() == unit_pat]
if len(hits) != 1:
sys.exit('ABORT: %d entry matches for %s' % (len(hits), key))
k = hits[0]
cur = item['measurements']
end = k
depth = 1 # entry '{' opened on line k-2
for j in range(k, len(lines)):
depth += lines[j].count('{') - lines[j].count('}')
if depth <= 0:
end = j
break
span = range(k, end + 1)
b = next(j for j in span if lines[j].strip().startswith('"base":'))
if lines[b].strip() == '"base": {':
for j, field in ((b + 1, 'size'), (b + 2, 'relocation_count'), (b + 3, 'normalized_sha256')):
m = re.match(r'^(\s*"%s": )(.*?)(,?)$' % field, lines[j])
if not m:
sys.exit('ABORT: layout of %s near line %d' % (key, j + 1))
lines[j] = m.group(1) + json.dumps(cur['base'][field]) + m.group(3)
else:
m = re.match(r'^(\s*"base": )\{.*\}(,?)$', lines[b])
if not m:
sys.exit('ABORT: compact layout of %s near line %d' % (key, b + 1))
lines[b] = m.group(1) + '{ "size": %d, "relocation_count": %d, "normalized_sha256": %s }' % (
cur['base']['size'], cur['base']['relocation_count'], json.dumps(cur['base']['normalized_sha256'])) + m.group(2)
o = next(j for j in span if lines[j].strip().startswith('"objdiff_percent":'))
m = re.match(r'^(\s*"objdiff_percent": )(.*?)(,?)$', lines[o])
lines[o] = m.group(1) + json.dumps(cur['objdiff_percent']) + m.group(3)
if note:
e = next(j for j in span if lines[j].strip().startswith('"evidence":'))
m = re.match(r'^(\s*"evidence": ")(.*)(",?)$', lines[e])
lines[e] = m.group(1) + m.group(2) + ' ' + json.dumps(note)[1:-1] + m.group(3)
done.append(key)
print('rebaselined', key, cur['base'], cur['objdiff_percent'])
missing = wanted - set(done)
if missing:
print('named but not stale (unchanged):', sorted(missing))
path.write_bytes(nl.join(lines).encode('utf-8'))
json.loads(path.read_text(encoding='utf-8'))
for inv in res['invalid']:
print('invalid (left alone):', inv)
@@ -0,0 +1,56 @@
"""In-place config/symbols.json line surgery (never re-serialises; preserves order and CRLF).
python scratch/campaign/sym_edit.py ops.json [--dry]
ops.json: list of operations, each keyed by the exact file_offset and current name:
{"op": "static", "file_offset": 75280, "name": "_foo"} -> add "static": true
{"op": "unstatic", "file_offset": 518208, "name": "_foo"} -> remove "static": true
{"op": "rename", "file_offset": 2600880, "name": "_old", "new": "_new"}
{"op": "insert_after", "file_offset": 2600880, "name": "_anchor", "line": "{ ... },"}
Each op must match exactly one line (file_offset AND name) or the script aborts.
"""
import json
import re
import sys
ops = json.load(open(sys.argv[1]))
dry = '--dry' in sys.argv
p = 'config/symbols.json'
raw = open(p, 'rb').read().decode('utf-8')
crlf = '\r\n' in raw
lines = raw.split('\r\n' if crlf else '\n')
def find(off, name):
pat = re.compile(r'"file_offset": %d, "flags": \d+, "name": "%s"' % (off, re.escape(name)))
hits = [i for i, l in enumerate(lines) if pat.search(l)]
if len(hits) != 1:
sys.exit('ABORT: %d matches for %s @%d' % (len(hits), name, off))
return hits[0]
inserts = []
for op in ops:
i = find(op['file_offset'], op['name'])
line = lines[i]
if op['op'] == 'static':
if '"static": true' in line:
print('already static', op['name'])
continue
lines[i] = re.sub(r'\s*\}(,?)\s*$', r', "static": true }\1', line)
elif op['op'] == 'unstatic':
lines[i] = line.replace(', "static": true', '')
elif op['op'] == 'rename':
lines[i] = line.replace('"name": "%s"' % op['name'], '"name": "%s"' % op['new'])
elif op['op'] == 'insert_after':
lines.insert(i + 1, op['line'])
print('inserted after %s: %s' % (op['name'], op['line']))
continue
else:
sys.exit('unknown op ' + op['op'])
print('%-12s %s -> %s' % (op['op'], line.strip(), lines[i].strip()))
for i, l in sorted(inserts, reverse=True):
lines.insert(i + 1, l)
print('inserted after line %d: %s' % (i + 1, l))
if not dry:
open(p, 'wb').write(('\r\n' if crlf else '\n').join(lines).encode('utf-8'))
@@ -0,0 +1,68 @@
"""Derive sym_edit.py ops from a worker's edited symbols.json copy vs HEAD's config/symbols.json.
python scratch/campaign/sym_ops_from_copy.py <worker_symbols.json> <out_ops.json> [<base-ref>]
The worker copy is diffed against <base-ref>:config/symbols.json (default HEAD); use the commit the
worker started from so later integrated edits are not read as reversions.
Handles in-place static add/remove, renames at the same file_offset, and inserted lines.
"""
import difflib
import json
import re
import subprocess
import sys
base = subprocess.run(['git', 'show', (sys.argv[3] if len(sys.argv) > 3 else 'HEAD') + ':config/symbols.json'], capture_output=True, text=True, encoding='utf-8').stdout
base = base.replace('\r\n', '\n').split('\n')
new = open(sys.argv[1], 'rb').read().decode('utf-8').replace('\r\n', '\n').split('\n')
R = re.compile(r'"file_offset": (\d+), "flags": \d+, "name": "([^"]+)"')
ops = []
sm = difflib.SequenceMatcher(a=base, b=new, autojunk=False)
for tag, i1, i2, j1, j2 in sm.get_opcodes():
if tag == 'equal':
continue
if tag == 'replace' and (i2 - i1) == (j2 - j1):
for a, b in zip(base[i1:i2], new[j1:j2]):
ma, mb = R.search(a), R.search(b)
assert ma and mb and ma.group(1) == mb.group(1), (a, b)
off, name = int(ma.group(1)), ma.group(2)
if ma.group(2) != mb.group(2):
ops.append({'op': 'rename', 'file_offset': off, 'name': name, 'new': mb.group(2)})
name = mb.group(2)
sa, sb = '"static": true' in a, '"static": true' in b
if sb and not sa:
ops.append({'op': 'static', 'file_offset': off, 'name': name})
elif sa and not sb:
ops.append({'op': 'unstatic', 'file_offset': off, 'name': name})
elif tag == 'insert':
anchor = R.search(base[i1 - 1])
prev = (int(anchor.group(1)), anchor.group(2))
# anchor must reference the (possibly renamed) previous line in new
pm = R.search(new[j1 - 1])
for k, l in enumerate(new[j1:j2]):
ops.append({'op': 'insert_after', 'file_offset': int(pm.group(1)), 'name': pm.group(2), 'line': l.strip()})
pm = R.search(l)
elif tag == 'replace':
# mixed: pair the common prefix, insert the rest
n = min(i2 - i1, j2 - j1)
for a, b in zip(base[i1:i1 + n], new[j1:j1 + n]):
ma, mb = R.search(a), R.search(b)
assert ma and mb and ma.group(1) == mb.group(1), (a, b)
off, name = int(ma.group(1)), ma.group(2)
if ma.group(2) != mb.group(2):
ops.append({'op': 'rename', 'file_offset': off, 'name': name, 'new': mb.group(2)})
name = mb.group(2)
if '"static": true' in b and '"static": true' not in a:
ops.append({'op': 'static', 'file_offset': off, 'name': name})
elif '"static": true' in a and '"static": true' not in b:
ops.append({'op': 'unstatic', 'file_offset': off, 'name': name})
assert (i2 - i1) <= (j2 - j1), 'deletions unsupported'
pm = R.search(new[j1 + n - 1])
for l in new[j1 + n:j2]:
ops.append({'op': 'insert_after', 'file_offset': int(pm.group(1)), 'name': pm.group(2), 'line': l.strip()})
pm = R.search(l)
else:
raise SystemExit('unsupported opcode %s at %d' % (tag, i1))
json.dump(ops, open(sys.argv[2], 'w'), indent=1)
print(len(ops), 'ops')
for o in ops:
print(o)
@@ -0,0 +1,73 @@
# review source/ai/action_charge
approve=False
## checks
I re-measured everything independently in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_action_charge\ (REVIEW.md, LEDGER.md, cand_gate.txt, variants_gate.txt, v\*.c/.obj, mkv*.py).
Byte and whole-object claims (all confirmed):
- gate.py --source cand.c --all (cand.c is the worker's candidate.c, sha 442f4388): EXACT 3248 _action_charge_perform, == exact 22 residual 0 unwritten 0. A fresh production compile gives [size 3264!=3248, sha] and 21/1.
- object_audit: PASS, 37 January symbols, 0 differ. The surplus list is identical to the production compile's.
- coffsecs against build/base/source/ai/action_charge.obj: only .text perform and .debug$S differ.
- relocdiff: 84/84, 0 differing rows.
- provider_link: SELECTED-PROVIDER LINK PASS.
- surplus_identity: 2/2 IDENTICAL.
- pdb_storage: 0 disagreements.
- build/report.json: data 256/256.
- fake_match_scan: 0 leads.
- CL /Zs /W3: same 12 header warnings as production, none from action_charge.c.
- `patch -p1 --binary` on a copy of production reproduces cand.c byte-for-byte.
- Parks, rejections and config.json index 470 checked; no action_charge entries.
- HCEX.pdb dump: neutral (no scalar local names).
/Od evidence re-read from od_perform.txt, with constants read from the exe as data only:
- 0.4f at 0x93dd48 and 0.3f at 0x93dd44.
- 0xa31df4 is global_forward2d: global_forward3d is 12 pointers later at 0xa31e24, matching the order in real_math.c.
- C1, C2, C4, C5 and C6 are integer 8-byte copies in /Od (0x4310c8, 0x4311c7, 0x430f04, 0x430cec, 0x430d28). Scalar copies there are movss.
- target_point is dead in /Od as well: projectile_aim_ballistic at 0x430e42 is passed prop+0x9c, i.e. &prop->body_position.
Strip tests and diagnostics (lab only):
- no_a4 (all dead leap-arm code removed): residual.
- no_a4_vec (only the dead alignment_vector chain removed, including casts C5/C6): EXACT 22/22, audit PASS.
- no_a4_tp, tp_noz, tp_decl, tp_long, tp_real: residual.
- tp_used (pass &target_point to projectile_aim_ballistic): size 3296, 85 relocs.
- c1m and c2m (member-wise copies): residual.
- full_od and plus_a3b: residual.
- swap_a3a_a3b (name the leap dot result instead of the suicide one): EXACT.
- no_named: residual.
- x_top, x_same_site_z, and x_suicide (a made-up dead `scratch_vector = unit->object.translational_velocity; scratch_vector.k += 0.4f;` in the suicide arm): all EXACT.
- Initialised dummy `long m8_N = 0;` locals: sc0 is EXACT at D=6/7, which reproduces the worker's result. no_a4 is EXACT at D=3..7.
- Uninitialised dummy locals: sc0 stays residual at D=6/7.
## issues
Recommendation: HOLD_FOR_OWNER. Do not apply production.patch and do not flip config.json index 470. The byte and audit claims are all true, but the closure is not admissible under the brief or the owner rulings.
B1 (blocking). The lever that closes the function is zero-byte dead code with no January evidence behind it: `real_point3d target_point = prop->body_position; target_point.z += 0.4f;`, and target_point is never read.
- In the /Od build it is dead too: the raised aim point is never passed to projectile_aim_ballistic. It looks like a defect in that later build.
- The byte match cannot confirm it. A made-up dead aggregate copy-and-modify in the suicide arm (x_suicide) closes the function to EXACT just as well. So does the same code at another position (x_top) or with another source vector (x_same_site_z).
- With all the dead leap-arm code removed, 3 to 7 initialised dummy longs also give EXACT. Three `long m8_N = 0;` locals are a drop-in replacement for the landed dead code.
- This is the name-count / IL-oracle instrument, which the brief says must never land. Its only support is a later build that is known to differ in this same function (lead-fraction arithmetic, the state layout).
- Astra research8 and the Lane B reviewer both declined this route before.
- Reproducing an apparent authentic bug needs target proof, a BUG comment and owner approval. The patch carries no comment at all.
B2 (blocking). Rule-24 casts C1 and C2 are load-bearing: the member-wise forms c1m and c2m are residual. Owner ruling #6 (2026-09-21) requires a byte-inert cast. The only exception is the per-site admission of the action_obey.c aggregate copy (3ba2eb91). C1/C2 are the same class and their /Od integer-copy evidence is correct (0x4310c8, 0x4311c7), but they need an explicit per-site owner ruling and cannot be self-admitted.
B3. The set of named result locals is fitted to the count, not recovered.
- The /Od build names both 3D dot results (-0xf4 suicide, -0xf8 leap), yet the full /Od set is residual.
- Naming the leap dot instead of the suicide dot is also EXACT (swap_a3a_a3b).
- The landed closing_velocity + alignment pair is one of many exact choices, and it departs from the /Od build on purpose.
Inaccurate worker claims:
- "A4 is REQUIRED" is only true of the target_point half. no_a4_vec drops the whole dead alignment_vector normalize2d chain, including rule-24 casts C5 and C6, and is still EXACT 22/22 with object_audit PASS. The candidate therefore carries an unnecessary dead block and two unnecessary view casts, and C5's "load-bearing" label is misleading.
- "The key is a count of IL locals" is too broad. Uninitialised declarations do not count (sc0 plus 6/7 bare longs stays residual; so does tp_decl). What counts is locals that receive an assignment.
Minor, only relevant if the owner admits the items:
- The dead code breaks rule 21 unless it is commented as inferred or as an authentic defect.
- The new casts use `(real_vector2d *)`, while the admitted precedent uses `(real_vector2d const *)`.
- Removing unit_index and target_range, and `success = FALSE`, are /Od-attested and fine.
- No header, config or symbols changes are involved, so there is no risk to other objects.
Questions to put to the owner:
(a) Admit C1/C2 on the action_obey precedent?
(b) Admit the /Od-only dead target_point copy-and-modify, knowing the bytes cannot tell it apart from fillers?
(c) Which named-result subset?
If (b) is refused, no admissible exact form is known. If (b) is admitted, land the minimal no_a4_vec.c form (scratch/w/review_action_charge/v/no_a4_vec.c), with a BUG/inferred comment and a matching log that discloses every item.
@@ -0,0 +1,56 @@
# review source/ai/actor_looking :: _actor_look_idle_find_prop (FUNCTIONS_EXACT_OBJECT_BLOCKED)
approve=True
## checks
All checks were re-run independently. Notes are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_actor_looking\REVIEW.md. The orchestrator's slug contains '::', which Windows paths cannot contain, so the directory is named review_actor_looking.
(1) Patch reproduction: I ran `git apply` on production.patch in a scratch repo with core.autocrlf=true. It applies cleanly and gives a file byte-identical to cand_final.c, with CRLF kept. The patch itself is LF-only, and GNU patch --binary does not apply it.
(2) gate.py --source cand.c --all: 15 exact, 1 residual (_actor_look_update, 4704 vs 4720), 0 unwritten. Production is 14 exact, 2 residual. No FORBIDDEN rows, and _point_from_line3d is absent.
(3) I compared every section of my rebuilt base.obj against cand.obj. Only _actor_look_idle_find_prop changes (b145a6e8 -> d9c69b27), and _cosine and _sine are new. _actor_look_update (0ecf37ee) and decode_direction are unchanged; only their $L label numbers move. The rebuilt base matches build/base.
(4) object_audit: FAIL(2) -> FAIL(1). The only remaining DIFF is _actor_look_update. 80 January symbols, 0 differ. New surplus is +_cosine and +_sine, 16 B each.
(5) objdiff 3.3.1 on a mini project (od_proj/): code 3894 -> 4496, so find_prop goes from 99.145% to 100% (+602). Data is 1700/1700 in both.
(6) Surplus identity: _cosine and _sine are section_infos_equal (IDENTICAL) to January's only definer, split actor_combat.obj, and all 17 pre-existing surplus COMDATs are still IDENTICAL. provider_link --baseline=production: PASS for both. Our actor_combat.obj defines both, so the pair-link is a real test. nodup_census finds 0 NODUP copies.
(7) pdb_storage: 0 disagreements. No parks, admission rejections or semantic_data entries exist for this unit.
(8) /W3 output is identical for base and candidate. The only C4013 is the existing game_time_get one.
(9) /Od re-derivation with odbuild: the TU calls wrapper 0x455220 at exactly the 15 patched cosine sites:
- find_prop 0x45dbd0 x4
- find_random_vector 0x45d670 x2
- update 0x45f5d0 x5, including the stationary site 0x4611d3, whose result is stored to its own local slot
- test_validity 0x462380 x4, identified by its calls to 0x4620b0 and 0x4621c0
Sine wrapper 0x455320 is called x2 in find_random_vector. The chain is 0x455220 -> 0x4551f0 (direct cos(float)) -> 0x4553b0. The direct level has its own callers elsewhere, so the /Od build distinguishes `cosine(x)` from `cos(x)`. Positive control: motion_sensor.c:1118 spells a direct `sin(`, and its /Od twin 0x642330 calls 0x4552f0 directly. HCEX shows the stationary local is held in a register and has no recorded name.
(10) STRIP TEST and plainer spellings: `(real)cos` (production) is residual, and a lab with plain `cos(` (no cast) in find_prop (labA_plaincos.c) is also residual. The candidate adds no decoration: it removes a cast and uses an existing genuine real_math.h helper that 32 .c files already use, and every site is /Od-attested.
(11) c1.c gated: find_prop is EXACT, but only `_cosine` is emitted.
(12) Ledger review: no prior cosine()-helper probe was recorded for this unit. The only earlier non-adoption was rasterizer_xbox_water on 2026-09-14, where the helper made no byte difference and a lane-local freeze was in force. That was before the owner's 2026-09-21 ruling that admits the all-inlined header-inline COMDAT class.
## issues
Nothing blocks approval. Conditions and corrections for the integrator:
(a) The integrator must still run the full ninja build, the stable-verdict sweep (diff the rows per function, not the aggregate count) and the full COMDAT ownership sweep, and see zero regressions, before the new _cosine and _sine emissions are admitted under rule 6. Both new symbols must be named in the ledger.
(b) Apply with `git apply` (worktree core.autocrlf=true). The patch is LF-only, the working file is CRLF, and GNU patch fails.
(c) Answer to the worker's rule-6 question. _cosine does not depend on the non-exact _actor_look_update:
- c1.c emits the identical _cosine without touching update.
- The strictly exact callers are find_prop, which needs the helper to match, plus test_validity and find_random_vector.
- update's bytes do not change.
_sine has one caller, find_random_vector, which is exact under both spellings, so only per-site /Od attestation supports it. That fits the admitted class (the caller is strictly exact). If the owner wants no surplus that the bytes do not force, the fallback is c1.c (emits only _cosine), at the cost of a TU that spells trig two ways. I recommend production.patch.
(d) Mistakes in the worker's report (none change the verdict):
- It says "15 sites in all"; there are 17 call sites (15 cosine + 2 sine).
- It says c1 has "the same surplus"; it does not, because c1 emits only _cosine.
- It says sine chains to 0x4553b0; it chains 0x455320 -> 0x4552f0 -> 0x455430. Cosine goes through the intermediate 0x4551f0.
(e) The object stays blocked by _actor_look_update (4704 vs 4720, register/slot residual; the worker's stop rule fired). Credit to book: +602 meaningful bytes (608 padded) for _actor_look_idle_find_prop only.
@@ -0,0 +1,25 @@
# review source/ai/actor_perception
approve=True
## checks
All checks were independent re-runs. Notes are in C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/review_actor_perception/REVIEW.md.
(1) Applied production.patch, fallback.patch and final_nostatic.patch with patch --binary to fresh copies of source/ai/actor_perception.c (tracked tree clean at e9e62b78). Each applies cleanly, keeps consistent CRLF, and is byte-identical to the worker's final_full.c, fallback.c and final_nostatic.c.
(2) gate.py --all results: base 42/2 (blockage [sha]; danger_zone [size 1504!=1472, sha]); production 44/0; final_nostatic 44/0; fallback 43/1 (blockage [sha] only). A fresh compile of production equals build/base (secdiff shows no differences).
(3) object_audit on the current split: final_nostatic PASS. production FAIL(5), all five being the storage 2/3 rows that Packet C addresses. fallback FAIL(1), the blockage section only. Against the worker's scratch csplit regen (833 objects; only actor_perception.obj differs, and only by storage 2->3 on the 5 symbols; all sections equal), my production object gives 121 symbols, 0 differ, PASS. All 76 January data sections are ok, and build/report.json data is 2068/2068.
(4) Surplus, checked with my own surplus_rev.py (candidate vs January's selected copy, NEW vs build/base). NEW symbols are _point_from_line3d (January's actor_perception has it UNDEF), _normalize2d, _scale_vector2d, _dot_product2d (action_charge), _negate_vector3d (action_obey) and __real@42340000 (actions). All are section_infos_equal. provider_link --baseline PASSes for all six in production and for both in fallback. nodup_census finds zero NODUP definers. The pre-existing _distance3d/_distance_squared2d vs action_vehicle NODUP conflict is confirmed (provider_link without baseline: FAIL(2), same as base).
(5) Relocation census: the candidate matches January for _point_from_line3d (actor_emotion_update only), __real@42340000 (danger_zone x3) and out-of-line knowledge calls from danger_zone (x2). Base had these wrong.
(6) Knowledge body. It is semantically identical to production. The TU-local enumerator investigate=2 matches 3 other TUs. January's bytes (dis_range) show `cmp ax,0xffff; jne` at 0xcb. The /Od build at 0x470910 shows the NONE sentinel and a single return. Lab variants over fallback: k_chain (the /Od else-if chain), k_orphan (/Od named orphan local with ?:) and k_od (fully /Od-shaped) are all 43/1 with knowledge EXACT 240 and danger_zone EXACT 1472; k_od is also 44/0 in the production and nostatic contexts. So the inline flip comes from the sentinel structure itself, not from one particular spelling. Lane A's no-source-spelling proof is refuted, and no pragma is used.
(7) Blockage. My own odbuild dump of 0x4710a0 confirms the aggregate integer copy, the helper sequence, the explicit else zero store at 0x4711d0, the `if (blockage > 0)` gate, the empty then-arm (jbe then jmp at 0x4711f9/0x4711fb), and the RTC names. The /Od helpers check out: 0x42df20 is magnitude_squared2d, 0x42def0 is magnitude2d and 0x42dd30 is dot_product2d. HCEX DIA2Dump confirms the parameter names and source_planar_direction.
Strip tests: removing the 5 pointer casts gives 44/0 (casts are byte-inert, so they pass). A castless field copy gives [size 384!=400]. The plain `!(hes<0.36f)` form gives [sha]. Dropping `else blockage = 0` gives [size 384!=400].
(8) Storage: all 5 proposed statics are absent from cachebeta publics, and every other symbol's public presence matches its storage. There are no UNDEF references in any build/split or build/base object, and no text references in any other source/ or libs/ file. symbols_static.patch edits 5 lines in place. Packet C is codegen-inert (nostatic and production code sections are identical).
(9) fake_match_scan reports 1 lead: the blockage empty-then-else, which has /Od evidence. There are no header or config.json changes.
(10) I built A_plus_C.patch (Packet A + Packet C, without B). It applies cleanly and gates 43/1. Its code is identical to fallback. Audit against the regen split: 0 symbol diffs, and only the blockage section differs.
## issues
I found no blocking defects. The verdict FUNCTIONS_EXACT_OBJECT_BLOCKED is accurate. Every worker claim reproduces.
Conditions and notes for the integrator:
(a) Packet A (fallback.patch) can land now under the committed rule-6 exception and ruling 20260921 #1. Packet C is also admissible. The worker shipped no A+C-without-B patch. If Packet B is held, apply the review-built C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/review_actor_perception/A_plus_C.patch instead of fallback.patch, then symbols_static.patch, then a csplit-only regen. Do not run --forbid-emitted-symbol _point_from_line3d on this unit.
(b) Packet B must stay held until the owner explicitly says yes to owner-packet s5. The ruling request must disclose that THREE constructs are load-bearing and fail the plain-spelling strip test, supported only by first-party /Od attestation: (1) the aggregate view copy (a field copy gives [size 384!=400]); (2) the empty then-arm of hes<0.36 (the plain `!(hes<0.36f)` gives [sha]); (3) the redundant `else blockage = 0;` (dropping it gives [size 384!=400]). Per ruling 6 it must also name every newly emitted COMDAT: _normalize2d, _scale_vector2d, _dot_product2d and _negate_vector3d. All are byte-identical and pass provider_link. If the owner says yes, apply production.patch and retire the parked.json blockage entry.
(c) Optional: the knowledge body uses the HCEA-lift spelling, a combined || and `_actor_knowledge_searching + (x != FALSE)`. That differs from the first-party /Od shape: an else-if chain, a named orphan local with `?: definite : searching`, and a final if/else. The fully /Od-shaped body at scratch/w/review_actor_perception/lab/k_od.txt is equally exact in every context: 43/1 over fallback, 44/0 over production and nostatic. It is clearer as semantics, so prefer it if convenient.
(d) The whole object stays blocked by the PRE-EXISTING _distance3d/_distance_squared2d SELECT_ANY surplus. It gives LNK2005 against action_vehicle's NODUP hand copies, and _distance3d is not identical to January's copy.
(e) The integrator must run a full ninja build and a stable per-function sweep. After the csplit-only regen, run a full ninja before stable_verdicts; a stale base makes the verdicts lie. My object_audit PASS used the worker's scratch regen, so the integrator must regenerate the split itself.
@@ -0,0 +1,62 @@
# review source/bungie_net/network/transport_endpoint_winsock
approve=True
## checks
All checks were independent re-runs. Notes are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_transport_endpoint_winsock\REVIEW.md.
(1) Patches
- All 3 patches pass `git apply --check` on a clean worktree at e9e62b78.
- production.patch applied to a copy of the source gives a file byte-identical to cand_final.c; storage_only.patch gives a file byte-identical to storage_only.c.
- symbols.patch result equals my own in-place edit: same 23,844 lines, only lines 1396 and 22943 change, CRLF preserved.
(2) Gate
- cand_final.c --all: 30/30 EXACT.
- Production: 29/1 (`_connect_endpoint_process@4` [sha]).
- storage_only.c: 29/1.
(3) Storage
- cachebeta publics have no entry at RVA 0x713A0 or 0x31CE38. The neighbours (`_create_transport_endpoint`, `_transport_initialized`, `_global_client_active`, `_global_key_depth`) are present.
- pdb_storage on production: exactly these 2 disagreements.
- Raw COFF scan of 833 split and 621 base objects: both names occur only in transport_endpoint_winsock.obj.
- grep of source/, config/ and tools/: no other reference and no header prototype.
(4) Split regen
- I ran csplit on my own config copy.
- Of 833 objects, only transport_endpoint_winsock.obj differs, and only by 2 bytes: the storage-class byte of symbols 13 and 409, 2 to 3.
- Raw section compare, production vs storage_only: 0 non-debug section differences; only the 2 storage classes change. Byte-inert confirmed.
(5) object_audit (split path substituted for the regenerated one)
- Against the regenerated split: cand PASS (135 symbols, 0 differ); storage_only FAIL(1), the .text only; production FAIL(3). This confirms the atomicity requirement.
- Against the current split: cand FAIL(2), storage only, as expected before regen.
(6) Original-bug evidence
- January disasm: the only store to [ebp+8] is on the success path (+0xdf). The failure arm is `or ebx,-1` alone. The +0x110 read is on the join.
- October oct-betaP.xbe, read as data only with my own script: 0x126 bytes compared, 100 relocation bytes masked, 0 of 194 meaningful bytes differ. Every relocation target resolves to a single consistent October VA.
- Re-ran lab_B (NULL in the else arm): residual, identical to production.
- Re-ran lab_D (NULL as a statement after connect_endpoint): residual; the only extra instruction is `mov [ebp+8],0` at +0xb9.
- /Od build: `input->thread` matches only the dns sister and 0x509790; there is no `input->ep` string, so the function is absent.
(7) Surplus and data
- surplus_identity: 0 candidate-only code COMDATs.
- provider_link: PASS in both orders (3 transport_address literals, 3 D3D tables).
- report.json: .bss 524 and .rdata 2040, both 100%.
- The inherited <100% functions are auto-credited as `hidden_exact` by audit_semantic_matches. local_symbols_skipped counts only `$` names, so static linkage does not drop credit.
- The park's base sha is unchanged by packet 1.
(8) Hygiene
- fake_match_scan: 0 leads.
- /W3 /WX: EXACT.
- The house-rules review of both diffs found no decoration, header edits or new names.
## issues
No blocking issues. All material worker claims reproduced.
Non-blocking nits:
(a) BUG tag form. The owner packet's section-4 decision text says rows would land with the methodology's `BUG (preserved for exact matching)` comment. production.patch uses the playbook's `BUG (original)` form instead. Both forms have repo precedent (ai_profile.c:276 says "January and October"). The owner or integrator should pick one at ruling time.
(b) Wording. "0 of 0x126 meaningful bytes" should read: 0x126 bytes compared, 100 relocation bytes masked, 0 of 194 meaningful bytes differ.
(c) Sequencing. The new October byte-identity and A/B/D evidence should be recorded now, in the owner-packet row and the object log, alongside packet 1. The owner needs it to rule, and the playbook requires the safe form's measured result in the object log.
(d) Optional hardening. A symbol_ownership.json .bss snapshot for this unit would lock the new static linkage. It is not required.
Packet 1 (storage_only.patch, symbols.patch lines 1396 and 22943, and a csplit-only regen) must land atomically. Production against the regenerated split fails 3 audit rows if only one side lands.
Packet 2 (production.patch, park retirement) stays OWNER-GATED under brief section 2 until the owner rules on the section-4 original-bug row.
@@ -0,0 +1,29 @@
# review source/cache/xbox_texture_cache
approve=False
## checks
I re-ran every check independently in C:\halo-worktrees\claude-fifty-objects-20260925 (base e9e62b78, tracked tree clean). Notes and evidence are in scratch/w/review_xbox_texture_cache/ (REVIEW.md, LEDGER.md, gate_all.txt, gate_prod.txt, object_audit.txt, pdb_storage.txt, surplus_identity.txt, provider_link.txt, and the X*/F1* lab files).
(1) gate.py --source cand.c --all --forbid-emitted-symbol _point_from_line3d: 25 exact, 0 residual, 0 unwritten. cand.c sha256 is 55588ed4..., as the worker recorded. On production the gate gives 24 exact and 1 residual (steal_memory [sha]), so the edit moves only steal_memory.
(2) object_audit: every January-owned .text, .rdata and .bss section is OK. The audit still fails with 3 storage-class differences (split 2, ours 3) on IDirect3DBaseTexture8_IsBusy@4, IDirect3DDevice8_IsBusy@4 and IDirect3DDevice8_KickPushBuffer@4. pdb_storage shows none of the three is a cachebeta public, and the publics list does include decorated stdcall names, so their absence counts as evidence. The true storage is file-static, which is what we emit; the split metadata is the side that is wrong. rasterizer_xbox_debug is already Matching with 8 differences of the same kind.
(3) build/report.json: data 6344/6344. Code 3248/3457, and steal_memory (209 bytes) is the only function below 100%.
(4) surplus_identity: both COMDATs are IDENTICAL. provider_link in both orders: SELECTED-PROVIDER LINK PASS on all 19 surplus rows.
(5) Strip and oracle variants. If the named local is declared and initialised but not used, the function stays residual, so this is not a name-count effect. Using the local only in the return is EXACT, and using it only in the call is EXACT. This agrees with the worker's allocator decode: sm5 (named local assigned after the resize) stays residual, and R1-R4 are exact. R1 does not repeat w2 sm1, which built the end guard from the local.
(6) House rules on the diff: declaration and initialisation are combined; `byte *` matches base_address; there is no cast, parenthesis, pragma, dummy or helper change; the single return is kept. It passes.
(7) Patches: `git apply --check` exits 0. `git apply` in a throwaway copy with autocrlf=true, and `patch --binary -p1` with production_crlf.patch, both reproduce cand.c byte for byte.
(8) The park entry exists and its target sha is 7ffbda42, the same as the candidate.
(9) I audited the whole TU source for admission and found pre-existing debt (see issues).
(10) Lab F1: including bitmaps/bitmaps_internal.h in place of the caller-local prototype keeps 25/25 EXACT with the same surplus.
## issues
Split verdict. The steal_memory change is fine; marking the whole object complete is not.
FUNCTION-LEVEL LANDING APPROVED: apply scratch/w/xbox_texture_cache/production.patch (or production_crlf.patch) and retire the config/parked.json entry {source/cache/xbox_texture_cache, _texture_cache_steal_memory, unclassified} in the same integration. This is an ordinary exact closure of 209 meaningful bytes, then a full ninja and a stable sweep. The named local is genuine and not fake: the unused-local variant stays residual, and the exactness holds in every declaration order.
OBJECT_COMPLETE / Matching status NOT APPROVED. None of these blockers comes from this patch; they already exist in the TU:
(a) object_audit fails with 3 storage-class differences because symbols.json keeps the three XDK wrappers external. Truth from the PDB is static. The fix is `"static": true` on those entries plus a split regeneration, the same fix used in the 20260923 rasterizer_xbox_decals admission. The worker proposed no symbols.json change.
(b) There is a caller-local prototype of bitmap_get_pixel_data_size, although bitmaps/bitmaps_internal.h:43 owns it (rule 9). Lab F1 shows the include fix keeps all 25 functions exact.
(c) The public January functions texture_cache_debug_render, bitmap_format_to_d3d_format and bitmap_format_to_d3d_linear_format are prototyped only in the .c and not in texture_cache.h. On 20260923 lruv_cache was held for exactly this kind of debt. Fixing it is a header edit and needs a full sweep.
(d) The TU defines its own duplicate struct bitmap_data (the owner is bitmaps/bitmap_group.h:29) and its own bitmap type, format and flag enums. The 20260904 ledger explicitly named this Bitmap schema/prototype debt as a whole-object veto.
(e) `extern D3DDevice global_d3d_device;` is a consumer-local declaration whose type disagrees with the definition `D3DDevice *global_d3d_device` at rasterizer_xbox.c:856 and the declaration at rasterizer_xbox.h:164.
(f) The TU reads bitmap tags through a reserved-byte prefix view (xbox_bitmap_group_prefix) with a raw tag_get('bitm'), against rule 4. The worker also proposed no change to config.json status.
Do not promote the object until (a) through (f) are resolved or the owner rules on them.
@@ -0,0 +1,27 @@
# review source/camera/dead_camera
approve=True
## checks
All checks were run independently in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_dead_camera\ (notes are in REVIEW.md there).
(1) Patch integrity. production.patch applied to the worktree HEAD de77a780 gives 708198eb, which equals final_prod.c. owner_update_bug.patch on top of that gives 7186c901, which equals final_full.c. full_object.patch also gives 7186c901. Dry runs against source/camera/dead_camera.c are clean.
(2) gate.py --all. applied_full.c is EXACT 4/4 (new 288, update 1248, 176, 112). applied_prod.c is exact 3, residual 1 (update). HEAD is exact 2, residual 2. For _dead_camera_update and both helpers, prod is section-identical to the HEAD compile, which is identical to build/base. Only _dead_camera_new changes.
(3) object_audit on cand_full.obj: all 6 January sections are ok. The only symbol difference is _dead_camera_constants storage 2/3, and the surplus list is identical to base's. My per-section section_infos_equal check is True for all 6 sections against the current split and against the storage-3 simulated split. That simulated split is a one-byte (storage class) edit of the real split.
(4) Surplus. surplus_identity only reads build/base, so I wrote a candidate-object variant (surplus_cand.py). All 8 code COMDATs and the 11 rdata literals are byte-identical to base and to January's provider. provider_link on the candidate fails only on _valid_real_vector3d_axes2 against bored_camera's NODUP copy. The base object fails identically, and I confirmed the same failure on the Matching objects static_camera, orbiting_camera and first_person_camera.
(5) Data. report.json .rdata is 48/48 (100%) and the bytes are unchanged. I also ran a private objdiff 3.3.1 project (odproj/report.json). It reproduces build/report.json for HEAD (280 / 93.09% / 99.83%). The full candidate scores 1792/1792 code and 48/48 data against both the real and simulated-static splits. The landable prod alone scores 557 code (+277 over HEAD) and 48/48 data.
(6) PDB. pdb_storage reports _dead_camera_constants as PDB-public False. The cachebeta publics do list external const .rdata objects (_rasterizer_global_defaults, _MOUSE_YAW_SCALE, _step_size_table), so their absence here is sound evidence that the constants are file-static. Only the dead_camera split and base objects reference the symbol, and no other source file names it. orbiting_camera sets a precedent for both `static struct ..._constants const` and a flags-0 "static": true symbols.json line.
(7) Strip tests, all mine:
- final_prod without `static`: residual. The static is required, and the PDB backs it independently.
- The ternary replaced by HEAD's per-arm stores: residual. alndiff shows only the tail: January `mov eax,edi; mov [esi+0x28],eax` (a phi copy) against ours `mov [esi+0x28],edi`. January's own bytes and the /Od build's single store through temp [ebp-0x14] both confirm the single-store phi.
- HEAD + static + swapping only the pitch and timer stores: residual.
- HEAD locals kept (observer declared separately; distance, yaw and player_index locals) + static + swap + ternary: EXACT. The rewrites that only /Od supports have no effect on the bytes.
(8) /Od readouts. I re-read od_new.txt: pitch store before the timer store, direct distance/yaw stores, player_index stored to the camera and reloaded, the unit value through a temp, and the switch_timer slot layout. I re-read od_update.txt: 0x522eb5 `mov [ebp-8],-1` at the top of the switch block, with no else arm.
(9) fake_match_scan reports 0 leads on prod and full. There is no _point_from_line3d emission. A /W3 /WX compile is clean.
(10) House-rule review of the diff: one parameter per line, `real`, explicit return, declaration and initialisation combined, no new prototypes or helper expansion, and the assert unchanged. The logic is semantically identical.
(11) No config/parked, admission-rejection or semantic entries exist for the unit. The HCEA frozen combined topology and the recorded n1/w1 negatives were not repeated. Those were measured with the external constant, and the static is new evidence.
## issues
1. DEFECT in deliverable (2): scratch/w/dead_camera/symbols_static.patch has CRLF content lines, but config/symbols.json is LF in both the index and the worktree (0 CR). `patch --dry-run` fails with "different line endings", so the patch cannot be applied mechanically as delivered, and forcing it would inject a CR. Use the corrected LF copy, C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_dead_camera\symbols_static_lf.patch (its dry run is clean), or hand-edit line 14063 to `{ "file_offset": 2452200, "flags": 0, "name": "_dead_camera_constants", "static": true },`.
2. Integration order: land production.patch together with the symbols edit. Then run a csplit-only regen and a full ninja before trusting stable_verdicts, because a split-only regen leaves a stale base. Re-run object_audit to confirm storage 3/3. The storage-3 split was only simulated (a one-byte storage-class edit), not produced by csplit. objdiff credit is unaffected either way: 1792/1792 against both splits.
3. Documentation caveat: in the later /Od build, the timers come from shared literal-pool entries (0x93dd68 = 3.0f for both dead_timer and the singleplayer switch timer; 0x940190 = 15.0f). The constants struct no longer exists there, so /Od supports only the statement order, not the reads of dead_camera_constants. January's relocations (+0/+4/+8) support the struct and cachebeta supports the static. Say this in the landing ledger, not "/Od-attested constants".
4. The owner patch (3), the uninitialised next_unit_index with a BUG comment, must stay owner-gated. It is correctly reported and not landed. Step (4), flipping the status to Matching, only after the ruling and the regen. The landing ledger must record the provider-link FAIL on _valid_real_vector3d_axes2 against bored_camera's NODUP copy. The failure predates this change: the base object fails identically, and so do the Matching static, orbiting and first_person cameras.
5. No blocking correctness, house-rule or fake-matching problem was found. `static` and the single-store unit phi each change the bytes, and each has evidence that does not depend on the match. The other /Od shape rewrites have no effect on the bytes.
@@ -0,0 +1,70 @@
# review source/cseries/profile
approve=True
## checks
All notes are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_profile\REVIEW.md. The worktree was clean at HEAD e9e62b78, and no tracked file was edited.
1) Patch reproducibility. I applied all three patches to a temp copy outside the repo. Running git apply inside a repo subdirectory silently skips the paths, so a temp copy was needed. production.patch gives a file byte-identical to final.c. storage_only.patch gives storage_only.c. symbols.patch gives symbols.json.edited. All of them keep CRLF, and `git apply --check` returns rc=0 for all three.
2) Independent gates:
- final.c: 44 exact, 0 residual (cand.obj).
- storage_only.c: 43/1, the same as the production baseline (43/1, _compare_profile_sections [size 304!=288, sha]).
3) object_audit of cand.obj against the current split: every January-owned section is ok. There are 4 storage-only symbol diffs (_compare_profile_sections 3/2, _header_strings 3/2, _format_strings 3/2, _profile_globals 2/3). The surplus is .drectve plus 13 pooled literals.
4) Storage evidence:
- cachebeta_publics.txt lists _compare_profile_sections [0007E840], _header_strings [002DCD30] and _format_strings [002DCD38] as publics. These equal the symbols.json file_offsets 518208, 3001648 and 3001656.
- There is no public at 0031DF40 (_profile_globals).
- pdb_storage.py finds exactly these 4 disagreements among 133 symbols.
- symscan.py over every .obj in build/base and build/split: only profile.obj defines or references the 4 names, so there is no LNK2005 risk.
- config/symbol_ownership.json has no profile entry.
5) Independent split regeneration. I built my own config copy with symbols.patch applied and ran build/tools/csplit.exe into scratch/w/review_profile/split. Of 833 objects, exactly 1 differs (profile.obj), and the only change is the 4 storage classes.
- Audit against the regenerated split: cand.obj PASS (133/133 symbols). storage_only.obj FAIL(1), only the comparator .text. Production base FAIL(4). So the source and symbols.json must land atomically.
6) Stability:
- The comparator's target sha (d94ba79b...) is unchanged in the regenerated split. Its base sha (aebbef78...) is unchanged in storage_only.obj. So the park stays valid after Packet A.
- objcmp of base.obj vs storage_only.obj: only .debug$S differs, plus the 3 storage flips.
- objdiff 3.3.1 one-unit reports: Packet A gives numbers identical to now (96.83%, 42 functions, data 100%). The full candidate gives 99.15%, 43 functions, comparator 100%. The remaining _profile_frame_get_value 91.46% is inherited and marked hidden_exact in semantic_report.
7) Provider links:
- provider_link.py against the current split: the 3 names FAIL as "no January provider", which is expected while the split still marks them static.
- Against my regenerated split: SELECTED-PROVIDER LINK PASS (13 literals).
- surplus_identity: 0 code COMDATs.
8) /Od attestation. I re-dumped 0x551e50 and it is byte-identical to the worker's file.
- The bytes at 0x5520c0 spell "result".
- Callee 0x92db80 builds "The variable '...' is being used without being initialized.", so it is _RTC_UninitUse.
- The RTC flag at [ebp-0x15] is set before every `result` store.
- The explicit `result = 0` stores are at 0x551fd7 and 0x55205d, and there is a single epilogue.
- January's bytes independently show `mov eax,[ebp+8]` after system_exit(-1), and [ebp+8] is reused as the fidiv spill. So the shape is attested by both builds.
9) STRIP TESTS:
- S1 (`int result = 0;`, keeping the else arms): 43/1 [sha]. alndiff shows a live esi result plus push/pop edi.
- S2 (BUG comment removed): 44/44 EXACT, so the comment is byte-inert.
- S3 (uninitialised declaration plus a defined `result = 0` after the assert): 43/1 [sha].
- So the undefined read carries the match and cannot be stripped.
10) Other checks:
- fake_match_scan final.c: 7 leads, the same as production (inherited rdtsc asm).
- No object_admission_rejections entry.
- No test or tool pins these names or storage flags.
- The Lane D manifest ruling_queue[45] and opus5 w3 history are confirmed.
- House rules: the body keeps double (/Od uses movsd/divsd), the enum constants, match_assert and the single return. The non-static prototype in profile.c has an in-file precedent (`void find_profile_section(`, line 407).
## issues
No blocking issues. The verdict FUNCTIONS_EXACT_OBJECT_BLOCKED is accurate, and Packet A (storage) is correct and can land now. Packet B is correctly held for the owner's ruling on astra item 8 / claude_structural_owner_packet_20260923.md section 4. The undefined post-system_exit read carries the match: the defined alternatives S1 (`int result = 0;`) and S3 (`result = 0;` after the assert) both stay non-exact. So Packet B is admissible only through that ruling, backed by the double attestation (/Od _RTC_UninitUse("result") plus January's `mov eax,[ebp+8]`).
Non-blocking points for the integrator:
(1) scratch/w/profile/production.patch already contains Packet A's profile.c hunks. After Packet A lands it will no longer apply. At ruling time, re-diff Packet B as a body-only patch: final.c against the post-A profile.c. Otherwise apply production.patch instead of storage_only.patch, never both.
(2) Land Packet A as one commit containing both storage_only.patch (source/cseries/profile.c) and symbols.patch (config/symbols.json, 4 lines edited in place, CRLF). Then run a full ninja so the split and base regenerate before any stable_verdicts are taken (stale-base hazard). Expected result:
- profile stays 43 exact / 1 residual;
- the parked comparator's target and base shas are unchanged, so the park still validates;
- no other object changes (split regen changes only profile.obj);
- objdiff metrics are unchanged.
(3) When Packet B is approved, retire the config/parked.json entry for source/cseries/profile / _compare_profile_sections (about lines 224-242) in the same commit.
(4) Keep the compare_profile_sections prototype in profile.c's prototypes section, as the patch does. Moving it to profile.h is not required, and it would widen the declaration-count blast radius to all 25 includers.
@@ -0,0 +1,63 @@
# review source/effects/weather_particle_systems
approve=False
## checks
I re-ran everything myself in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_weather_particle_systems\. Notes are in REVIEW.md there.
(1) Patch: production.patch applies in an isolated core.autocrlf=true repo. The result is byte-identical to final_weather_particle_systems.c, which equals fin2.c.
(2) Gate: gate.py --source cand.c --all gives exact 20, residual 0, unwritten 0. Render is EXACT at 1680.
(3) Object audit: object_audit on cand.obj PASSES. Every January-owned section is ok and 30/30 symbols match. The production base object FAILS the same audit on 2 items: render, and one_over_char_max with storage 2 in January against 3 in ours.
(4) Surplus: the candidate's external sections are byte- and relocation-identical to the base object's. The only new external symbol is _one_over_char_max, which January owns.
- provider_link on the candidate fails only on _real_local_random (the effects.obj NODUP copy), in both orders. The base object fails the same way, so this predates the candidate. With --baseline there is no new surplus.
- surplus_identity: 13 of 14 IDENTICAL; _real_local_random is DIFFERENT, as it already is in production.
(5) PDB storage: pdb_storage shows one cachebeta public, _one_over_char_max at 0x25AAFC, and no other definer exists in source/. So January's storage for it is external.
(6) Data: report.json already has data at 949/949. Code goes from 4114/5779 to 5779/5779, a gain of 1665 meaningful bytes.
(7) /Od check: I dumped fn 0x57fb00 again and it matches the worker's od_render.txt exactly. I checked each shape claim against the dump myself:
- The only reference to [ebp-0x24] is `mov word ptr [ebp-0x24],cx` with cx=0, at 0x57fbb4.
- box_count=0 is stored before weather_polyhedra_find. The real at -0x218 is set to box_width after the find call.
- viewer_box uses the chained assignment and then +=. box_deltas and box_delta match, and the box store order is x0,y0,z0,x1,y1,z1.
- The particle loop is a while loop. The loop condition includes `&& !box_offset` and there is a break.
- position is built particle first. viewer_to_particle is computed with explicit subtractions, with no call to vector_from_points3d.
- The polyhedron plane test passes the element straight in as an argument, and the visible byte goes TRUE then FALSE.
- /Od transform 0x580c10 calls dot_product3d(&planes[i], box_position) as a real function, with the same argument order as the candidate.
- HCEX types box_offsets as union real_point3d[0x1A].
(8) Strip tests, each a whole-TU gate:
- Removing `short unused_count = 0;` leaves render residual [sha], 19/20. The dead local is LOAD-BEARING.
- Removing the (real_vector3d const *) cast still gives 20/20; the cast is byte-inert but its absence raises C4133.
- Removing `&& !box_offset` still gives 20/20, so it is inert.
- The production sprite-scale spelling `2.f*r*w` leaves render residual. The candidate's `w*(2.f*r)` keeps the grouping January uses (fld r; fadd st0,st0; fmul w, per the fresh-graphs ledger), so the parentheses are a real parse difference, not decoration.
(9) Warnings: CL /Zs /W3 gives the same warning set as production (15). fake_match_scan reports 0 leads, but it does not detect dead locals.
(10) Separable fix: dropping only `static` from one_over_char_max on production still gives 19/20 with render unchanged, and 30/30 symbols.
(11) House rules, reviewed across the whole diff: every change is value-equivalent to production. The patch removes a hand-expanded dot product. The only other checks are that no header or config file changes and that the prior ledgers and owner queues were read.
## issues
BLOCKING: the render match depends on `short unused_count = 0;`, a dead local with an invented name. The strip test fails without it: render goes residual.
- WORKER_BRIEF section 2 lists "filler declarations/dummy locals (they are the name-count ORACLE, never a landing)" as fake matching.
- This exact item is already waiting in the owner queue. docs/object_matching_logs/claude_lane_d_refresh_HANDOFF_20260922.md line 142 lists class "I placeholder-named dead local (_weather_particle_system_render) 1 / 1,680 exact-under-ruling". The manifest (claude_lane_d_refresh_manifest_20260922.json, around lines 1081-1084) describes the same /Od [ebp-0x24] short with the same placeholder name `unused_count`, and it is HELD.
- The only admission precedent is Lane C ruling R8 (2026-09-21). It admitted /Od dead arrays that the RTC descriptors NAME, and says "rule 8 is not weakened generally". It does not cover an unnamed dead scalar with a guessed name.
- I found no later owner ruling on class I in docs/ or in memory.
- Landing this now would pre-empt a pending owner decision.
The /Od evidence itself is strong and verified. The candidate's local inventory matches the /Od frame slot for slot, and the dead short really does exist in the first-party build. So this should go back to the owner as a ruling request, not be rejected as fake matching.
Recommendation for the integrator:
- Do NOT apply production.patch yet.
- Send this candidate to the owner as the updated class-I item. It supersedes Lane D's variant, which hand-expanded the particle plane distances. This one uses the real plane3d_distance_to_point and dot_product3d helpers, both attested as calls in /Od.
- If the owner admits /Od-attested dead scalars with invented names, apply scratch/w/weather_particle_systems/production.patch unchanged. I verified that it applies and reproduces 20/20, object audit PASS, and zero new surplus.
Minor notes:
- `unused_count` is an invented name, not an authentic one.
- The (real_vector3d const *) view cast is attested per site in /Od and is byte-inert. It only meets the view-cast rule once the caller (render) is exact, so it lands only together with the dead local.
- The one_over_char_max storage fix can land on its own now, without the rest of the patch. It is a one-line change on production: `static real const one_over_char_max` becomes `real const one_over_char_max`. The candidate file is scratch/w/review_weather_particle_systems/prod_storage_only.c. It keeps 19/20 with no row changes, brings symbols to 30/30, earns no code credit, and has a single cachebeta PUBLIC with no other definer.
- The object remains blocked regardless by the older _real_local_random surplus conflict with the effects.obj NODUP copy.
@@ -0,0 +1,46 @@
# review source/interface/interface
approve=True
## checks
All checks are independent re-runs. Evidence is in C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/review_interface/ (REVIEW.md, audit_*.txt, rlab*.c, wA.c/wB.c, rfsA*.c, sA_noproto.c/sB_noproto.c, odproj/).
1. Provenance: diffed production interface.c against cand_interface.c. The difference is exactly production.patch, and the function body is unchanged. `git apply --check` (non-mutating) passes for all three patches. Applying them to scratch copies reproduces cand_interface.c and cand_interface_variantA.h byte for byte, with CRLF kept.
2. Storage evidence:
- cachebeta_publics.txt: _interface_get_weapon_hud_index, _render_debug_profile and _interface_splitscreen_render are absent. All six _profile_* data symbols are present.
- pdb_storage.py reproduces all 9 disagreements.
- HCEX DIA2Dump: interface_get_weapon_hud_index has "static function: true" / `static long ...(float *)`, and interface_splitscreen_render is static. The control, interface_draw_screen, is not static, so the tool does tell them apart. render_debug_profile is absent from HCEX, so only cachebeta attests it.
3. Gate: `gate.py --source cand_interface.c --all --out review_interface/cand.obj` gives exact 18/18 (Variant B, original header). A Variant A build (header without the prototype, abs include) also gives 18/18. coff_compare: weapon_hud_index is strict EQUAL to split (448/20/0673f867...). The only .text section that differs between base and cand is that function. _sine/_cosine are identical, and .text emission order is unchanged.
4. object_audit on cand.obj: every January-owned section is ok. The only symbol DIFFs are storage 2/3 on the 3 functions. I also audited a scratch copy of the split with those 3 storage bytes flipped to 3 (simulating symbols_static.patch plus csplit regen): 34/34 symbols match and the result is OBJECT AUDIT: PASS. symbols.json lines 3535/3539/3540 are the right entries, the patch applies to a scratch copy, the JSON still parses, and no other split object references the three symbols.
5. Data: .data (100632 B, 18 relocs) is strict EQUAL in both cand and base, and cand storage now equals split. All 10 .rdata literals are EQUAL.
6. Surplus: surplus_identity shows sine/cosine IDENTICAL to actor_combat. provider_link on cand.obj passes for all 17 surplus symbols (SELECTED-PROVIDER LINK: PASS).
7. objdiff 3.3.1 (build/tools) mini-project: cand 5695 B and 17/18 functions; prod 5251 B and 16/18. The only function below 100% is stall_tick at 67.27%.
8. Warnings: cand at /W3 matches production. /W4 adds exactly one C4211 "redefined extern to static". Variant A has no C4211.
9. Strip tests:
- Removing `static` from the definition gives the production residual. The keyword is attested January storage, not decoration.
- The static prototype in the .c file does no work: removing it gives 18/18 in both Variant A and Variant B.
- The render_debug_profile / splitscreen_render `static` changes and the profile_* changes are bytes-inert storage corrections.
10. Blast radius:
- Variant A with a byte-preserving include swap: rasterizer_frame_statistics_draw goes from EXACT to residual [sha]; production is 10/10.
- Oracle only, not for landing: +1 extern-long dummy stays residual, +3 gives EXACT. That confirms a pure declaration-count tie.
- No header includes interface.h, so the 18 direct consumers are the full set.
11. Mechanism lab:
- rlab15 (copy of the worker's lab15) reproduces the effect: static gives an integer copy, extern gives x87.
- New shapes rlab1/rlab2 (local real set to 0.0, conditionally assigned from a call or *g(), then *out = local): static and extern compile IDENTICALLY with x87.
12. fake_match_scan: 0 leads on cand and on production.
13. Semantic credit: build/semantic_report.json already has interface:_render_debug_profile_stall_tick in hidden_exact, semantic_exact and accepted_ledger, with proof_sources semantic-coff. Running semantic_progress.apply_semantic_accepted_ledger on the current report.json credits it (+271 B, +1 function).
## issues
The verdict holds: FUNCTIONS_EXACT_OBJECT_BLOCKED. Every January function is strict EXACT. Admission is correctly held for the owner's header ruling:
- Variant A (prototype removed from interface.h) regresses rasterizer_frame_statistics_draw (4176 B) through a pure declaration-count tie. I verified this.
- Variant B (header kept) is an extern-then-static redeclaration: C89 6.1.2.2 calls it undefined behaviour, and MSVC reports C4211 at /W4. I verified this too.
- Moving a `static` prototype into the shared header would be no cleaner.
Four amendments are needed before the integrator acts:
(a) Drop proposed item (5), the config/semantic_matches.json entry for _render_debug_profile_stall_tick. The claim behind it is false. build/semantic_report.json already accepts that function through accepted_ledger (proof source semantic-coff), and project_x86.py and audit_object_admission.py both apply that ledger. The function is therefore already credited at 100% (+271 B, +1 function). Only raw objdiff shows 67.27%. The entry would be a harmless duplicate. Its reason text also pins $L label numbers (29019..., 29348, 29353); these shift in the candidate (29021..., 29350, 29355).
(b) The ledger's wording overstates the mechanism. "Storage class decides the copy template" and "VC7 uses x87 exactly when direct->indirect" are not general laws. My counterexamples rlab1/rlab2 compile static and extern identically, both with x87. The candidate is justified by the attested storage class plus measured strict exactness, not by that generalised claim. Record it as shape-dependent.
(c) If the owner picks Variant A, the header-removal check of the other 17 interface.h consumers must be redone at section-byte level. The worker compared per-function rows only, as their own ledger notes.
(d) When landing:
- apply symbols_static.patch in place (never re-serialize symbols.json);
- regenerate csplit and run a full ninja, because a split-only regen leaves a stale base;
- retire the parked.json entry source/interface/interface:_interface_get_weapon_hud_index in the same change, or the parked function's moved bytes fail PROGRESS.
production_changes if the owner rules: scratch/w/interface/production.patch, plus scratch/w/interface/production_variantA_header.patch only for Variant A, plus scratch/w/interface/symbols_static.patch, then the park retirement. Do not apply item (5). Review notes: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/review_interface/REVIEW.md
@@ -0,0 +1,49 @@
# review source/items/weapons
approve=False
## checks
All checks were independent. Notes are in scratch/w/review_weapons/REVIEW.md. Worktree e9e62b78 is clean and no tracked file was touched.
(1) Patch integrity: production.patch applied with `patch -p1 --binary` to a fresh mirror (review_weapons/applymirror) reproduces final/{weapons.c, weapons.h, object_types.c, action_charge.c, symbols.json} byte-for-byte.
(2) Consumer blast with my own harness (blast2.py + objcmp2.py). I grepped the includers: weapons.h has exactly 19 direct includers and no header includes it. The control reproduces all 19 build/base objects with 0 diffs (sections, bytes, relocations, full symbol table). With the final header and sources, the other 17 consumers and object_types are identical to the control once internal label numbering is normalised. Units stays 189/189, game_engine 180/180, objects 121/121, object_types 31/31. The research12 units regression does not reproduce.
(3) Own csplit with final/symbols.json: only weapons.obj differs from build/split.
- gate-equivalent against the re-split: 79/79 strict.
- object_audit (SPLIT_ROOT copy, checked against the original tool): PASS, 98 symbols, 0 differ.
- surplus_identity: 17 COMDATs, all identical, same set as production.
- objdiff 3.3.1 in a scratch project: .data and .rdata 100%, _weapon_place 100%. The objdiff under-credit on 3 other functions is pre-existing.
(4) cachebeta publics: all 11 functions and the data symbols are absent (file-static). A build/split census shows none of them is referenced outside weapons.obj; _weapon_place is referenced only by object_types.
(5) My own MSF7/DBI/TPI parser (pdbcheck.py) on HCEX.pdb (sha f55cfe95, not the same PDB research12 used):
- weapon_place GPROC32 is LF_PROCEDURE returning 0x12 (long), with args (long, scenario_weapon_datum *). Parser control: weapon_new and weapon_update return 0x20.
- finish_reload is an LPROC32 returning void (long, short).
- blurred_permutation_names is LDATA32 char*[2].
- All 9 other *_place callbacks return void.
- HCEX has no profile statics, and the symbol atlas has no name for this one.
(6) /W3 warnings are identical before and after: weapons 20, object_types 12, action_charge 12.
(7) STRIP TEST of the object_types cast: the plain `weapon_place` gives a byte- and symbol-identical object_types.obj and only adds C4028. The cast is inert.
(8) fake_match_scan: 0 leads.
(9) p4 (action_charge):
- Gate is 21/22 before and after. The residual is _action_charge_perform. The real_random caller, action_charge_setup, is exact.
- objcmp2 finds 0 diffs. The only change is _real_random's COMDAT selection, NODUP to ANY, which now matches every other header-inline helper in the object.
- object_audit is unchanged and provider_link passes.
- Pair links: weapons + base action_charge gives LNK2005 in both orders; weapons + p4 action_charge gives none in either order.
(10) Fallback (p1+p3+p4 without the header change): the object is identical to the primary.
(11) Subsets I measured:
- 'Void subset' (everything except the long return): 78/79 against the re-split.
- 'Static-only subset' (11 static flags plus static finish_reload): the csplit changes only weapons.obj, the gate stays 78/79 against both the current split and the re-split, and object_audit shows 97 symbols with 0 differ; its only failure is _weapon_place.
## issues
No technical defect found: the worker's measured claims reproduced independently. approve=false is a POLICY HOLD. The patch lands two items the brief and earlier owner rulings reserve for the owner.
(A) _weapon_place long return bound to the void datum_place slot with a `BUG:` comment is a reproduction of an authentic January interface bug. WORKER_BRIEF s2 says such a change "needs target+layout proof, a BUG comment and owner approval - report, don't land". This exact item is class F ("return ABI (_weapon_place)", 208 B) of the Lane D owner-ruling queue (docs/object_matching_logs/claude_lane_d_refresh_HANDOFF_20260922.md), and no ruling has been recorded since. Research12's House21 review was another lane's reviewer, not the owner.
New evidence the owner should see:
- The units regression is gone at e9e62b78 (the 19-consumer blast is clean).
- The raw long type appears in two different HCEA/HCEX PDBs.
- It is the only long return among the 10 placement callbacks.
- The strip test shows the cast is byte-inert.
(B) The p3 symbols.json split of _data_00307140 adds the static name `_weapon_update_section`. That name is descriptive, not authenticated: HCEX has no profile statics and the atlas has no name for it. The owner ruling of 2026-09-15 held symbols.json static splits with invented names and said not to re-ask without authenticated names. HCEX now authenticates blurred_permutation_names and shows the weapons_globals struct is invented, which justifies a re-ask, but it is still an owner question.
(C) The proposed FALLBACK (p1+p3+p4) is not house-rule clean and should not be offered. It leaves object_types.c's consumer-local `void weapon_place(long, struct scenario_object_datum *)` prototype contradicting a `long` definition, with no BUG disclosure (rule 9 and a lying cross-TU declaration). It also leaves a public function with no owner-header prototype. If the owner rejects p2, the long return should not land at all.
(D) Integration hazard: the p1 data respell and the p3 data lines must land atomically with the csplit regeneration. Production source against the new split scores 77/79, and the new source against the old split scores 78/79, losing _weapon_update on relocation identity.
Admissible now without a ruling (zero code credit; correctness only, measured):
- the 11 `"static": true` lines of p3;
- `static` on the weapon_magazine_finish_reload prototype and definition;
- p4 (remove action_charge's NODUP real_random hand copy).
After those, weapons is 78/79 with object_audit 97 symbols / 0 differ (only _weapon_place fails), and the _real_random LNK2005 is fixed.
Follow-up: objdiff under-credits the strict-exact _weapon_export_function_values and _weapon_update, and neither has a semantic entry. Both are pre-existing.
@@ -0,0 +1,69 @@
# review source/math/geometry + source/game/players (Layer 2 ABRPL header-packet function gains)
approve=False
## checks
All work is in "C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_players (header-packet function gains)\" (LEDGER.md R0-R16, REVIEW.md). No tracked file was touched; git status is empty.
(1) Build method. I made full COPIES of source/ (no junctions) as trees/base, trees/ABRPL and trees/full. trees/full = ABRPL plus every Layer 1 and Layer 2 .c diff. The patches were applied with patch -p1 and CRLF was restored. The patched files equal the worker's lab headers and cand files. I wrote my own tools: rvgate.py (build.ninja flags with /Isource remapped into the tree), seccmp.py (section_infos_equal plus COMDAT selection and storage), and rvsweep.py (full board). Control: trees/base gives 447/447 units SAME as build/base.
(2) gate.py on the candidate (--out cand.obj, current header): 67/70, the same rows as production. Candidate under ABRPL: 68/70, with _player_set_action_result EXACT (240/5/ab7ce57f, equal to January). Production players.c under ABRPL stays residual.
(3) geometry production under ABRPL: 27/30, with _convex_hull2d_perimeter EXACT (96/0/71292feb). The only other delta is a NEW _distance_squared2d, identical to January's action_vehicle copy.
(4) object_audit. players stays incomplete; the 3 failures are pre-existing. geometry has 90 January symbols and 0 differ. Data is 100% for both, and no data section changed.
(5) Surplus identity (surplus_obj.py):
- players under ABRPL: 0 of 13 non-identical. Production has 1 non-identical, _distance3d.
- Across all 28 changed units, every _distance3d (17 units) and _project_point2d (4 units) flips from DIFFERENT to IDENTICAL.
- No new non-identical surplus anywhere.
(6) Provider link (provider_link with my full-tree providers substituted):
- players: PASS.
- geometry: FAIL on _cross_product2d and _plane2d_from_points. Both are pre-existing; production also fails _distance2d, which Layer 2 cures.
(7) Full-board sweep of the whole packet: 419 SAME, 28 DIFF, 0 LOSS, 0 compile failures.
- GAIN: exactly the two functions.
- DRIFT, residuals only: actor_perception_refresh_danger_zone (alndiff 234->238), and the parked ai_test_line_of_sight (1040/50, sha 61a3ce7f -> c524fe19, alndiff 264->270).
- The canaries (bitmap_copy, frame_statistics_draw, collision_move_point) are SAME.
(8) Attribution (header variants A = distance3d body only, B = distance2d body only, AB = both):
- The players gain needs A plus the no-locals caller; B alone leaves it residual.
- The geometry gain needs B; A alone leaves it residual.
- Both gains hold under A/B, AB and ABRPL (name-count shifts of -1, -2 and 0), so they are not name-count ties.
- AB alone loses _bitmap_copy and _rasterizer_frame_statistics_draw. A alone and B alone each lose _rasterizer_frame_statistics_draw.
(9) /Od re-dumped independently:
- 0x443e70 (distance2d) and 0x42dc50 (distance3d) are frameless: square_root(distance_squared*(a,b)).
- 0x5d0d50 (player_set_action_result): the distance results are expression temps with a movss spill across the second call, i.e. one expression with arguments (unit, current) and (unit, new).
(10) Strip / fidelity test. I also adopted the rest of the /Od shape (no '= FALSE', and a last arm 'set_action = action_result > player->action_result'). It gives 256 != 240 under both headers, so the diff correctly takes only the January-attested part. The new spelling has no decoration.
(11) CL /Zs /W3 warnings:
- players: identical (18).
- main.c: loses its 2 C4013s (lock/unlock).
- network_client_manager: keeps its get_random_seed C4013.
(12) Ledger history. The geometry park reopen criterion ('genuine j-first helper') is met. The players reopen criterion is explicitly 'owner decision on the real_math.h distance3d/magnitude inline spelling'.
## issues
No technical defect. Verdict: POLICY HOLD (exact-under-ruling), consistent with the review_weapons and review_weather_particle_systems precedent.
Blocking:
- The two gains need the real_math.h distance2d/distance3d body change. Those hunks cannot land alone: header AB regresses bitmap_drawing::_bitmap_copy and rasterizer_frame_statistics::_rasterizer_frame_statistics_draw by a pure name-count effect, since neither TU uses distance2d/3d.
- Landing therefore needs a declaration-count compensation chosen by the oracle count. ABRPL moves lock/unlock_global_random_seed from random_math.h into real_math.h's RANDOM_MATH.C section; the ABR alternative moves get_random_seed instead.
- These are genuine prototypes, not fillers, but which ones January had is underdetermined. ABRPL cures main.c's C4013s and leaves network_client_manager's get_random_seed C4013; ABR does the reverse. C4013 on void(void) or int-returning calls does not change codegen.
- The worker routes this to the owner (P13/P14), and the players ledger's reopen criterion is explicitly an owner decision. A review approval would pre-empt that ruling.
Once the owner admits the Layer 2 header packet (ABRPL as written, or ABR if project_point2d is dropped), approve with these conditions:
- (a) Apply real_math.h, random_math.h and ALL Layer 1 and Layer 2 .c diffs as one change. Never apply the distance hunks alone.
- (b) Credit geometry::_convex_hull2d_perimeter (96/0/71292feb) and players::_player_set_action_result (240/5/ab7ce57f). Retire the _convex_hull2d_perimeter park.
- (c) Re-measure the _ai_test_line_of_sight park base: 1040/50/c524fe19529bfd4a72e95be52a875925bcc929d347b37471eb16b75bd393da78, plus a fresh objdiff %. A parked function whose bytes move fails PROGRESS.
- (d) Record the non-exact fuzzy drift of _actor_perception_refresh_danger_zone (alndiff 234->238 lines) and of ai_test_line_of_sight (264->270).
- (e) Run a full ninja and diff every function row.
Non-blocking:
- scratch/w/provider/patch/layer2_ABRPL/players.c.diff is house-rule clean. It removes the unattested current_distance/new_distance locals in favour of the /Od-attested single expression, with no decoration.
- It is neutral under the current header (67/70, only the residual's bytes move), so it may be staged standalone at zero credit.
- geometry and players both remain incomplete objects. geometry has a pre-existing provider-link failure on _cross_product2d (BLOCKED) and on the _plane2d_from_points decals hand copy. players has 2 residuals and the _player_examine_nearby_unit storage mismatch.
Files: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_players (header-packet function gains)\REVIEW.md, LEDGER.md, sw_full.tsv, sw_base.tsv, surplus_full_vs_prod.txt, objs\.
@@ -0,0 +1,51 @@
# review source/objects/objects
approve=True
## checks
All checks were independent: my own compiles, a COFF parser and a Link.Exe harness written for this review. Evidence is in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_objects\ (REVIEW.md, LEDGER.md).
(1) Patch integrity: all four Layer 1 diffs pass `git apply --check` on the clean tree at e9e62b78. Each patched file equals cand1.c or cand2.c, ignoring CRLF.
(2) objects (objects.c unchanged):
- gate --all gives 121/121 EXACT. cand.obj equals build/base in every section except .debug$S.
- object_audit PASS: 220 January-owned sections ok, including .data (1530 B) and .bss (13 B); 225 symbols, 0 differ.
- pdb_storage: 0 disagreements.
- surplus_identity: 26 code COMDATs, 0 not identical. My own check over all 71 candidate-only external definitions (code, literals, 3 D3D tables) found all identical to January's selected copies.
- provider_link on production fails exactly 4 symbols (_point_in_sphere, _real_random, _signed_angular_difference, _valid_real_vector3d_axes2). _valid_real_vector3d already PASSes.
(3) Providers, candidate vs production compile:
- Gate row text is identical: action_charge 21+1, player_control 49/49, bored_camera 9/9, path_obstacles 15+1 (residuals pre-existing).
- objcmp2.py (includes COMDAT selection): only NODUP->ANY on the 5 helpers, plus bored_camera's new ANY _valid_real_normal3d, _magnitude_squared3d and __real@3f800000. All three are identical to January's selected copies and pass provider_link.
- object_audit lines for January-owned sections are unchanged in all four providers.
(4) Board census: every other build/base definer of these helpers is ANY and identical to January's selected copy. The only NODUP definers are the 5 hand copies being removed.
(5) Link.Exe:
- objects x each candidate provider: 0 LNK2005/LNK1169 in both orders.
- Positive controls (objects x each production provider) report the expected LNK2005.
- objects + all 4 candidates combined: 0 in both orders.
(6) Genuine uses confirmed from January relocations: action_charge_setup @16b/@171 (real_random inlined), _interpolate_scalar @51f, _valid_real_vector3d_axes2 @1e8, _point_in_sphere @114. signed_angular_difference is used 3 times.
(7) Cross-lane composition: the action_charge diff applied to the action_charge lane's candidate.c (perform EXACT) gives 22/22, with only a selection change. The weapons lane's p4 is the identical edit.
(8) fake_match_scan: 0 leads on objects.c, the candidates and the production files.
(9) House rules: the diff only removes renames, #undefs, two consumer-local prototypes and the hand copies. Nothing new is introduced, so no strip test applies.
(10) Scorer/admission: I found the frozen 3.3.1 data gap and simulated tools.audit_object_admission with and without a semantic data entry (see issues).
## issues
REQUIRED AMENDMENT (the production-change list is incomplete as written):
- Under the frozen objdiff 3.3.1 build/report.json, objects' .data section ?section@?1??objects_update@@9@9 (1530 B, one relocation to a ??_C@...$AA@ literal) scores 99.74%, so strict matched_data is 3785/5315.
- The section is identical under coff_compare section_info_resolved (the known '$'-literal defect), but config/semantic_data_matches.json has no entry for objects.
- Simulation: setting objects.c to Matching without an entry leaves objects revoked ('1530 unmatched data bytes') and marked completion-label-contradicted.
- Fix: append the single-section entry in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_objects\semantic_data_entry_objects.json (symbol ?section@?1??objects_update@@9@9, size 1530, relocation_count 1, normalized_sha256 e94bb79478c95dce11c4a1f720a07ccbaa3a8d18b84acbe5605472d6f8635b74). With it, the simulation shows 0 revoked and 0 contradicted.
- It must land in the same change as the Matching status, because apply_semantic_data_matches rejects an entry for a unit that is not marked complete.
Full apply order:
- git apply the four Layer 1 diffs.
- Rebuild with ninja.
- provider_link source/objects/objects should PASS.
- Remove the objects entry from object_admission_rejections.json. Its _valid_real_vector3d symbol was already stale.
- Set objects.c to Matching together with the data entry.
- Credit no helper bytes.
Integration notes:
- The action_charge edit is the same as the weapons lane's p4. Apply it once.
- It composes with the action_charge lane's patch: the hunks don't overlap, and the result was measured at 22/22.
Non-blocking, pre-existing:
- objects.c has 15 single-line parameter lists (static prototypes on lines 114-140 and void object_pvs_clear(void) at line 517), against house rules 1 and 2. 47 of 361 Matching objects have the same deviation. The fix is whitespace-only.
- path_obstacles still emits 3 invented-name COMDATs (*_inline). player_control keeps the limit2d rename.
- The bored_camera candidate ends with an extra trailing blank line.
@@ -0,0 +1,68 @@
# review source/physics/collision_debug
approve=False
## checks
All checks were run independently at worktree HEAD e9e62b78 (clean tree). Notes are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_collision_debug\REVIEW.md.
1. gate.py --source (production copy) --all: EXACT 4192 _collision_debug_render, 1/1. The candidate object is identical to build/base (objeq, 36 sections).
2. object_audit: all 17 January sections ok. 29 symbols, 1 differs: _collision_debug_cube_vertices, storage 2 in the split vs 3 in ours. This matches the worker.
3. Storage evidence:
- pdb_storage: that same single disagreement, PDB-public False.
- My own grep of cachebeta_publics: every other collision_debug global in this object is a public, and there is no public at 0x0028B2A8.
- A scan of all 833 build/split and 621 build/base objects finds no reference to _collision_debug_cube_vertices outside collision_debug.obj.
- contribs.json module 140 has exactly 17 contributions (1 code, the 96-byte cube array at selection 0, 13 literals, .data 16, .bss 28962). There is no hidden code.
4. Split regenerated with only line 19561 changed (byte-level edit in place, 1-line diff), using build/tools/csplit.exe into a scratch folder:
- Of 833 objects, only collision_debug.obj differs, and its only difference is the storage class of cube_vertices.
- Audit against the new split: PASS, 29/29.
- Gate against the new split: EXACT.
- One-unit objdiff 3.3.1 report: code 4182/4182, data 29278/29278 (the same as the old split). build/report.json is also 100/100, so no semantic_data entry is needed.
5. Surplus:
- surplus_identity: 4 of 4 identical.
- My own surplus_all.py compares every January definer (not only provs[0]) on raw bytes and flags: 18 of 18 identical.
- provider_link: 18/18 PASS in both orders.
- nodup_census: no NODUP definers.
- Census of every build/base copy of the 7 helpers: all have the same hash as January's single selected copy.
- .drectve contains only the standard LIBC/OLDNAMES directives.
6. No parks, rejections or semantic entries exist for this unit. fake_match_scan: 0 leads.
7. Strip tests (whole object compared with objeq): with the add_vectors3d point casts removed, the cube-vertex cast removed, both removed, or 6.2831854820251465 replaced by _pi*2.f, the object is IDENTICAL every time. None of these constructs is buying the match. The add_vectors3d(point, vector, point) call is attested in the /Od build at 0x7b0c5c (target 0x44e8e0 has the add_vectors3d body).
8. Full /Od call census of the render function (od_linear.py 0x7af800..0x7b1ee4, 43 callees) mapped against the production calls. This is where the finding below came from.
9. Fix variant mag3d.c:
- EXACT, and audit PASS against the regenerated split. objdiff 100/100, fake scan 0.
- The 3 extra COMDATs (_magnitude3d, _magnitude_squared3d, _square_root) are identical to January's selected copies (action_charge/action_alert) and have no NODUP definers.
- provider_link: 21/21 PASS.
10. Both patches I wrote apply with patch --binary, and their results are byte-equal to the tested files.
## issues
BLOCKING: the source hand-expands a header helper, and the object would be marked Matching with it.
The /Od first-party build calls magnitude3d in the features block: 0x7b1dc6 calls 0x432950, which calls magnitude_squared3d 0x42df70 and then square_root 0x42e320 / 0x42e2f0. That is real_math.h:1140 magnitude3d applied to &collision_debug_vector. Production instead writes:
`radius = (real)sqrt(collision_debug_vector.i * collision_debug_vector.i + collision_debug_vector.j * collision_debug_vector.j + collision_debug_vector.k * collision_debug_vector.k) * 0.5f + collision_debug_height * 0.5f + collision_debug_width;`
This breaks brief rule 6 ("never hand-expand ... any header helper") and campaign rule 13. It also goes against the owner's 20260924 reconciliation standard: "no ... hand expansion ... admitted". It is left over from the era when helper COMDATs were forbidden. The owner's commit 5671843f converted point_from_line3d, scale_vector3d, add_vectors3d and set_real_vector3d to helpers but missed this one. It is the only helper in the /Od census that is hand-expanded. The fabs/acos/fmod wrappers in the /Od build are artifacts of the later C++ build's <cmath> overloads, not helpers in the source. So the worker's claim "No source change is needed" is wrong.
The fix is measured and admissible:
- The genuine spelling `radius = magnitude3d(&collision_debug_vector) * 0.5f + collision_debug_height * 0.5f + collision_debug_width;` is EXACT.
- It adds only three SELECT_ANY COMDATs, all identical to January's selected copies. 17/36/21 Matching units already emit them.
- provider_link: 21/21 PASS. Audit PASS. objdiff 100/100.
Everything else in the proposal checks out independently. The static symbols.json row is correct, the split regeneration changes only this object, surplus and link pass, and the data is complete.
Minor: the worker's patches/symbols.json.patch also carries rows for units that are BLOCKED (breakable_surfaces, rasterizer_xbox, hardware_geometry). Do not apply it as a whole for this unit.
Non-blocking: the three raw double literals 6.2831854820251465 in the fmod calls are a folded-constant readout that no person would type. `_pi*2.f` (the spelling real_math.h itself uses) gives an identical object; see scratch/w/review_collision_debug/mag3d_twopi.c. That cleanup is optional.
CORRECTED PRODUCTION CHANGES for the integrator, applied mechanically:
(1) config/symbols.json line 19561, edited in place with no re-serialization: `{ "file_offset": 2667176, "flags": 0, "name": "_collision_debug_cube_vertices" },` becomes `{ "file_offset": 2667176, "flags": 0, "name": "_collision_debug_cube_vertices", "static": true },`
- Patch: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_collision_debug\symbols.json.collision_debug.patch
(2) source/physics/collision_debug.c: replace the 4-line radius sqrt expression, around line 553, with `radius = magnitude3d(&collision_debug_vector) * 0.5f`. The next two continuation lines stay unchanged. Keep CRLF line endings.
- Patch: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_collision_debug\collision_debug.c.patch
- Post-image: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_collision_debug\mag3d.c
- Optional: mag3d_twopi.c instead.
(3) Regenerate the split, then run the full ninja build and the stable sweep. Expect 0 regressions, and collision_debug should stay EXACT 1/1.
(4) Only then change config/config.json line 515 for source/physics/collision_debug.c from "status": "NonMatching" to "Matching".
The admission record should list:
- 7 surplus code COMDATs: _add_vectors3d, _point_from_line3d, _scale_vector3d, _set_real_vector3d, _magnitude3d, _magnitude_squared3d, _square_root.
- 3 SDK tables: _D3DPRIMITIVETOVERTEXCOUNT 88, _D3DSIMPLERENDERSTATEENCODE 328, _D3DTEXTUREDIRECTENCODE 16.
- 11 literals.
- That all of these are identical to January's selected copies and pass the selected-provider link in both orders.
@@ -0,0 +1,80 @@
# review source/physics/collisions
approve=False
## checks
All checks were run independently. Notes and objects are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_collisions\ (see REVIEW.md).
1. Patch application. I built a mirror tree, scratch/w/review_collisions/tree, from `git archive HEAD` (e9e62b78) and applied production.patch with `patch -p1 --binary`. It applies cleanly and keeps CRLF. The three patched files are byte-identical to scratch/w/collisions/final/*, and `diff -rq` against the tracked source shows only those three files.
2. Scope of the change. After the patch, only collisions.c and matrix_math.c include real_math_planes.h, and no .c file includes another .c file. So only those two TUs can change.
- collisions: the only delta is move_point plus a new `_valid_real_plane3d`. STRICT gained=[_collision_move_point].
- matrix_math: identical to build/base (all 64 sections, same symbol set).
- render_cameras: identical.
3. Strict rows (rows.py, the gate.py logic run on a prebuilt object):
- collisions: 20/20 EXACT. move_point: 4752 bytes, 226 relocations, sha 8b2f2900…117e2c, which equals January's.
- matrix_math: 35/35.
- `gate.py --source final/collisions.c` with the tracked headers: 19/20, so the header half is load-bearing.
4. Object audit and surplus:
- object_audit on cand.obj: PASS, 44/44 symbols. Compared with the base audit, the only changes are move_point going from DIFF to ok and the new surplus `_valid_real_plane3d`.
- Surplus identity on cand.obj: 21 COMDATs, all identical. The new `_valid_real_plane3d` equals January's matrix_math copy.
5. Provider link:
- `provider_link.py cand.obj` against the current base providers: FAIL(3).
- `_plane3d_negate` and `_point_in_sphere`: these two NODUP failures were already on build/base.
- `_valid_real_plane3d`: a new failure while matrix_math.c still has its NODUP definition.
- Pair link of cand.obj with the patched matrix_math.obj: PASS in both orders.
6. Data and scoring:
- report.json data for the unit is at 100% (590/590).
- The worker's proposed semantic_matches.json entries check out: sizes, relocation counts and normalized shas match January for move_point (4752/226/8b2f…) and object_get_features_in_sphere (544/13/d529…).
- object_get_features_in_sphere is strict-exact but already scores only 93.81% in report.json today.
7. Other scans. fake_match_scan finds only the matrix_math.c:940 `__asm` lead, which was already there. parked.json and the admission rejections have nothing that affects this packet.
8. Evidence sources:
- /Od build (od_move_point.txt): `position = *old_position` is set at function entry (0x7A52DC). The same slot, [ebp-0x18], is reassigned from `collision->point` at 0x7A5710. So the /Od build declares `position` at function scope.
- HCEX.pdb lists `position` among the function-level locals. HCEX records no block scopes, so this is weak evidence.
- Lane B's REPORT (try_13, try_22 and the m8probe): the 0x17b/0x17f preheader order is a roughly 50% name-count coin flip.
9. Strip tests (my own compiles):
- Packet headers with the production function-scope `position`: NOT exact. The two REAL regions are the 0x17b/0x17f preheader transposition.
- Packet headers with the /Od initialiser chain: the same two REAL regions.
- Packet headers with the loop site hand-expanded again: EXACT, so the loop-site call is byte-inert.
- Broad real_math.h inline (worker's hdr/) with the /Od-attested function-scope `position`: move_point EXACT. Block scope there is also EXACT.
- The same broad route loses `_bitmap_copy` (2,784 bytes) and `_rasterizer_frame_statistics_draw` (4,176 bytes). This reproduces the worker's negative result.
## issues
REJECT. Every byte claim reproduces, but the source is not admissible under the house rules and owner rulings.
1. Scope steering against first-party evidence (the main defect). The patch moves `real_point3d position;` into the success-arm block of collisions.c.
- The /Od build shows `position` as a function-scope local. It is initialised from `*old_position` at 0x7A52DC and reassigned from `collision->point` at 0x7A5710, in the same [ebp-0x18] slot.
- Lane B landed function scope on that evidence (claude_lane_b_attempt_logs_20260920/collisions_REPORT.md, try_13/try_22).
- Under the packet's headers, the /Od-attested form fails, with or without the initialiser chain. It fails only on the 0x17b/0x17f preheader order, which Lane B proved is a roughly 50% declared-name-count coin flip.
- So the block scope makes no difference to behaviour and contradicts the /Od evidence. It exists only to land a count-keyed coin flip that the focused-header change moved. That fails the STRIP TEST: the attested plain spelling no longer matches.
- Under the January-like broad real_math.h inline, the /Od-attested function scope is EXACT. This confirms the scope revert is compensating for the header choice, not recovering January source.
2. The focused-header placement is the owner-refused workaround.
- Owner ruling 2026-09-21 (claude_lane_b_HANDOFF_20260920.md §3 item 4; claude_lanes_AB_HANDOFF_TO_LANE_C_20260921.md rule 7 and lines 507-509; claude_lane_b_rejected_hypotheses_20260920.md lines 38-43): declarations belong in the genuine owner header, real_math.h, where the prototype already is at :672. That requires a full sweep and zero regressions, "never a one-consumer header". Choosing a header position because it spares the victims is coincidence-hunting.
- The patch's own comment gives declaration-count sensitivity as the reason for the placement.
- real_math_planes.h's charter is "real_math.c plane-intersection routines that real_math.h does not declare". valid_real_plane3d is neither: it is declared in real_math.h and defined in matrix_math.c.
- matrix_math.c would include the header only to get back its own deleted body.
- The ruling also requires a "genuine shared-header __inline" before a new COMDAT is admitted. This is not one.
3. The genuine-owner route is held. The broad real_math.h inline, plus the matrix_math definition removal and the loop-site call, keeps function scope and makes move_point exact. But it loses `_bitmap_copy` (2,784 bytes) and `_rasterizer_frame_statistics_draw` (4,176 bytes).
- `_bitmap_copy` can be restored by removing the duplicate `bitmap_delete` prototype.
- `_rasterizer_frame_statistics_draw` could not be restored in the worker's name-count sweep from -6 to +30 units.
- The zero-regression rule therefore blocks it.
4. Other items:
- The loop-site change to `valid_real_plane3d(&collision->plane)` is fine. It is /Od-attested (one call at 0x7A5833), byte-inert and follows the house rule.
- The mechanism finding is sound and worth keeping. VC7 treats `&local` passed to an opaque external prototype as an escape, while a visible `__inline` body does not capture.
- The object remains blocked regardless by the NODUP surplus link failures `_plane3d_negate` (decals.c) and `_point_in_sphere` (path_obstacles.c), which were already on build/base.
- The object_get_features_in_sphere semantic entry has valid evidence (544/13/d5292109…8d8325; strict-exact, but report.json shows 93.81%). It is independent of this packet and could go through as its own item.
Reopen criteria:
- (a) Find a genuine (not filler) declaration fix in rasterizer_frame_statistics' include set that restores `_rasterizer_frame_statistics_draw` under the broad real_math.h inline. Then land the broad route together with the bitmap_delete duplicate-prototype cleanup, keeping function-scope `position`.
- (b) Or get an explicit owner ruling that either accepts the frame_statistics_draw loss (recorded and subtracted), or admits both the focused placement and the scope that contradicts the /Od build. The request must state every side effect: a new surplus `_valid_real_plane3d` COMDAT in collisions.obj, and matrix_math's COMDAT selection changing from NODUP to ANY.
No production changes approved.
@@ -0,0 +1,33 @@
# review source/rasterizer/rasterizer_geometry
approve=False
## checks
Everything below was re-run independently. Evidence is in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_rasterizer_geometry\ (REVIEW.md, LEDGER.md, gate_cand.txt, object_audit*.txt, provider_link.txt, w3_*.txt, probes/).
- Source diff: the worker's cand.c, final.c and my copy are identical. Against production, the only change is the body of uncompress_int32_to_real_vector3d. Both files are CRLF.
- Gate: `gate.py --source cand.c --all` gives 19/19 EXACT, 0 residual, 0 unwritten. The production copy leaves _uncompress_int32_to_real_vector3d residual [sha]. alndiff on production shows 49/49 instructions, and the only difference is January's fadd placed before `shl eax,0x16`. alndiff on the candidate shows only relocation-name rows.
- object_audit.py on cand.obj: PASS. All 44 January symbols match and every January-owned section is ok. The base audit fails only on this one function. The candidate-only surplus list is identical to the production base.
- Surplus and linkage: surplus_identity finds _fast_ftol IDENTICAL to the copy in actor_combat.obj. provider_link on cand.obj passes every surplus symbol, and the selected-provider link passes. pdb_storage finds 0 disagreements.
- Data and scoring: build/report.json shows data 864/864 (100%) and code 3919/4054, with the missing 135 bytes in this function. No rejection or semantic entries exist for the unit.
- Lint: fake_match_scan finds 0 leads. CL /Zs /W3 gives the same warning set for production and candidate.
- Patch: production.patch passes `git apply --check`, and `patch --binary -p1` reproduces cand.c byte for byte.
- /Od readout: re-derived with odbuild (fn 0x82ea80, called from 0x82d080). It is identical to the worker's od_uncompress.txt. The frame is 0x18 (RTC guard + v, size 12 + guard + GS cookie) and has no scalar user local.
- STRIP TEST:
- s1, the exact /Od spelling (masks, divisions, no temp): residual.
- s2, the same spelling plus the staged `real value` temp (the worker's t7): EXACT.
- Production, with no temp: residual.
- House rules on the diff: real type, parameter layout and return are unchanged. No header, prototype or ABI change. The name is descriptive and there is no fake-scan smell.
## issues
The byte and object-audit claims all reproduce. The candidate still fails the source-credibility gate as proposed, so this needs an owner ruling, not a landing.
1) The strip test fires. The added `real value;` staging temp changes no semantics, and it alone buys the match. The plain production spelling and the exact first-party /Od spelling are both residual. Every staged form is EXACT, so the lever is robust, but that is byte-derived evidence, not evidence about January's source.
2) First-party evidence contradicts the temp. The later /Od+/RTC build of the function (0x82ea80) has frame 0x18 = guards + v + cookie, with no scalar user local. /Od homes every named local, so that source had no staging temp. Its one store per component also rules out the in-place staging variant. Nothing shows the function was revised between January and that build: its masks and divisions fold to January's bytes.
3) The worker's "file style" support does not hold. compress_real_to_int16's `z = (real)floor(...)` reassigns a parameter, which January's bytes attest (fstp [ebp+8]); it is not a new temp. The siblings uncompress_int8/16_to_real are direct expressions with no staging. The `value` temp leaves no trace in January's bytes.
4) The 2026-09-20 owner ruling says to escalate, not rule unilaterally, when the strip test fires. It also requires an inferred construct to be disclosed as inferred, with a comment in the .c that reproduces the measurement. production.patch adds a bare temp with no such comment.
The one precedent for a scheduling temp is the units FP-intermediate reused temp. It predates the /Od-readout rule and did not face contrary /Od evidence.
If the owner admits it:
- Apply production.patch, or t7.txt, whose arithmetic is the /Od one and is also EXACT.
- Add a comment above the staged statements covering three points: the staging is inferred from January's scheduling bytes; the later /Od revision has no scalar local; plain and /Od spellings are residual while staged ones are EXACT.
- After the full ninja, retire the parked.json entry for _uncompress_int32_to_real_vector3d (instruction-scheduling) with `python -B -m tools.campaign.unpark --write`.
- Optionally flip config.json index 96 to Matching.
Blast radius: none found. The function is extern, all 19 functions in the TU stay EXACT, and nothing shared changes.
Other routes to approval: a temp-free spelling that gates EXACT, or first-party evidence of a January-era staged conversion.
@@ -0,0 +1,53 @@
# review source/structures/structures
approve=False
## checks
All checks were run independently. Notes are in C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/review_structures/REVIEW.md.
(1) Patch identity. production.patch was applied to a copy of production structures.c (HEAD e9e62b78, sha 72611997, tracked file untouched). The result is byte-identical to scratch/w/structures/final.c (sha256 2fb89c42). `git apply --check` is clean at the repo root, and the patch touches only the sphere_intersects_cluster_portal body.
(2) Gate. Baseline (production copy): exact 16, residual 1, `_sphere_intersects_cluster_portal [size 416!=432, sha]`. Candidate final.c into review_structures/cand.obj: exact 17, residual 0, unwritten 0. Reproduced.
(3) relocdiff on cand.obj: 11/11 relocations, 0 differing rows, size 432/432, sha equal.
(4) object_audit cand.obj: PASS, 26 January symbols, 0 differ. The surplus list is identical to both the gated production copy and build/base structures.obj.
(5) surplus_identity run against cand.obj (local copy surplus_identity_cand.py): 12 code COMDATs, 0 not identical.
(6) provider_link on cand.obj: FAIL(2), on `_project_point3d` (path_obstacles NODUP) and `_projection_sign_from_vector3d` (decals NODUP). build/base output is identical, and `--baseline` shows no new surplus. This failure was already there and the patch does not introduce it.
(7) pdb_storage: 0 disagreements. build/report.json data is 2292/2292 (100%). Code is 3470/3888; the 418-byte gap is this function.
(8) No parks or rejections exist for the unit. I read the prior sphere ledgers (first_shot, w2, n1, fog_inline_closeout). The point_from_line3d COMDAT is already admitted in this TU.
(9) Reviewer strip test on the load-bearing construct:
- S1 `real t = -plane_distance;`: residual [sha].
- S2 no t, `-plane_distance` passed directly (the /Od spelling): residual [sha]. alndiff shows the same three one-slot moves: movzx ax,al; push eax; lea eax,[ebp-0xc].
(10) I read the /Od dump od_sphere_portal.txt (0x8a0670). Statement order, the standalone call, short projection/sign, the plane local, the RTC names and the single shared false epilogue are all confirmed.
(11) /W3 re-run: the candidate adds C4244 double->real at final.c:367 plus 2x C4244 short->boolean. S2 adds only the two short->boolean warnings.
(12) House-rule pass on the diff, with precedents checked: actor_moving.c:2136 (commit ea507d5b), units.c:11087, and the Lane B manifest reopen bar.
## issues
Not approved. Every worker measurement reproduces, but the one construct that closes the function does not pass admission.
1. STRIP TEST FAILS. Only the `double t` local matches. The type-stripped `real t` (S1) and the plain `point_from_line3d(point, &plane->n, -plane_distance, ...)` (S2) are both residual [sha]. The worker's TG2 stripped only the inert `(real)` cast, not the double. By brief section 2, when only the decorated form matches, the decoration is the fake.
2. CONTRADICTED BY FIRST-PARTY EVIDENCE.
- The /Od readout at 0x8a07bb negates the float local inline at the call (`movss xmm0,[ebp-0xc]; xorps; movss [esp],xmm0`).
- The /Od frame has no slot for any `t`; every user-area slot is accounted for. So the later source passed `-plane_distance` directly.
- In the same TU, the exact January sibling _render_debug_fog_planes also passes `-render.fog.planar_maximum_distance` directly to point_from_line3d (final.c:607-608).
- The worker also shows collision_prism_test_point: the same helper with a float `-distance` gives the separated form.
- The `double` is inferred only from the schedule. It is not attested anywhere.
3. HOUSE RULE. docs/common_constants.md says to use `double` only where an API or vararg promotion requires it; neither applies here. The construct also adds a /W3 C4244 double->real warning at final.c:367 that the plain spelling does not produce.
4. PRECEDENTS DO NOT SETTLE IT.
- actor_moving.c:2136 is a zero-credit fidelity landing in a still-parked function (ea507d5b), with no stated ruling on the double.
- The units tgr closure predates the /Od-attestation practice.
- The Lane B manifest (_actor_action_handle_vehicle_entry) explicitly requires a natural fact "without a double temp".
- If the owner admits it, the 2026-09-20 inferred-construct ruling needs an in-source comment saying it is inferred from January bytes and contradicted by /Od. The patch has no such comment.
5. PROCESS. The proposal says both "apply production.patch" and "HOLD FOR RULING". An integrator cannot apply that mechanically. It is an owner escalation, and the FUNCTIONS_EXACT label holds only if the owner admits the double.
6. OBJECT BLOCKER. provider_link FAIL(2) is real, but it already exists on production and is identical there, so it is not caused by this change.
Everything else in the diff is sound and an improvement:
- It removes production's hand expansion of point_from_line3d (a rule-6 violation) and uses the genuine inlined call.
- `point` and the RTC/HCEX names are correct.
- short sign is attested by January's `movzx ax,al` and the /Od word slot.
- It follows the /Od statement order and the single shared `return FALSE`.
A fallback that needs no ruling is scratch/w/review_structures/s2_inline_neg.c. It uses the /Od-attested plain spelling: 432/11 [sha], three one-slot moves, surplus identical to production. It earns zero credit and would need a park entry under rule 11.
Recommended action: do not apply production.patch now. Send the double-t question to the owner with REVIEW.md R4/R5 as the evidence.
@@ -0,0 +1,67 @@
# review source/tag_files/files_windows
approve=True
## checks
I re-ran every check myself; all artifacts are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_files_windows\ and the notes are in REVIEW.md there.
1) Patch application. production.patch passes `git apply --check`. A copy of the production file with the patch applied (patched.c) has sha256 aa3dac72..., the same as the worker's files_windows.c and a1.c. All lines are CRLF in both files. The tracked tree is untouched.
2) Gate. gate.py --all on patched.c gives 27 exact / 0 residual / 0 unwritten (cand.gate.txt). The production source gives 26/1, with _file_get_size [sha] as the residual (prod.gate.txt).
3) object_audit on cand.obj: PASS. 55 January symbols, 0 differ. The surplus list is identical to the production audit; the only changed row is _file_get_size, DIFF -> ok.
4) Section comparison (secdiff.py, coff_compare.section_infos_equal per owner key). build/base vs cand.obj: 60 sections the same, 1 different (_file_get_size). So _file_error's own out-of-line body and the four callers that inline it (open/read/write/exists) are byte- and relocation-identical to production. The worker's a1.obj matches cand.obj on 61/61 sections. Against January's split obj, _file_get_size and _file_error are both equal.
5) pdb_storage: 0 disagreements. surplus_identity: 0 candidate-only code COMDATs. A direct symbol census of cand.obj finds only the 6 surplus .rdata entries production already has (3 literals, 3 D3D tables). provider_link on cand.obj: SELECTED-PROVIDER LINK PASS.
6) Data. build/report.json already shows 1348/1348, and every non-.text section is identical to build/base. objdiff-cli 3.3.1 (the frozen scorer in build/tools) on a fresh scratch project: code 4062/4062, data 100%, 27/27 functions.
7) Other gates. fake_match_scan: 0 leads. The unit has no entry in object_admission_rejections.json or semantic_data_matches.json. The only config reference to _file_get_size is its parked.json entry.
8) Strip tests (my own recompiles):
- strip_errcode.c (the candidate with GetLastError() passed inline again): 26/1.
- strip_singleexit.c (production's two-return get_size plus the error_code local): 26/1.
Both edits are load-bearing and together sufficient.
9) Name-count / tie check, lab only:
- La: an UNUSED `unsigned long error_code;` declaration: 26/1.
- Lb_1..4 / Lc_1..4: 1-4 extern names before file_error or before file_get_size, without the local: all 26/1.
- So the lever is the local that holds a value, not the name count.
- Robustness: the candidate plus 1, 3 or 7 extern names at either position stays 27/27, so this is not a fragile declaration-count tie.
10) /Od build. I re-read fn 0x8ae8f0 and 0x8aef00 with odbuild; both match the worker's dumps. I resolved the IAT with pefile, reading the file as data only: 0xf56180=GetLastError, 0xf56184=SetLastError, 0xf562c0=FormatMessageA, 0xf56320=GetFileAttributesExA.
- file_error keeps GetLastError() in the named slot [ebp-0xc], after info at [ebp-8].
- get_size shows the separate `if (!result)` test and declaration order info, full_path, result (byte), data. The candidate matches both exactly.
11) HCEX.pdb has no file_error or file_get_size record, so it gives no scalar-local evidence either way. The Stian files.c is an LLM lift and counts as corroboration only. The optional a3.c variant (sibling functions call file_error) re-gates at 27/27.
12) House-rule review of the diff:
- info is declared with its initializer (rule 17).
- `boolean result = FALSE;`, braced ifs, a single `return result;`.
- The match_assert is unchanged.
- `unsigned long error_code` is DWORD with a descriptive name.
- Behaviour is identical, because GetLastError() still runs after get_info.
- No header, symbols.json, config, relocs, cast, paren or macro change, and file_error is static, so nothing outside files_windows.obj can move.
## issues
No blocking issues. Disclosures for the integrator and owner:
1) Evidence for the error_code local (owner disclosure).
- In the later PC /Od build, file_error has exactly three locals: info, the GetLastError() result at [ebp-0xc], and a 2048-byte tempstring.
- January cannot have tempstring. Its 64-byte file_error is exact without FormatMessage, and its format string lacks the trailing '%s'.
- That leaves error_code as the only first-party local production lacks, and adding it closes the row.
- Caveat: in the PC revision the value is used twice (FormatMessageA and error()), while January uses it once. Its presence in January is therefore inferred from first-party shape plus January's get_size register allocation, not read directly.
- This is the campaign's standard /Od plus January-bytes double attestation. It differs from the owner-rejected "steering pointer local" (rasterizer_sun_glow_draw), which had no first-party attestation.
- Name-count and declaration-count explanations are refuted by lab tests La, Lb and Lc.
- The ledger text should state this caveat.
2) Park retirement.
- The config/parked.json entry {unit source/tag_files/files_windows, function _file_get_size} must be removed in the same change, because the parked bytes move and the park would otherwise fail PROGRESS.
- It can be removed by hand or with `python -B -m tools.campaign.unpark --write` after the full ninja build.
- The integrator must still run the full ninja build and the stable-verdict sweep; I only gated the single TU.
3) Pre-existing house-rule-13 debt (not introduced here, byte-inert, does not block this admission).
- Production hand-expands file_error's body in 6 places (the `error_info` blocks: create, delete, get_last_modification_date, close, set_position, and one more).
- Those copies still pass GetLastError() inline, so they no longer match the helper's text.
- The worker's a3.c recovers `file_error()` calls there plus January's definition order. It re-gates at 27/27, and the worker reports its emission order matches January's.
- It is a 735-line reorder, so I recommend it as a separate follow-up packet rather than part of this change.
@@ -0,0 +1,68 @@
# review source/units/vehicles
approve=False
## checks
All notes and objects are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_vehicles\ (REVIEW.md).
1. Patch reproduction. I applied each patch outside the repo to HEAD's vehicles.c.
- production.patch gives cand.c byte for byte.
- production_linkclean.patch gives cand_nofighter.c byte for byte.
- symbols_turret_static.patch changes only config/symbols.json line 7052, in place.
2. Gate, re-run independently.
- cand.c: exact 39, residual 0.
- cand_nofighter.c: 38/1 (fighter_new [sha]).
- Production: 37/2.
3. object_audit on the review objects.
- cand.obj: all 68 January-owned sections ok. The only symbol DIFF is the _update_turret_physics storage (2 vs 3).
- New surplus vs base: exactly _cross_product2d (A only), _point_from_line2d and _point_from_line3d.
4. surplus identity (my copy, which takes an explicit obj). All new COMDATs are IDENTICAL to January's selected copies. The only DIFFERENT one is _object_get_bounding_sphere, which base also has.
5. provider_link.
- A: FAIL(1), _cross_product2d LNK2005 against actor_combat in both orders.
- B: new-surplus PASS. Its full FAIL(3) is the same set as base (_interpolate_scalar, _object_get_bounding_sphere, _triple_product3d).
6. /W3 (my warn.py). The warning multiset is identical for base, A and B.
7. Cast strip. I re-gated cand_strip.c: 39/39, and the scout and fighter sections equal cand.obj. The 28 pointer representation casts are byte-inert.
8. /Od listing regenerated with od_linear.py; it matches dbg_scout.asm. Checked site by site:
- Hunk 2: pfl3d calls at 0x8fda42 and 0x8fdad2.
- Hunk 3: cross_product2d at 0x8f6cbf..0x8f6ccd; the body at 0x455250 is a.i*b.j - a.j*b.i.
- Hunk 4 matches /Od everywhere except the two disclosed deviations and a minor tail-block const/delta placement.
9. HCEX DIA2Dump confirms the param name powered_mass_points and all RTC/local names.
10. The symbols.json change for _update_turret_physics is backed by the evidence.
- cachebeta has no public symbol at 0x1A4F50; its neighbours _vehicle_hover and _vehicle_is_flipped are public.
- Only vehicles.obj defines or references it.
11. Strip tests on the constructs that were not stripped:
- /Od-literal leveling: rv_od_leveling.
- Field copies for the forward/left 2D copies: rv_fc_fwd, rv_fc_left, rv_fc_both.
- Scout (real) casts: rv_nocast_sqrt, rv_nocast_fabs.
12. build/report.json vehicles data is 1744/1744. There are no parks or admission rejections for the unit.
13. Fallback: fallback_hunks12.patch (hunks 1 and 2 only) was built and checked.
## issues
BLOCKING 1 (not disclosed): the scout adds two aggregate 3D->2D view copies, and both are needed for the match.
- The copies are `real_vector2d vehicle_forward_horizontal = *(real_vector2d const *)&vehicle->object.forward;` and `vehicle_left_horizontal = *(real_vector2d const *)&vehicle_left;`.
- Production uses field copies at both sites. With field copies in cand.c, the scout has 11 REAL regions (forward only), 23 (left only) and 29 (both).
- The brief requires a byte-inert strip test for any new 3D->2D view cast. These casts fail it.
- The owner has not yet ruled on this shape for this function: docs/object_matching_logs/claude_structural_owner_packet_20260923.md section 5 lists _update_alien_scout_physics and asks whether a /Od-attested aggregate view copy qualifies when the field copy does not match.
- The worker's disclosure says all 28 casts are byte-inert and does not mention these two.
BLOCKING 2: the leveling point_from_line2d is chosen only because it shifts the count.
- It contradicts /Od at its own site. /Od 0x8f8b4a..0x8f8bba has two component statements, `applied_alpha.i/j += leveling_scale*alpha_desire.i/j*(1-up.k)`, and 0x444020 is called only twice, both for alpha_desire.
- The worker's claimed January-byte evidence is refuted. The homed t at +0x53a..+0x557 (`fst [ebp-4]`) is produced identically by the /Od component spelling. My rv_od_leveling.obj and the worker's own i2.obj both have those exact bytes there.
- With the component spelling, the only remaining difference is a 2-instruction M8 leaf swap at +0x628 (S3): January has `fld [ebx]; fmul [esi+0x18]` and ours has the reverse.
- The worker's own bisect shows that any +1 inline temp anywhere in the window closes S3, so the evidence does not single out this construct.
- That makes it the count oracle used as a landing. Per the owner gates, the leveling needs an owner ruling or independent evidence.
BLOCKING 3 (disclosed): option (A) emits a _cross_product2d COMDAT that fails LNK2005 against actor_combat's hand copy (the actor_combat provider problem). The fix is owner packet section 1, decision (b), which is still pending.
Because hunk 4 is in both (A) and (B), neither can land now.
MINOR:
- Two (real) casts in the scout are needed for the match: square_root((real)(...)) gives 4 REAL regions without it, and (real)fabs gives 3. Production already uses the same idiom and the worker disclosed the sqrt one. Listed for owner awareness only.
- The candidate declares the tail-block 0.1 const and delta at the top of the block. /Od declares them in a nested block after target (worker's t2c shows this is byte-inert). So the "statement-for-statement" claim is slightly overstated.
LANDABLE WITHOUT A RULING:
- (C) scratch/w/vehicles/symbols_turret_static.patch plus a csplit-only regen of vehicles. The evidence is verified, and it clears the only symbol DIFF.
- Optionally, scratch/w/review_vehicles/fallback_hunks12.patch (hunks 1 and 2: the define removal and the /Od-attested pathfinding point_from_line3d calls).
- Gate stays 37/2; the fighter improves to [sha] and no row is lost.
- The _point_from_line3d COMDAT is IDENTICAL and provider_link PASSes.
- /W3 is unchanged. It earns no exact credit; it is a rule-6 cleanup.
Once the owner rules on section 5 (view copies) and on the count-only leveling pfl2d, (B) could land as proposed (scout EXACT, 38/1). (A) would also need the actor_combat provider repair.
@@ -0,0 +1,63 @@
# source/ai/actor_firing_position
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
null
## candidate_files
null
## production_changes
APPLY NOW; these fixes do not depend on the provider blocker and are verified.
(1) Source: apply C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_firing_position_audit\actor_firing_position_cand1.diff to source/ai/actor_firing_position.c. The full file is afp_cand1.c in the same folder.
- Line 397: drop `static` from global_pre_evaluator_table.
- Line 409: drop `static` from global_post_evaluator_table.
- Lines 420-421: `short global_temporary_sort_firing_position_count= 0;` and `struct firing_position *global_temporary_sort_firing_position_array= NULL;`.
(2) config/symbols.json: edit lines in place, never re-serialise. Append `, "static": true` to these 14 entries: _firing_position_store_evaluation_debug (75280), _firing_position_reject (75360), _pre_evaluator_guard (75376), _pre_evaluator_combatmove (75584), _post_evaluator_global (76160), _post_evaluator_pursuit (76384), _post_evaluator_hide (76768), _post_evaluator_uncover (76992), _post_evaluator_attack (77184), _firing_position_compare (77664), _pre_evaluator_global (78592), _pre_evaluator_pursuit (80208), _pre_evaluator_panic (80672), _pre_evaluator_attack (88608). The combined diff for all 4 units is scratch/w/actor_firing_position_audit/symbols_json_all.diff.
(3) Regenerate the split, then run a full ninja.
Do NOT set config.json status to Matching until the _distance3d provider is repaired.
## evidence
Before (production): object_audit FAIL with 18 storage differences. pdb_storage showed two separate faults:
- 14 functions are absent from cachebeta publics, so January had them file-static, but the split says external.
- 4 data symbols are PDB PUBLIC but our source had them static.
Why the sort globals need an initialiser: contribs.json shows module 456 owns its own 8-byte .bss contribution at 0x319ca0, not the linker_common pool. An uninitialised external would be COMMON, so January wrote them with `= 0`/`= NULL`.
After:
- afp_cand1 gate 27/27 EXACT; the .bss and .rdata sections stay byte-, relocation- and alignment-identical.
- csplit regen with the scratch config: of 833 split objects, only this one plus game_engine, real_math and model_animations differ; here the sections are identical and only the 14 storage rows changed.
- object_audit against the scratch split: PASS, 73/73 symbols.
- pdb_storage2: 0 disagreements.
- provider_link: every surplus item passes except _distance3d.
Files: LEDGER.md, final_gate.txt, final_verification.txt, xrefs_all.py, pub_census.py.
## blockers
The surplus _distance3d COMDAT from real_math.h is not byte-identical to January's selected copy in source/ai/action_vehicle.obj. That copy is a no-duplicates (NODUP) hand copy.
- Order differs: ours squares dz first and ends `fstp st(3)`; January's squares dx first and uses `fxch st(2); fsqrt; fstp st(2)`.
- It fails to link against action_vehicle in both orders (LNK2005).
- January's actor_firing_position has no call relocation to _distance3d, so the COMDAT is the normal header-inline emission.
This is the same conflict already recorded for leaf_map and biped_limp_noodle in config/object_admission_rejections.json.
## reopen_criteria
Reopen when the action_vehicle distance3d provider is repaired board-wide: a real_math.h distance3d that is byte-identical to January's selected copy, with no loss of an exact sibling. Then rerun provider_link on this object; it should pass in both link orders, which makes the object admissible.
## task notes
No tracked files were edited, and no ninja, configure or git-mutating command was run.
Law confirmed on this board: cachebeta.pdb publics include externals that nothing references. So a missing public means file-static, and a static that January emits with no relocation was called in source through inlined calls. When a hand-written copy of a static's body sits where January called the static, you must restore the call. That applies to game_engine_get_type and press_start_to_begin, whose call sites are confirmed by the /Od build and HCEA.
The alignment rule for loose statics versus one aggregate also holds for .rdata: splitting real_math_constants into the four HCEX statics turned 8-byte alignment into January's 4-byte.
Stale comment: game_engine_get_starting_location_rating has a comment saying 'NonMatching with the same EBX/EDI mirror', but the function is EXACT. I left it; the integrator may drop it.
All changes are local to the .c files, and the symbols.json edits change only these 4 split objects; the other 829 splits are byte-identical. So no blast radius is expected, but the integrator must still regenerate the split and do a full sweep.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_firing_position_audit\LEDGER.md
Evidence and patch files, all in the same folder:
- Candidate sources: afp_cand1.c, ge_cand3.c, rm_cand2.c
- Source patches: actor_firing_position_cand1.diff, game_engine_cand3.diff, real_math_cand2.diff
- symbols.json patch: symbols_json_all.diff (27 lines), plus the scratch config/ and regenerated split/
- Results: final_gate.txt, final_verification.txt, hcex_real_math_compiland.txt, admission_audit_base.json
- Tools: xrefs_all.py, pub_census.py, object_audit_split.py, pdb_storage2.py, split_diff.py, od_empty_stubs.py, od_small_refs.py
@@ -0,0 +1,65 @@
# source/game/game_engine
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
null
## candidate_files
null
## production_changes
APPLY NOW; these fixes do not depend on the provider blocker and are verified.
(1) Source: apply C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_firing_position_audit\game_engine_cand3.diff to source/game/game_engine.c. The full file is ge_cand3.c in the same folder.
- Add `static` to the prototype and definition lines of 12 functions: game_engine_build_lighting, game_engine_infinite_grenades_internal, game_engine_post_rasterize_in_game, game_engine_rasterize_in_game_score, game_engine_predict_resources, game_engine_update_purge, game_engine_update_weapons, game_engine_update_item_spawn, find_closest_player_callback, find_closest_player_index, game_engine_get_type, game_engine_press_start_to_begin.
- Remove the hand-written copies of the game_engine_get_type body (`long game_type = NONE; if (game_engine) game_type = game_engine->type;`) and pass `game_engine_get_type()` directly as the match_game_type argument, in three places: game_engine_get_starting_location_rating, game_engine_update_item_spawn and handle_custom_starting_equipment. The now-unused game_type locals are removed.
- In game_engine_nonplayer_post_rasterize, add `game_engine_press_start_to_begin();` before `break;` in `case 0: case 1:`.
(2) config/symbols.json: append `, "static": true` to _find_closest_player_callback (617600), _game_engine_press_start_to_begin (619872), _find_closest_player_index (632448), _game_engine_update_item_spawn (635808). These are in symbols_json_all.diff.
(3) Regenerate the split, then run a full ninja.
Do NOT set Matching until _distance3d is repaired.
## evidence
Before (production): object_audit FAIL on 8 functions that the split marks static but our source defined as external. pdb_storage found 4 more functions (find_closest_player_callback/_index, press_start_to_begin, update_item_spawn) that are external in both the split and our build but absent from cachebeta publics.
Census (pub_census.py): unreferenced externals DO appear as publics (for example _static_camera_new and _collision_test_pill_new). So a function missing from the publics is file-static, even when no relocation names it.
That raised a problem for _game_engine_get_type and _game_engine_press_start_to_begin. January emits both as statics with no relocation anywhere, which means they were referenced by calls that were inlined away. Our source had no caller for either, so making them static would drop them; this was measured in G2 (press_start becomes UNWRITTEN).
Where the callers came from:
- get_type: in the /Od build, fn 0x59df80 is called from 0x59dbc0 (starting_location_rating, passing the result straight into match_game_type), 0x5aefc0 (update_item_spawn) and 0x5b25d0 (handle_custom_starting_equipment). The HCEA sources game_engine_update_item_spawn.c and handle_custom_starting_equipment.c make the same call.
- press_start_to_begin: /Od fn 0x5a1600 is nonplayer_post_rasterize. Its jump table at 0x5a172c sends cases 0 and 1 to `call 0x5a6510`, an empty stub whose only caller is that site. HCEA says press_start_to_begin is an empty function.
- Names are attested by HCEX.pdb (find_closest_player_index/_callback, update_item_spawn, get_type) and by HCEA (press_start_to_begin).
After:
- ge_cand3 gate 180/180 EXACT, including get_type (32), press_start (16), nonplayer_post_rasterize (160), update_item_spawn (336), starting_location_rating (96) and handle_custom (304).
- Scratch-split regen: all 263 sections identical; only 4 storage rows changed.
- object_audit: PASS, 269/269 symbols.
- pdb_storage2: 0 disagreements.
- provider_link: the 8 'NO JANUARY PROVIDER' rows are gone; only _distance3d fails.
The tools/audit_object_admission.py base run already lists this unit with function_gap 0 (build/report.json's objdiff under-credit is covered by the semantic report), so no semantic_matches entry is needed.
## blockers
The surplus _distance3d COMDAT from real_math.h differs from January's selected NODUP hand copy in source/ai/action_vehicle.obj. It fails to link in both orders (LNK2005: '_distance3d already defined'). January's game_engine has no call relocation to _distance3d.
## reopen_criteria
Reopen after the board-wide action_vehicle distance3d provider repair. Then rerun provider_link on ge_cand3.obj; it should pass in both orders.
## task notes
No tracked files were edited, and no ninja, configure or git-mutating command was run.
Law confirmed on this board: cachebeta.pdb publics include externals that nothing references. So a missing public means file-static, and a static that January emits with no relocation was called in source through inlined calls. When a hand-written copy of a static's body sits where January called the static, you must restore the call. That applies to game_engine_get_type and press_start_to_begin, whose call sites are confirmed by the /Od build and HCEA.
The alignment rule for loose statics versus one aggregate also holds for .rdata: splitting real_math_constants into the four HCEX statics turned 8-byte alignment into January's 4-byte.
Stale comment: game_engine_get_starting_location_rating has a comment saying 'NonMatching with the same EBX/EDI mirror', but the function is EXACT. I left it; the integrator may drop it.
All changes are local to the .c files, and the symbols.json edits change only these 4 split objects; the other 829 splits are byte-identical. So no blast radius is expected, but the integrator must still regenerate the split and do a full sweep.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_firing_position_audit\LEDGER.md
Evidence and patch files, all in the same folder:
- Candidate sources: afp_cand1.c, ge_cand3.c, rm_cand2.c
- Source patches: actor_firing_position_cand1.diff, game_engine_cand3.diff, real_math_cand2.diff
- symbols.json patch: symbols_json_all.diff (27 lines), plus the scratch config/ and regenerated split/
- Results: final_gate.txt, final_verification.txt, hcex_real_math_compiland.txt, admission_audit_base.json
- Tools: xrefs_all.py, pub_census.py, object_audit_split.py, pdb_storage2.py, split_diff.py, od_empty_stubs.py, od_small_refs.py
@@ -0,0 +1,65 @@
# source/math/real_math
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
null
## candidate_files
null
## production_changes
APPLY NOW; these fixes do not depend on the provider blocker and are verified.
(1) Source: apply C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_firing_position_audit\real_math_cand2.diff to source/math/real_math.c. The full file is rm_cand2.c.
- Delete the invented `struct real_math_constant_block` and the `real_math_constants` aggregate.
- Define four file statics, in this order and with the same initialisers: `static real_matrix4x3 const private_identity4x3`, `static real_matrix4x3 const private_negative_identity4x3`, `static real_quaternion const private_identity_quaternion`, `static real_rectangle3d const private_null_rectangle`.
- Point the 31 global_* pointer initialisers at those statics, for example `&private_identity4x3.up` and `&private_null_rectangle`. The existing casts are unchanged.
(2) config/symbols.json: edit lines in place. Rename line `2600880 _real_math_constants static` to `_private_identity4x3` (keep static:true). Insert directly after it, in ascending order: `{ "file_offset": 2600932, "flags": 0, "name": "_private_negative_identity4x3", "static": true },`, `{ "file_offset": 2600984, "flags": 0, "name": "_private_identity_quaternion", "static": true },`, `{ "file_offset": 2601000, "flags": 0, "name": "_private_null_rectangle", "static": true },`. The file uses CRLF line endings. These edits are in symbols_json_all.diff.
(3) Apply source and symbols.json together, then regenerate the split and run a full ninja. The January split's .data relocations get renamed to the new statics.
No other config or source names real_math_constants. Do NOT set Matching until the 4 provider conflicts are resolved.
## evidence
Before: object_audit FAIL, 2 problems:
- _real_math_constants storage mismatch (January static, ours external).
- The 168-byte .rdata section alignment: January ALIGN_4 (contribs module 222, flags 0x40300040) versus ours ALIGN_8.
R1 (static only) fixed the storage, but the alignment stayed 8.
The aggregate-vs-scalar alignment rule says any object of 64 bytes or more forces 8-byte alignment. January's 4-byte alignment therefore proves the 144-byte aggregate did not exist.
HCEX.pdb (compiland real_math.obj, saved as hcex_real_math_compiland.txt) lists 4 file statics: private_identity4x3, private_negative_identity4x3, private_identity_quaternion and private_null_rectangle. Their sizes (52, 52, 16, 24) put them at January offsets 0, 0x34, 0x68 and 0x78; global_projection3d_mappings sits at 0x90. January's only references to the block are inside real_math's own .data.
R2 (rm_cand2): 84/84 EXACT. The .rdata section is now 0x40300040 (ALIGN_4), with symbols at 0/52/104/120 and 144, exactly matching January.
Against the scratch split: object_audit PASS, 134/134 symbols; pdb_storage2 0 disagreements. The _real_math_constants 'NO JANUARY PROVIDER' row is gone.
## blockers
The surplus helper COMDATs are byte-IDENTICAL to January's selected copies, but those providers are no-duplicates (NODUP) hand copies (nodup_census), so linking fails with LNK2005 in both orders:
- _cross_product2d: source/ai/actor_combat.obj
- _project_point3d: source/ai/path_obstacles.obj
- _projection_sign_from_vector3d: source/effects/decals.obj
- _triple_product3d: source/effects/decals.obj
January's own point_in_triangle3d calls _project_point3d (x3) and _projection_sign_from_vector3d, so real_math's copies are legitimate. The defect is the hand copies in the providers.
## reopen_criteria
Reopen when actor_combat, path_obstacles and decals replace their NODUP hand copies with the genuine real_math.h inlines, with every exact caller kept and zero board regressions. Then rerun provider_link on rm_cand2.obj; it should pass in both orders.
## task notes
No tracked files were edited, and no ninja, configure or git-mutating command was run.
Law confirmed on this board: cachebeta.pdb publics include externals that nothing references. So a missing public means file-static, and a static that January emits with no relocation was called in source through inlined calls. When a hand-written copy of a static's body sits where January called the static, you must restore the call. That applies to game_engine_get_type and press_start_to_begin, whose call sites are confirmed by the /Od build and HCEA.
The alignment rule for loose statics versus one aggregate also holds for .rdata: splitting real_math_constants into the four HCEX statics turned 8-byte alignment into January's 4-byte.
Stale comment: game_engine_get_starting_location_rating has a comment saying 'NonMatching with the same EBX/EDI mirror', but the function is EXACT. I left it; the integrator may drop it.
All changes are local to the .c files, and the symbols.json edits change only these 4 split objects; the other 829 splits are byte-identical. So no blast radius is expected, but the integrator must still regenerate the split and do a full sweep.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_firing_position_audit\LEDGER.md
Evidence and patch files, all in the same folder:
- Candidate sources: afp_cand1.c, ge_cand3.c, rm_cand2.c
- Source patches: actor_firing_position_cand1.diff, game_engine_cand3.diff, real_math_cand2.diff
- symbols.json patch: symbols_json_all.diff (27 lines), plus the scratch config/ and regenerated split/
- Results: final_gate.txt, final_verification.txt, hcex_real_math_compiland.txt, admission_audit_base.json
- Tools: xrefs_all.py, pub_census.py, object_audit_split.py, pdb_storage2.py, split_diff.py, od_empty_stubs.py, od_small_refs.py
@@ -0,0 +1,45 @@
# source/models/model_animations
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
null
## candidate_files
null
## production_changes
APPLY NOW; this fix is verified and needs no source change.
config/symbols.json: edit lines in place and append `, "static": true` to _animation_is_compressed (1113712), _animation_keyframe_search (1115488), _animation_get_keyframe_rotation (1117056), _animation_get_keyframe_translation (1117840), _animation_get_keyframe_scale (1118608). These are in scratch/w/actor_firing_position_audit/symbols_json_all.diff.
Then regenerate the split. Do NOT set Matching until _distance3d is repaired.
## evidence
Before: object_audit FAIL with 5 storage rows. The split says external, but our source is static and the names are absent from cachebeta publics. All relocations naming these functions are inside model_animations.obj (xrefs_all.py over every split object).
After the scratch-split regen: only these 5 rows changed from 2 to 3, and all 68 sections are identical. object_audit on the production build/base obj: PASS, 73/73 symbols. pdb_storage2: 0 disagreements. gate: 32/32 EXACT. provider_link: every surplus item passes except _distance3d, including _fast_ftol against actor_combat.
## blockers
The surplus _distance3d COMDAT from real_math.h differs from January's selected NODUP hand copy in source/ai/action_vehicle.obj (x/z squaring order), and fails to link in both orders (LNK2005). January's model_animations has no call relocation to _distance3d.
## reopen_criteria
Reopen after the board-wide action_vehicle distance3d provider repair. Then rerun provider_link on this object; it should pass in both orders.
## task notes
No tracked files were edited, and no ninja, configure or git-mutating command was run.
Law confirmed on this board: cachebeta.pdb publics include externals that nothing references. So a missing public means file-static, and a static that January emits with no relocation was called in source through inlined calls. When a hand-written copy of a static's body sits where January called the static, you must restore the call. That applies to game_engine_get_type and press_start_to_begin, whose call sites are confirmed by the /Od build and HCEA.
The alignment rule for loose statics versus one aggregate also holds for .rdata: splitting real_math_constants into the four HCEX statics turned 8-byte alignment into January's 4-byte.
Stale comment: game_engine_get_starting_location_rating has a comment saying 'NonMatching with the same EBX/EDI mirror', but the function is EXACT. I left it; the integrator may drop it.
All changes are local to the .c files, and the symbols.json edits change only these 4 split objects; the other 829 splits are byte-identical. So no blast radius is expected, but the integrator must still regenerate the split and do a full sweep.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_firing_position_audit\LEDGER.md
Evidence and patch files, all in the same folder:
- Candidate sources: afp_cand1.c, ge_cand3.c, rm_cand2.c
- Source patches: actor_firing_position_cand1.diff, game_engine_cand3.diff, real_math_cand2.diff
- symbols.json patch: symbols_json_all.diff (27 lines), plus the scratch config/ and regenerated split/
- Results: final_gate.txt, final_verification.txt, hcex_real_math_compiland.txt, admission_audit_base.json
- Tools: xrefs_all.py, pub_census.py, object_audit_split.py, pdb_storage2.py, split_diff.py, od_empty_stubs.py, od_small_refs.py
@@ -0,0 +1,50 @@
# source/ai/path
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/path_distance3d_1_real_math_h.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/path_distance3d_2_object_lights_c.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/path_distance3d_3_action_vehicle_c.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/lab/math/real_math.h.distance3d",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/lab/cand_ol_nolocal.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/lab/cand_av_dist.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/lab/sweep2_d1.txt"
]
## production_changes
Three edits, all CRLF files. GNU patch rewrote the file as LF in a test, so hand-apply these edits or copy the lab files byte-for-byte. (1) source/math/real_math.h, the distance3d body (around lines 1195-1196): replace `real_vector3d v;` and `return magnitude3d(vector_from_points3d(a, b, &v));` with the single line `return square_root(distance_squared3d(a, b));`. (2) source/objects/object_lights.c:1834-1835: replace `real_point3d const *light_position = &light->position;` and `real distance = distance3d(light_position, center);` with `real distance = distance3d(&light->position, center);`. (3) source/ai/action_vehicle.c: delete `#define distance3d distance3d_inline` (line 73), `#undef distance3d` (line 79), and the hand copy `real distance3d(real_point3d const *a, real_point3d const *b) { return square_root(distance_squared3d(a, b)); }` (lines 202-207). This third edit is the action_vehicle provider repair, so dedupe it with the provider worker. (4) Re-measure the park for source/ai/ai _ai_test_line_of_sight in config/parked.json: measurements.base changes to 1040 B / 50 relocs / normalized_sha256 c524fe19529bfd4a72e95be52a875925bcc929d347b37471eb16b75bd393da78; re-take objdiff_percent. It stays residual, but its moving bytes would otherwise fail the PROGRESS check. (5) Full ninja, regression gate and stable sweep. Then admit source/ai/path as Matching. No symbols.json change and no split regeneration are needed.
## evidence
Production audit: path is 32/32 EXACT, object_audit PASS (74 January symbols, 0 differ), pdb_storage 0 disagreements. The only failure is `_distance3d`. January's path.obj references it out of line (from _path_state_build_path). Our COMDAT differs from January's selected action_vehicle copy in x87 operand order, and action_vehicle defines it as an ordinary hand copy (NODUP), so the link fails with LNK2005 in both orders.
Decode: January's selected copy equals the `square_root(distance_squared3d(a,b))` form. With that header body (probe D1), path stays 32/32, its _distance3d is section_infos_equal to January's, and all 19 TUs that emit _distance3d produce byte-identical copies. The one regression in a 20-TU sweep was object_lights _find_point_lights_for_object_in_cluster. That turned out to be a steering local: with the natural call `distance3d(&light->position, center)` (D2) object_lights is 43/43 and object_audit PASS. With the old header body both that spelling and an argument swap fail, so this is a real strip-test result.
D3 (action_vehicle without the rename and hand copy; the genuine out-of-line use at action_vehicle.c:555 stays) gives 17/17, object_audit PASS, and the header-emitted _distance3d owns January's row. Pair link of path + action_vehicle PASSes in both orders.
Board sweep with D1+D2+D3 (lab/sweep2_d1.txt): 466 units, 0 January-status changes, 0 compile failures. Content-only moves: three residuals (_ai_test_line_of_sight, which is parked; _actor_perception_refresh_danger_zone, 234->238 diff lines; _player_set_action_result, 16->6 diff lines, closer). _distance_squared3d is newly emitted in 11 TUs, all identical to January's action_alert copy, and that provider links PASS. _magnitude3d is no longer emitted in 4 TUs.
All other path surplus is IDENTICAL and links PASS. `_point_from_line3d` is emitted even though January's path.obj does not reference it out of line. It is identical and links PASS, which satisfies the current rule-6 exception in docs/campaign_house_rules.md; flag it if the older 17-object PFL ruling is still enforced. The .drectve surplus is systemic.
## blockers
None remaining in path itself once the three edits land. The admission depends on the action_vehicle provider repair (edit 3), which the provider worker may also be doing, and on the integrator's full-build verification.
## reopen_criteria
If the integrator's full build shows any January-status regression or the regression gate fails, stop and report the row. Do not revert only the header body: path, object_lights and action_vehicle must move together.
## task notes
Tools written under scratch/w/path_audit/tools/: gate_lab.py (a copy of gate.py whose main file compiles from scratch/w/path_audit/lab/ so lab/<dir>/<header> shadows a shared header; lab only), sweep.py and sweep2.py (per-function and per-section board sweeps of base vs lab, with controls), symdis.py, strrefs.py, strcensus.py, symcensus.py, linkpair.py (VC7 pair link in both orders), mkvariant.py, mk_objects_variant.py, object_audit_split.py / pdb_storage_split.py (run against a scratch split via the AUDIT_SPLIT env var).
Caveat for the integrator: in the lab, rasterizer_frame_statistics _rasterizer_frame_statistics_draw flips EXACT/residual even with an UNCHANGED header copy. This is an environment artifact (main-file path/PID), so ignore it in lab sweeps.
Useful follow-ups from the D1 sweep: players _player_set_action_result gets closer (16 -> 6 diff lines; its three `real_point3d const *` pointer locals look like the same M8 steer). Pre-existing pdb_storage disagreements outside this lane: action_vehicle _action_vehicle_evaluate_seat; object_lights _cluster_get_first_light, _cluster_get_next_light, _light_attenuation, _should_render_lights.
Line endings: all touched source files are CRLF, and GNU patch converted a test file to LF, so hand-apply the edits or copy the lab candidate files.
The lab shadow headers were restored to their original copies. The lab/objects shadow is active with an unchanged objects.h; lab/math/real_math.h is .orig.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/LEDGER.md
@@ -0,0 +1,37 @@
# source/ai/path_obstacle_avoidance
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/plink_path_obstacle_avoidance.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/surplus_path_obstacle_avoidance.txt"
]
## production_changes
None. The provider repair for source/ai/actor_combat _cross_product2d is owned by the provider worker.
## evidence
24/24 EXACT, object_audit PASS (52 January symbols, 0 differ), pdb_storage 0 disagreements. All 11 surplus COMDATs are IDENTICAL to January's selected copies. The provider link fails only on `_cross_product2d` (provider actor_combat): LNK2005 in both orders. actor_combat.c renames the header inline (#define cross_product2d cross_product2d_inline at :129) and hand-writes a NODUP copy at :523, with no call anywhere in actor_combat.c. January census: there are ZERO out-of-line references to _cross_product2d anywhere on the board, and January's path_obstacle_avoidance neither defines nor references it. January's actor_combat row is therefore an emitted-but-unreferenced COMDAT from an inlined use.
## blockers
Helper `_cross_product2d`. The selected provider is source/ai/actor_combat, which holds a NODUP hand copy at actor_combat.c:523 with no genuine use. Our copy is byte-identical but fails with LNK2005 in both orders.
## reopen_criteria
actor_combat emits _cross_product2d as a SELECT_ANY header inline, by restoring January's genuine inlined use and removing the rename and hand copy while keeping actor_combat's rows exact. Then rerun provider_link.py on source/ai/path_obstacle_avoidance and admit.
## task notes
Tools written under scratch/w/path_audit/tools/: gate_lab.py (a copy of gate.py whose main file compiles from scratch/w/path_audit/lab/ so lab/<dir>/<header> shadows a shared header; lab only), sweep.py and sweep2.py (per-function and per-section board sweeps of base vs lab, with controls), symdis.py, strrefs.py, strcensus.py, symcensus.py, linkpair.py (VC7 pair link in both orders), mkvariant.py, mk_objects_variant.py, object_audit_split.py / pdb_storage_split.py (run against a scratch split via the AUDIT_SPLIT env var).
Caveat for the integrator: in the lab, rasterizer_frame_statistics _rasterizer_frame_statistics_draw flips EXACT/residual even with an UNCHANGED header copy. This is an environment artifact (main-file path/PID), so ignore it in lab sweeps.
Useful follow-ups from the D1 sweep: players _player_set_action_result gets closer (16 -> 6 diff lines; its three `real_point3d const *` pointer locals look like the same M8 steer). Pre-existing pdb_storage disagreements outside this lane: action_vehicle _action_vehicle_evaluate_seat; object_lights _cluster_get_first_light, _cluster_get_next_light, _light_attenuation, _should_render_lights.
Line endings: all touched source files are CRLF, and GNU patch converted a test file to LF, so hand-apply the edits or copy the lab candidate files.
The lab shadow headers were restored to their original copies. The lab/objects shadow is active with an unchanged objects.h; lab/math/real_math.h is .orig.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/LEDGER.md
@@ -0,0 +1,37 @@
# source/ai/path_smoothing
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/plink_path_smoothing.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/surplus_path_smoothing.txt"
]
## production_changes
None. The same actor_combat _cross_product2d provider repair is owned by the provider worker.
## evidence
6/6 EXACT, object_audit PASS (15 January symbols, 0 differ), pdb_storage 0 disagreements. All 10 surplus COMDATs are IDENTICAL. The provider link fails only on `_cross_product2d` against actor_combat's NODUP hand copy (LNK2005 in both orders). January's path_smoothing neither defines nor references _cross_product2d, and there are no January out-of-line references to it anywhere. This matches the earlier object-closure-lane finding.
## blockers
Helper `_cross_product2d`: provider source/ai/actor_combat holds a NODUP hand copy (actor_combat.c:523) with no use in actor_combat.c. Our bytes are identical, but the link fails in both orders.
## reopen_criteria
The same as path_obstacle_avoidance: once the actor_combat provider emits _cross_product2d as SELECT_ANY with its rows exact, rerun provider_link and admit.
## task notes
Tools written under scratch/w/path_audit/tools/: gate_lab.py (a copy of gate.py whose main file compiles from scratch/w/path_audit/lab/ so lab/<dir>/<header> shadows a shared header; lab only), sweep.py and sweep2.py (per-function and per-section board sweeps of base vs lab, with controls), symdis.py, strrefs.py, strcensus.py, symcensus.py, linkpair.py (VC7 pair link in both orders), mkvariant.py, mk_objects_variant.py, object_audit_split.py / pdb_storage_split.py (run against a scratch split via the AUDIT_SPLIT env var).
Caveat for the integrator: in the lab, rasterizer_frame_statistics _rasterizer_frame_statistics_draw flips EXACT/residual even with an UNCHANGED header copy. This is an environment artifact (main-file path/PID), so ignore it in lab sweeps.
Useful follow-ups from the D1 sweep: players _player_set_action_result gets closer (16 -> 6 diff lines; its three `real_point3d const *` pointer locals look like the same M8 steer). Pre-existing pdb_storage disagreements outside this lane: action_vehicle _action_vehicle_evaluate_seat; object_lights _cluster_get_first_light, _cluster_get_next_light, _light_attenuation, _should_render_lights.
Line endings: all touched source files are CRLF, and GNU patch converted a test file to LF, so hand-apply the edits or copy the lab candidate files.
The lab shadow headers were restored to their original copies. The lab/objects shadow is active with an unchanged objects.h; lab/math/real_math.h is .orig.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/LEDGER.md
@@ -0,0 +1,45 @@
# source/render/render_debug
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/lab/pp2d_body.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/lab/math/real_math.h.pp2d",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/lab/cand_decals_pp2d.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/lab/sweep2_pp2d.txt"
]
## production_changes
None landable yet. Proposal for the provider worker: the real_math.h project_point2d body should become the decals form. That means `short x`/`short y` mapping locals, and a single store `p3d->n[projection] = (fabs(plane->n.n[projection])<_real_epsilon) ? 0.0f : ((plane->d - (p2d->x * plane->n.n[x])) - (p2d->y * plane->n.n[y])) / plane->n.n[projection];` in place of the long locals and if/else. The body is in lab/pp2d_body.txt. It also needs the decals provider repair: drop the project_point2d rename and hand copy at decals.c:216/227/833. Neither can land until the two blockers below are resolved.
## evidence
36/36 EXACT, object_audit PASS (63 January symbols, 0 differ), pdb_storage 0 disagreements. The only failure is `_project_point2d`: it is DIFFERENT from January's selected decals copy, and decals holds a NODUP hand copy, so the link fails with LNK2005 in both orders. January's render_debug references the helper out of line from _render_debug_point2d and _render_debug_line2d.
The decals-form header body (P1) gives render_debug 36/36 and a COMDAT section_infos_equal to January's. collision_bsp, collision_features and leaf_map copies also become identical; breakable_surfaces differs by one per-TU scheduling tie.
Board sweep (lab/sweep2_pp2d.txt), with controls: bitmap_drawing _bitmap_copy goes EXACT->residual under this header in 2 of 2 runs and stays EXACT under the unchanged header, with one `mov edi,[ebp+10h]` moved. That is distant blast radius. The rasterizer_frame_statistics flip also appears in the unchanged-header control, so it is a lab artifact. Splitting the change: short locals + if/else keeps bitmap_copy but the copy is not identical; long locals + ?: regresses bitmap_copy; short + if/else(<) is not identical. Only the full ?: form reproduces January's copy, and the ?: is what flips bitmap_copy. I stopped there under the methodology stop rule.
The decals repair lab (P2) makes the header-emitted _project_point2d exact but regresses _decals_delete_permanent_from_cluster: .bss relocations re-attribute and one scheduling move appears. With a SELECT_ANY decals, render_debug + decals link PASSes in both orders; against production decals it FAILs.
## blockers
Helper `_project_point2d`, provider source/effects/decals. (a) Our real_math.h body differs from January's selected copy, and the only exact body (short locals + ?:) flips bitmap_drawing _bitmap_copy, a known fragile tie. (b) decals defines it as a NODUP hand copy (decals.c:833; the genuine use is at decals.c:1500), and removing the hand copy regresses _decals_delete_permanent_from_cluster.
## reopen_criteria
The provider worker lands a project_point2d header body that is byte-identical to January's selected copy with the bitmap_copy tie held and a zero-regression board sweep, plus a decals SELECT_ANY emission that keeps all decals rows exact. Then rerun surplus_identity and provider_link on render_debug and admit.
## task notes
Tools written under scratch/w/path_audit/tools/: gate_lab.py (a copy of gate.py whose main file compiles from scratch/w/path_audit/lab/ so lab/<dir>/<header> shadows a shared header; lab only), sweep.py and sweep2.py (per-function and per-section board sweeps of base vs lab, with controls), symdis.py, strrefs.py, strcensus.py, symcensus.py, linkpair.py (VC7 pair link in both orders), mkvariant.py, mk_objects_variant.py, object_audit_split.py / pdb_storage_split.py (run against a scratch split via the AUDIT_SPLIT env var).
Caveat for the integrator: in the lab, rasterizer_frame_statistics _rasterizer_frame_statistics_draw flips EXACT/residual even with an UNCHANGED header copy. This is an environment artifact (main-file path/PID), so ignore it in lab sweeps.
Useful follow-ups from the D1 sweep: players _player_set_action_result gets closer (16 -> 6 diff lines; its three `real_point3d const *` pointer locals look like the same M8 steer). Pre-existing pdb_storage disagreements outside this lane: action_vehicle _action_vehicle_evaluate_seat; object_lights _cluster_get_first_light, _cluster_get_next_light, _light_attenuation, _should_render_lights.
Line endings: all touched source files are CRLF, and GNU patch converted a test file to LF, so hand-apply the edits or copy the lab candidate files.
The lab shadow headers were restored to their original copies. The lab/objects shadow is active with an unchanged objects.h; lab/math/real_math.h is .orig.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/LEDGER.md
@@ -0,0 +1,53 @@
# source/render/render_objects
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/cand_ro_b1.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/render_objects_lighting_storage_B1.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/render_objects_lighting_storage_B1_symbols_json.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/config_b1/symbols.json",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/cand_ro_a.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/render_objects_lighting_storage_A.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/render_objects_lighting_storage_A_symbols_json.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/render_objects_ogbs_1_action_vehicle_c.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/render_objects_ogbs_2_objects_h.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/lab/cand_av_ogbs.c"
]
## production_changes
This storage fix is independent of the helper blockers and can be applied now; it keeps 22/22. B1 (HCEX-attested, primary): (1) source/render/render_objects.c: delete the file-scope line `struct render_lighting lighting_storage = { 0 };` (line 307). Insert ` static struct render_lighting lighting_storage;` as the first line inside object_get_cached_render_lighting(...) { ... }, before `long render_state_index = ...`. (2) config/symbols.json line 23248: edit the line in place, without re-serializing, from `{ "file_offset": 4980856, "flags": 0, "name": "_lighting_storage" },` to `{ "file_offset": 4980856, "flags": 0, "name": "?lighting_storage@?1??object_get_cached_render_lighting@@9@9", "static": true },`. (3) Regenerate the split with csplit and rebuild. Only source/render/render_objects.obj changes in the split (verified byte-for-byte over all 833 objects). Fallback A, if the owner prefers the file-scope form: make line 307 `static struct render_lighting lighting_storage = { 0 };` and add only `, "static": true` to line 23248. The helper blockers are not landable; proposals for the provider worker: render_objects_ogbs_1_action_vehicle_c.patch (drop action_vehicle's object_get_bounding_sphere rename and hand copy) and render_objects_ogbs_2_objects_h.patch (objects.h:491-492 file string -> "..\\objects\\objects.h"). The objects.h edit needs an owner ruling on per-TU __FILE__ modelling first.
## evidence
22/22 EXACT, object_audit PASS against the current split. pdb_storage flags `_lighting_storage`: split 2, ours 2, but it is absent from the cachebeta.pdb publics, so January had it non-external. HCEX lists lighting_storage as a Static Local of object_get_cached_render_lighting. No other .c references it.
B1 candidate: 21/22 against the current split, the one difference being reloc-identity from the name. Against the scratch split built from config_b1: object_audit PASS (41 symbols, 0 differ), pdb_storage 0 disagreements, and the .bss layout is identical to January's (static local at 0, render_object_globals at 120, debug_inactive_objects at 1148 external, overflow at 1149, 1150 B). Candidate A is 22/22 and also PASSes against split_a. The initialised static-local form was REFUTED: .bss becomes 1148 B and the local moves to offset 1032. As a control, candidate A against the production split fails only on `_lighting_storage` storage 2/3, so the symbols.json edit is required together with the source edit.
Helpers: `_object_get_bounding_sphere` differs from January's selected action_vehicle copy only in the assert FILE string. Ours is "c:\halo\source\objects\objects.h"; January's is "..\objects\objects.h". January's own render_objects-owned _object_get_render_bounding_sphere also uses "..\", so January's render_objects copy was identical to the selected one. A raw scan of cachebeta.exe finds zero code references to the c:\ string (an orphan attributed to object_lights), and 4 references to "..\objects\objects.h" (action_vehicle and render_objects).
Lab B2 (objects.h "..\" + action_vehicle without its hand copy): 0 function changes across all 9 users; action_vehicle 17/17 and object_audit PASS; both copies identical; render_objects + action_vehicle link PASS in both orders. However, object_lights then loses its January-owned 33 B .rdata string ??_C@_0CB@NDAECIIM (object_audit FAIL).
`_plane3d_negate` is byte-identical to January's decals copy, but decals holds a NODUP hand copy (decals.c:896) with no call in decals.c. January's out-of-line references to it come only from collisions, bipeds and units. `_plane3d_distance_to_point` links PASS.
## blockers
(1) `_object_get_bounding_sphere`, provider source/ai/action_vehicle: a NODUP hand copy (action_vehicle.c:168, with its rename at :82), plus the objects.h hardcoded assert string. A single header string cannot serve both object_lights, which needs the c:\ string for its January-owned .rdata, and render_objects/action_vehicle, which need "..\". It needs an owner ruling on per-TU __FILE__ modelling. (2) `_plane3d_negate`, provider source/effects/decals: a NODUP hand copy with no genuine use in decals.c, so the link fails with LNK2005 in both orders.
## reopen_criteria
Two conditions. First, the owner rules on how objects.h assert strings vary per TU (a TU in the header's own directory sees c:\halo\source\<dir>\<h>, other directories see ..\<dir>\<h>), and object_lights keeps its string while action_vehicle's object_get_bounding_sphere hand copy is replaced by the SELECT_ANY header inline (patches verified in lab). Second, the decals provider emits plane3d_negate SELECT_ANY from a genuine January use. Then apply B1, regenerate the split, rerun object_audit, provider_link and surplus_identity, and admit.
## task notes
Tools written under scratch/w/path_audit/tools/: gate_lab.py (a copy of gate.py whose main file compiles from scratch/w/path_audit/lab/ so lab/<dir>/<header> shadows a shared header; lab only), sweep.py and sweep2.py (per-function and per-section board sweeps of base vs lab, with controls), symdis.py, strrefs.py, strcensus.py, symcensus.py, linkpair.py (VC7 pair link in both orders), mkvariant.py, mk_objects_variant.py, object_audit_split.py / pdb_storage_split.py (run against a scratch split via the AUDIT_SPLIT env var).
Caveat for the integrator: in the lab, rasterizer_frame_statistics _rasterizer_frame_statistics_draw flips EXACT/residual even with an UNCHANGED header copy. This is an environment artifact (main-file path/PID), so ignore it in lab sweeps.
Useful follow-ups from the D1 sweep: players _player_set_action_result gets closer (16 -> 6 diff lines; its three `real_point3d const *` pointer locals look like the same M8 steer). Pre-existing pdb_storage disagreements outside this lane: action_vehicle _action_vehicle_evaluate_seat; object_lights _cluster_get_first_light, _cluster_get_next_light, _light_attenuation, _should_render_lights.
Line endings: all touched source files are CRLF, and GNU patch converted a test file to LF, so hand-apply the edits or copy the lab candidate files.
The lab shadow headers were restored to their original copies. The lab/objects shadow is active with an unchanged objects.h; lab/math/real_math.h is .orig.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/path_audit/LEDGER.md
@@ -0,0 +1,83 @@
# physics/breakable_surfaces
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\breakable_surfaces.c.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\b\\breakable_surfaces.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\config.json.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\symbols.json.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\LEDGER.md"
]
## production_changes
These three fixes are admissible and were verified in the lab. They do not close the object on their own.
(1) Apply patch scratch/w/breakable_surfaces_audit/patches/breakable_surfaces.c.patch (post-image patches/b/breakable_surfaces.c, CRLF preserved). It deletes the TU-local static __inline breakable_surface_get_plane_from_designator, which is a hand copy of bsp3d.h's helper and breaks rule 6. The one call site becomes the genuine bsp3d_get_plane_from_designator(&collision_bsp->bsp3d, surface->plane_designator, &surface_plane).
(2) Apply patches/config.json.patch. It removes the per-TU options override "/Ow","/QIfist" from the source/physics/breakable_surfaces.c entry (config.json line 520), leaving default flags (/O2 /Oy-). Needs configure.py plus ninja.
(3) In config/symbols.json, add , "static": true in place to two lines. Line 5362 is { "file_offset": 1266464, "flags": 32, "name": "_breakable_surface_effect" }. Line 23111 is { "file_offset": 4550316, "flags": 0, "name": "_globals" }. Then regenerate the split.
Do not set metadata.complete. Hand the cross-cutting provider list below to the provider-repair worker.
## evidence
Production baseline:
- gate 12/12 EXACT.
- object_audit: all 26 January sections are ok. Two symbols differ in storage (split 2, ours 3): _breakable_surface_effect and _globals. pdb_storage finds neither is a PDB public, so January had them static and our source is right; the split is wrong.
/Ow flag:
- helper_census: breakable is the only board object whose _cross_product3d (46 others identical), _vector_from_points3d (75), _plane3d_negate (11), _plane3d_from_point_and_normal (6) and _project_point3d (7) copies differ from January's selected copy.
- The override came from commit 369b71e7 (2026-07-01) with no January evidence. Memory compiler-settings-retired already lists the /QIfist override as dead config.
- lab_gate --drop /Ow --drop /QIfist keeps 12/12 EXACT, and those 5 COMDATs become IDENTICAL to January's selected copies.
Designator helper (T1):
- The /Od build confirms the genuine helper: breakable_surface_effect at 0x7a0ca0 calls designator helper 0x5666f0 (planes +0xc, &LONG_MAX, plane3d_negate).
- January's selected _bsp3d_get_plane_from_designator is in the decals range at 556208.
- The January text for this object is contiguous (1265728..1271184 is exactly the 12 functions), so January had no out-of-line static helper.
- cand_T1 stays 12/12 EXACT under both flag sets. With default flags its new _bsp3d_get_plane_from_designator COMDAT is IDENTICAL to January's decals copy, provider_link PASSes and nodup_census finds no NODUP definer.
Split check:
- A scratch symbols.json with the 2 static lines, regenerated by csplit, changes only breakable_surfaces.obj across all 833 objects.
- audit2 then PASSes with 26/26 symbols. Final combined check against split_hg0: 12/12, audit PASS, data surplus all identical.
Static plane-distance helper (T2):
- Replacing the TU-local breakable_surface_plane_distance with real_math.h plane3d_distance_to_point (the helper the /Od build calls 4 times at 0x56d580) turns the effect into a sha residual. The cause is 4 sites where January adds the j+k terms before the i term.
- The same TU-local association lever sits in the admitted Matching bsp3d (_bsp3d_plane_distance_to_point, ledger B24/B25). static_surplus_census finds 3 Matching units carrying candidate-only static code (bsp3d, structure_visibility, texture_page).
fake_match_scan: 0 leads. There are no parks and no rejection entries for this unit.
## blockers
(a) Seven selected-provider link FAILs (LNK2005 in both orders), all NODUP hand copies in January's selected providers, confirmed by nodup_census:
- effects/decals: _plane3d_from_point_and_normal, _plane3d_negate, _project_point2d, _projection_sign_from_vector3d
- ai/path_obstacles: _point_in_sphere, _project_point3d
- effects/effects: _real_local_random
(b) Two helper bodies differ from January's selected copy even with default flags, which are header or provider defects:
- _project_point2d: the real_math.h body (hash 4cd22885) differs from January's and our decals copy (50a54b92).
- _real_local_random: the header body (50ca5c49) differs from January's and our effects copy (22c6e67e).
(c) The static surplus _breakable_surface_plane_distance (32 B) is a TU-local regrouped copy of plane3d_distance_to_point. January's image has no such static, and the genuine helper costs _breakable_surface_effect its exactness. The bsp3d precedent may cover it, but the owner must confirm it is acceptable for whole-object admission.
## reopen_criteria
Reopen after the provider worker replaces the decals, path_obstacles and effects NODUP hand copies with genuine header uses (or authentic ordinary providers) that link in both orders, and fixes the real_math.h bodies of project_point2d and real_local_random so they are byte-identical to January's selected copies. The owner must also accept or reject the TU-local plane-distance association helper (bsp3d precedent). Then re-run provider_link and surplus_obj on the T1/default-flags object.
## task notes
Tooling note: in Git Bash, run lab_gate --drop with MSYS_NO_PATHCONV=1, otherwise '/Ow' is rewritten into a path and silently ignored.
My scratch tools, all under scratch/w/breakable_surfaces_audit/:
- lab_gate.py: gate.py plus --drop, --split-root and --tmpdir. A --tmpdir header shadow lets headers be edited in the lab.
- audit2.py, surplus_obj.py and surplus_data.py: honor AUDIT_SPLIT_ROOT.
- objeq.py, helper_census.py and static_surplus_census.py.
Combined scratch config and split:
- config_hg0/symbols.json holds every proposed symbols.json edit for all 4 units. patches/symbols.json.patch is its diff against config/symbols.json. The file was edited in place, with no re-serialization.
- csplit into split_hg0 changes only the 4 audited objects out of 833.
Findings for the provider worker:
- NODUP providers: effects/decals (_plane3d_from_point_and_normal, _plane3d_negate, _project_point2d, _projection_sign_from_vector3d); ai/path_obstacles (_point_in_sphere, _project_point3d); effects/effects (_real_local_random).
- Header-body mismatches with January's selected copies: _project_point2d (real_math.h body vs the decals copy) and _real_local_random (header vs the effects copy).
The stock D3D SDK select-any tables (_D3DPRIMITIVETOVERTEXCOUNT, _D3DSIMPLERENDERSTATEENCODE, _D3DTEXTUREDIRECTENCODE) emitted by collision_debug, rasterizer_xbox and hardware_geometry are byte-identical to January's selected copies and pair-link. This matches the 54 Matching objects that already emit them.
Policy question for the owner: three already-Matching units carry candidate-only static code that January's image lacks: bsp3d (_bsp3d_plane_distance_to_point, _bsp3d_polygon_plane_distance_to_point), structure_visibility (_structure_visibility_dequantize_byte_to_real) and texture_page (_texture_page_verify_and_return). This is the same class as breakable's _breakable_surface_plane_distance.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\breakable_surfaces_audit\LEDGER.md
@@ -0,0 +1,65 @@
# physics/collision_debug
## verdict
OBJECT_COMPLETE_CANDIDATE
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\symbols.json.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\LEDGER.md"
]
## production_changes
No source change is needed. In config/symbols.json line 19561, change { "file_offset": 2667176, "flags": 0, "name": "_collision_debug_cube_vertices" } to { "file_offset": 2667176, "flags": 0, "name": "_collision_debug_cube_vertices", "static": true }. Edit the line in place and do not re-serialize the file. This row is included in patches/symbols.json.patch.
Then regenerate the split and run the full ninja build and stable sweep. Mark source/physics/collision_debug.c as Matching / metadata.complete only if that run matches the scratch result.
State in the admission record that the object emits the stock D3D SDK select-any tables _D3DPRIMITIVETOVERTEXCOUNT (88), _D3DSIMPLERENDERSTATEENCODE (328) and _D3DTEXTUREDIRECTENCODE (16). All three are identical to January's selected copies (rasterizer_xbox_decals, progress_bar, headers) and pair-link, which follows the precedent of the 54 already-Matching objects that emit them.
## evidence
Production baseline:
- gate 1/1 EXACT (_collision_debug_render 4192).
- object_audit: all 17 January sections are ok, including .bss 28962 and .rdata cube vertices 96. Only _collision_debug_cube_vertices differs, in storage (split 2, ours 3).
- pdb_storage: it is not a PDB public, and no January split object has an UNDEF reference to it, so January had it file-static and our source is right.
Surplus:
- surplus_identity: 4 code COMDATs (_set_real_vector3d/actions, _point_from_line3d/action_charge, _scale_vector3d/action_charge, _add_vectors3d/actor_combat), all IDENTICAL.
- surplus_data.py: the 3 SDK tables and 11 literals are all IDENTICAL to January's selected copies.
- provider_link: 18/18 PASS.
- _point_from_line3d meets the rule-6 exception: byte-identical, caller strictly exact, link passes. The integrator's sweep still has to confirm zero regressions.
Split check:
- A scratch symbols.json with the static line, regenerated by csplit, changes only this object (plus the other audited objects in the combined config).
- audit2 PASSes with 29/29 symbols. Final combined check against split_hg0: 1/1, audit PASS, surplus 0 problems, link PASS.
Other checks:
- The spray arrays' names are descriptive; neither HCEX nor the PDB has names for them, which is allowed under house rules 7/8.
- fake_match_scan: 0 leads. There are no parks and no rejection entries.
## blockers
None beyond applying the symbols.json line and regenerating the split. The integrator must confirm the full-build sweep.
## reopen_criteria
Not applicable. If the integrator's full ninja or sweep shows any regression in the objects touched by the split regeneration, hold the admission and re-audit.
## task notes
Tooling note: in Git Bash, run lab_gate --drop with MSYS_NO_PATHCONV=1, otherwise '/Ow' is rewritten into a path and silently ignored.
My scratch tools, all under scratch/w/breakable_surfaces_audit/:
- lab_gate.py: gate.py plus --drop, --split-root and --tmpdir. A --tmpdir header shadow lets headers be edited in the lab.
- audit2.py, surplus_obj.py and surplus_data.py: honor AUDIT_SPLIT_ROOT.
- objeq.py, helper_census.py and static_surplus_census.py.
Combined scratch config and split:
- config_hg0/symbols.json holds every proposed symbols.json edit for all 4 units. patches/symbols.json.patch is its diff against config/symbols.json. The file was edited in place, with no re-serialization.
- csplit into split_hg0 changes only the 4 audited objects out of 833.
Findings for the provider worker:
- NODUP providers: effects/decals (_plane3d_from_point_and_normal, _plane3d_negate, _project_point2d, _projection_sign_from_vector3d); ai/path_obstacles (_point_in_sphere, _project_point3d); effects/effects (_real_local_random).
- Header-body mismatches with January's selected copies: _project_point2d (real_math.h body vs the decals copy) and _real_local_random (header vs the effects copy).
The stock D3D SDK select-any tables (_D3DPRIMITIVETOVERTEXCOUNT, _D3DSIMPLERENDERSTATEENCODE, _D3DTEXTUREDIRECTENCODE) emitted by collision_debug, rasterizer_xbox and hardware_geometry are byte-identical to January's selected copies and pair-link. This matches the 54 Matching objects that already emit them.
Policy question for the owner: three already-Matching units carry candidate-only static code that January's image lacks: bsp3d (_bsp3d_plane_distance_to_point, _bsp3d_polygon_plane_distance_to_point), structure_visibility (_structure_visibility_dequantize_byte_to_real) and texture_page (_texture_page_verify_and_return). This is the same class as breakable's _breakable_surface_plane_distance.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\breakable_surfaces_audit\LEDGER.md
@@ -0,0 +1,100 @@
# rasterizer/xbox/rasterizer_xbox
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\rasterizer_xbox.c.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\b\\rasterizer_xbox.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\rasterizer_xbox_internal.h.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\symbols.json.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\mk_rx.py",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\mk_rx_config.py",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\lab\\a3b.c"
]
## production_changes
Every change below is admissible and was verified in the lab. They should be landed together, and the object still stays NonMatching until the provider is repaired.
(1) Apply patches/rasterizer_xbox.c.patch (post-image patches/b/rasterizer_xbox.c). It makes these source changes:
- Replaces the invented aggregate struct rasterizer_xbox_d3d_globals and its 23 '#define member rasterizer_xbox_d3d_globals.member' redirects with 23 separate file-static definitions initialized to 0, in the same declaration order.
- Makes d3d_palette and rasterizer_state_cache static.
- Splits framebuffer_blend_function_states[3][9] into three static const long[9] arrays, defined in the order source, destination, operation: framebuffer_source_blend_states, framebuffer_destination_blend_states, framebuffer_blend_operation_states. Their 3 uses are updated and the now-unused _framebuffer_blend_state_* enum is removed.
- Makes rasterizer_filthy_bitmap_default_initialize static, with a static prototype in the .c.
- Replaces IDirect3DDevice8_SetFlickerFilter(global_d3d_device, RASTERIZER_FLICKER_FILTER_LEVEL) and IDirect3DDevice8_SetSoftDisplayFilter(global_d3d_device, FALSE) with D3DDevice_SetFlickerFilter(RASTERIZER_FLICKER_FILTER_LEVEL) and D3DDevice_SetSoftDisplayFilter(FALSE).
(2) Apply patches/rasterizer_xbox_internal.h.patch, which removes the rasterizer_filthy_bitmap_default_initialize prototype.
(3) Apply the rasterizer_xbox rows of patches/symbols.json.patch. They add "static": true to the 20 XDK wrapper lines, to _rasterizer_filthy_bitmap_default_initialize (1336912), _d3d_palette (4581588) and _rasterizer_state_cache (3198700). The wrapper lines are:
- 5495-5497: _Direct3D_Release@0, _IDirect3D8_Release@4, _IDirect3D8_CreateDevice@28
- 5502-5507: _D3DDevice_SetRenderState, _D3DDevice_SetTextureStageState, _IDirect3DDevice8_Release@4, _GetDeviceCaps@8, _Present@20, _GetBackBuffer@16
- 5512: _SetRenderTarget@12
- 5517-5518: _Clear@28, _SetViewport@8
- 5520: _SetRenderState@12
- 5525: _SetTextureStageState@16
- 5533-5535: _SetVertexData2s@16, _Begin@8, _End@4
- 5568: _IDirect3DSurface8_Release@4
- 5570-5571: _GetDesc@8, _LockRect@16
The same patch replaces line 19673 (_framebuffer_blend_function_states) with three static lines at 2670508, 2670544 and 2670580. It replaces line 23134 (_rasterizer_xbox_d3d_globals) with 23 static member lines from 4579368 upward: _node_matrix_constants +0, _bitmap_dimensions_non_blocking +2112, _bitmap_dimensions +2116, _d3d +2120, _global_d3d_texture_render_primary +2124 ... _global_d3d_surface_render_primary_copy +2212. The generator is mk_rx_config.py and the result is config_rx/symbols.json.
Regenerate the split and run the full ninja build. Optionally, also update the file's top-of-file symbol-listing comment so it no longer lists the old names.
## evidence
Production baseline:
- gate 95/95 EXACT.
- object_audit FAIL(22): .rdata _framebuffer_blend_function_states has align 3/4, and 21 symbols differ in storage.
- pdb_storage finds 25 disagreements. Four of them are external in both the split and ours but are not PDB publics, and no split or base object references them: _d3d_palette, _rasterizer_state_cache, _rasterizer_xbox_d3d_globals and _rasterizer_filthy_bitmap_default_initialize.
Aggregate vs separate globals:
- The range 0x45E028..0x45E8D0 has no PDB public.
- January's IDirect3D* call-text strings name the globals bare (&global_d3d_texture_render_secondary, d3d, &d3d_palette, ...). By the aggregate-vs-scalar stringification oracle, January had separate globals and the aggregate is invented (compare the hs_globals_external and dynavobgeom rejections).
Blend-array alignment:
- contribs.json module 128 .rdata is 108 B with flags 0x40300040 (align 4).
- Lab lab/a2d.c vs a3b.c: a single long[3][9] gets align 8. Three long[9] arrays get align 4 and are laid out in reverse definition order, which gives src+0, dst+36, op+72 and matches January's addends +0/+36/+72.
Wrapper copies and call spelling:
- January module 128 has exactly 95 code contributions, all named, and no SetFlickerFilter or SetSoftDisplayFilter wrapper copy. D3D8.h:1694-1695 shows those wrappers are pure forwards.
Candidate results:
- cand_rx_A, cand_rx_B and cand_rx_C are all 95/95 EXACT against split_rx.
- audit2 PASSes with 216/216 symbols, including the .rdata alignment.
- For cand_rx_C: zero static surplus. _dot_product3d, _plane3d_distance_to_point and _plane3d_from_point_and_normal are IDENTICAL to January's selected copies. The data surplus (3 SDK tables and 4 literals) is identical.
- Storage re-check on candidate and split: 0 PDB disagreements.
Header blast radius:
- All 7 other includers of rasterizer_xbox_internal.h were compiled with a shadow header with and without the prototype. A control #error header proved the shadow was used. objeq.py shows every section and symbol table IDENTICAL.
Naming checks:
- The atlas names 0x146650 _rasterizer_filthy_bitmap_defaults_initialize (plural). January's own string reads 'rasterizer_filthy_bitmap_default_initialize failed', so the singular name is kept.
- SetupSmartStates is a PDB public.
fake_match_scan: 0 leads.
## blockers
(a) Selected-provider link FAIL(1): _plane3d_from_point_and_normal is a NODUP hand copy in the January-selected provider effects/decals and gives LNK2005 in both orders. The same failure exists on the production object.
(b) Owner naming decision (not a byte blocker): the three blend-array names are descriptive, since no PDB, HCEX, atlas or string name exists. The 9 member names that were already in production and are not attested by strings are also descriptive: node_matrix_constants, bitmap_dimensions_non_blocking, bitmap_dimensions, global_d3d_texture_render_primary, global_d3d_surface_render_primary, global_d3d_texture_render_secondary_z, global_d3d_surface_render_secondary_z, global_d3d_texture_render_primary_copy and global_d3d_surface_render_primary_copy.
## reopen_criteria
Reopen when the provider worker repairs the decals NODUP _plane3d_from_point_and_normal (genuine header use, or an authentic provider that links in both orders and is byte-identical). Then apply the three patches plus the symbols.json rows, regenerate the split, run full ninja and the stable sweep, and re-run object_audit, pdb_storage, surplus_identity and provider_link. Expect PASS on every check, given the lab result.
## task notes
Tooling note: in Git Bash, run lab_gate --drop with MSYS_NO_PATHCONV=1, otherwise '/Ow' is rewritten into a path and silently ignored.
My scratch tools, all under scratch/w/breakable_surfaces_audit/:
- lab_gate.py: gate.py plus --drop, --split-root and --tmpdir. A --tmpdir header shadow lets headers be edited in the lab.
- audit2.py, surplus_obj.py and surplus_data.py: honor AUDIT_SPLIT_ROOT.
- objeq.py, helper_census.py and static_surplus_census.py.
Combined scratch config and split:
- config_hg0/symbols.json holds every proposed symbols.json edit for all 4 units. patches/symbols.json.patch is its diff against config/symbols.json. The file was edited in place, with no re-serialization.
- csplit into split_hg0 changes only the 4 audited objects out of 833.
Findings for the provider worker:
- NODUP providers: effects/decals (_plane3d_from_point_and_normal, _plane3d_negate, _project_point2d, _projection_sign_from_vector3d); ai/path_obstacles (_point_in_sphere, _project_point3d); effects/effects (_real_local_random).
- Header-body mismatches with January's selected copies: _project_point2d (real_math.h body vs the decals copy) and _real_local_random (header vs the effects copy).
The stock D3D SDK select-any tables (_D3DPRIMITIVETOVERTEXCOUNT, _D3DSIMPLERENDERSTATEENCODE, _D3DTEXTUREDIRECTENCODE) emitted by collision_debug, rasterizer_xbox and hardware_geometry are byte-identical to January's selected copies and pair-link. This matches the 54 Matching objects that already emit them.
Policy question for the owner: three already-Matching units carry candidate-only static code that January's image lacks: bsp3d (_bsp3d_plane_distance_to_point, _bsp3d_polygon_plane_distance_to_point), structure_visibility (_structure_visibility_dequantize_byte_to_real) and texture_page (_texture_page_verify_and_return). This is the same class as breakable's _breakable_surface_plane_distance.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\breakable_surfaces_audit\LEDGER.md
@@ -0,0 +1,77 @@
# rasterizer/xbox/rasterizer_xbox_hardware_geometry
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\rasterizer_xbox_hardware_geometry.c.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\b\\rasterizer_xbox_hardware_geometry.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\rasterizer_xbox_hardware_geometry.HYPOTHESIS_hg1.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\symbols.json.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\patches\\symbols.HYPOTHESIS_hg1_on_top.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\breakable_surfaces_audit\\mk_hg.py"
]
## production_changes
The partial fix below (hg0) is admissible and verified.
(1) Apply patches/rasterizer_xbox_hardware_geometry.c.patch (post-image patches/b/rasterizer_xbox_hardware_geometry.c). It deletes the four hand-written empty stubs code_00158470, code_001584b0, code_001584d0 and code_00158510. The real XDK wrappers already emitted by the existing IDirect3DVertexBuffer8_Unlock and IDirect3DIndexBuffer8_Unlock calls take their place.
(2) Apply the hardware_geometry rows of patches/symbols.json.patch.
- Add "static": true to the 7 wrapper lines at file_offsets 1410064 (_IDirect3DDevice8_CreateVertexBuffer@24), 1410096 (_IDirect3DDevice8_CreateIndexBuffer@24), 1410176 (_IDirect3DVertexBuffer8_Release@4), 1410192 (_IDirect3DVertexBuffer8_Lock@20), 1410240 (_D3DIndexBuffer_Lock@20), 1410272 (_IDirect3DIndexBuffer8_Release@4) and 1410288 (_IDirect3DIndexBuffer8_Lock@20).
- Rename and make static: 1410160 _code_00158470@4 to _D3DVertexBuffer_Unlock@4; 1410224 _code_001584b0@4 to _IDirect3DVertexBuffer8_Unlock@4; 1410256 _code_001584d0@4 to _D3DIndexBuffer_Unlock@4; 1410320 _code_00158510@4 to _IDirect3DIndexBuffer8_Unlock@4.
Regenerate the split. Do not mark the object complete: two placeholder stubs remain.
A hypothesis variant exists but is NOT for landing without owner approval: patches/rasterizer_xbox_hardware_geometry.HYPOTHESIS_hg1.patch plus patches/symbols.HYPOTHESIS_hg1_on_top.patch.
## evidence
Production baseline:
- gate 17/17 EXACT.
- object_audit FAIL(7): 7 XDK wrappers are external in the split but static in ours, and they are not PDB publics.
- pdb_storage also flags 6 placeholders, _code_00158450@8/460@8/470@4/4b0@4/4d0@4/510@4, as not public.
What the placeholders are:
- They are hand-written empty __stdcall stubs in the .c, called by nothing.
- Our object also emits the real _D3DVertexBuffer_Unlock@4, _IDirect3DVertexBuffer8_Unlock@4, _D3DIndexBuffer_Unlock@4 and _IDirect3DIndexBuffer8_Unlock@4 as surplus statics.
- Bytes plus header-definition emission order map them 1:1 onto the placeholders. For example 470 is 'c2 04 00' (D3DVertexBuffer_Unlock, D3D8.h:2243) and 4b0 is 'xor eax,eax; ret 4' (IDirect3DVertexBuffer8_Unlock, 2257); 4d0 and 510 are the index-buffer pair.
- Other objects already name these wrappers static (symbols.json 5691-5699).
- The w2 ledger's claim that January emits no Unlock COMDAT is wrong.
Lab results:
- hg0 against split_hg0 (only the 4 audited objects change): 17/17 EXACT, audit2 PASS with 25/25 symbols, no code surplus, data surplus identical (SDK tables and 3 literals), provider_link PASS.
- hg1 (the 2 @8 stubs deleted and one D3DVertexBuffer_MoveResourceMemory(d3d_vertex_buffer, D3DMEM_VIDEO) added): also 17/17 EXACT, audit PASS, zero surplus.
Other checks:
- The preserved CreateVertexBuffer failure-path BUG comment was owner-reconciled in 6130661c (Astra).
- fake_match_scan: 0 leads.
## blockers
Two January-owned 16-byte 'ret 8' statics are still matched only by hand-written empty stubs with placeholder names: _code_00158450@8 and _code_00158460@8. This breaks rule 7 and uses hand-written substitutes for compiler-emitted XDK inlines.
- Header emission order puts them between IDirect3DDevice8_CreateIndexBuffer (D3D8.h:1702) and D3DVertexBuffer_Unlock (2243).
- Several no-op pairs produce identical bytes: D3DResource_MoveResourceMemory@8 (2015) together with any of the *_MoveResourceMemory wrappers (2028-2235), or D3DTexture_UnlockRect@8 / D3DVolumeTexture_UnlockBox@8.
- No PDB, HCEX, atlas, /Od or string evidence names them, or says where January called them.
## reopen_criteria
Reopen on first-party evidence that names the two 'ret 8' no-op statics and shows the January source call that emitted them. That could be an atlas or map record at January offsets 0x158450/0x158460 (+0x400000), a later build's rasterizer_vertex_buffer_new call graph, or an XDK 3911 header difference. Alternatively, the owner may explicitly accept the hg1 MoveResourceMemory hypothesis. Then apply hg0 plus the chosen pair and regenerate the split.
## task notes
Tooling note: in Git Bash, run lab_gate --drop with MSYS_NO_PATHCONV=1, otherwise '/Ow' is rewritten into a path and silently ignored.
My scratch tools, all under scratch/w/breakable_surfaces_audit/:
- lab_gate.py: gate.py plus --drop, --split-root and --tmpdir. A --tmpdir header shadow lets headers be edited in the lab.
- audit2.py, surplus_obj.py and surplus_data.py: honor AUDIT_SPLIT_ROOT.
- objeq.py, helper_census.py and static_surplus_census.py.
Combined scratch config and split:
- config_hg0/symbols.json holds every proposed symbols.json edit for all 4 units. patches/symbols.json.patch is its diff against config/symbols.json. The file was edited in place, with no re-serialization.
- csplit into split_hg0 changes only the 4 audited objects out of 833.
Findings for the provider worker:
- NODUP providers: effects/decals (_plane3d_from_point_and_normal, _plane3d_negate, _project_point2d, _projection_sign_from_vector3d); ai/path_obstacles (_point_in_sphere, _project_point3d); effects/effects (_real_local_random).
- Header-body mismatches with January's selected copies: _project_point2d (real_math.h body vs the decals copy) and _real_local_random (header vs the effects copy).
The stock D3D SDK select-any tables (_D3DPRIMITIVETOVERTEXCOUNT, _D3DSIMPLERENDERSTATEENCODE, _D3DTEXTUREDIRECTENCODE) emitted by collision_debug, rasterizer_xbox and hardware_geometry are byte-identical to January's selected copies and pair-link. This matches the 54 Matching objects that already emit them.
Policy question for the owner: three already-Matching units carry candidate-only static code that January's image lacks: bsp3d (_bsp3d_plane_distance_to_point, _bsp3d_polygon_plane_distance_to_point), structure_visibility (_structure_visibility_dequantize_byte_to_real) and texture_page (_texture_page_verify_and_return). This is the same class as breakable's _breakable_surface_plane_distance.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\breakable_surfaces_audit\LEDGER.md
@@ -0,0 +1,67 @@
# source/ai/path_structure_bsp
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/pathbsp/cand_wh.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/patches/path_structure_bsp.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/fix_pathbsp.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/pathbsp/cand_hw.c"
]
## production_changes
Case B, a real reconstruction. It needs no semantic entry because ordinary objdiff credit follows.
(1) git apply scratch/w/data_gap/patches/path_structure_bsp.patch. In source/ai/path_structure_bsp.c it adds, under /* ---------- globals */ and in this order:
static real const quantized_pathfinding_surface_widths[8] = { 0.2f, 0.4f, 0.6f, 0.8f, 1.0f, 1.5f, 2.0f, 4.0f };
static real const quantized_pathfinding_surface_heights[8] = { 0.25f, 0.5f, 0.75f, 1.0f, 1.5f, 2.0f, 4.0f, 8.0f };
It also updates the header symbol comment.
(2) Edit config/symbols.json by line surgery, keeping CRLF and line order:
- line 13113: rename to { "file_offset": 2419724, "flags": 0, "name": "_quantized_pathfinding_surface_widths", "static": true },
- insert directly after it: { "file_offset": 2419756, "flags": 0, "name": "_quantized_pathfinding_surface_heights", "static": true },
These edits are included in patches/symbols_json_all.patch.
(3) Do NOT flip the status to Matching. The pre-existing NODUP provider conflicts below remain.
## evidence
contribs.json shows that module 426 (path_structure_bsp) owns a non-COMDAT 64-byte .rdata at file offset 2419724 with flags 0x40300040. Its bytes are the floats {0.2 .. 4} followed by {0.25 .. 8}.
No January object refers to it, going by a resolved-address scan of all 833 split objects. cachebeta.pdb does not list it as a public.
HCEX.pdb's compiland lists two File Static const float[8] arrays: quantized_pathfinding_surface_widths at 0xC5494 and quantized_pathfinding_surface_heights at 0xC54B4. HCEX_debug.exe holds the big-endian {0.2..4} at exactly 0xC5494 and {0.25..8} at 0xC54B4, which ties each name to its values by address.
Results:
- gate: 6/6 EXACT.
- object_audit against a scratch csplit (only this split object changes): PASS, 13/13 symbols, 64-byte section equal including flags.
- objdiff 3.3.1 mini report: .rdata goes from 70.14% to 100%, and matched_data from None to 200 (+200 B). Code stays 2512/2512.
- Control: defining heights first swaps the halves (DIFF). This non-COMDAT const .rdata follows forward definition order.
The codex salvage ledger withdrew an earlier single 64-byte array because it came out 8-aligned and only __declspec(align(4)) fixed that. The two authentic 32-byte arrays are 4-aligned naturally, so no annotation is needed.
fake_match_scan finds 0 leads, and compiler warnings are unchanged.
## blockers
Whole-object admission is blocked by pre-existing surplus pair-link failures. The 10 helper COMDATs are all identical to January's selected copies, but provider_link fails for _cross_product2d (actor_combat NODUP hand copy) and _distance_squared2d (action_vehicle NODUP hand copy). This is the same in the production base.
## reopen_criteria
Flip to Matching after the NODUP providers are genuinely repaired and provider_link passes in both orders.
## task notes
Expected credit: +1,973 data bytes.
- +1,773 comes from the three Case A single-section entries (periodic 96, king 136, object_lights 1,541); scratch/tools/progress_calc.py takes data from 2,579,142 to 2,580,915, with 38 credits and 0 revoked.
- +200 is path_structure_bsp's ordinary objdiff credit after the Case B reconstruction.
- Code is unchanged and there are no function regressions.
The Case A entries and the ownership/source patches are independent. The entries verify against today's production objects, and I re-checked their snapshots under the proposed symbols/storage changes; they are unchanged.
Integrator order: apply the patches, then csplit plus ninja, then rerun the verifier and progress. The combined symbols.json edits (34 ops, scratch/w/data_gap/fix_all.txt) have been tested with a scratch csplit (cfg_all_split). They change exactly 4 split objects out of 833.
Tool caveat for other workers: tools/campaign/gate.py copies --source to scratch/_gate_PID.c, so a shadow header placed beside the candidate is silently ignored. scratch/w/data_gap/sgate.py compiles in place and honours shadow headers; I confirmed this with a #error test.
Code under-credit is already handled: objdiff scores periodic_function_build_table, transition_function_build_table, bitmap_copy and bitmap_draw_line below 100%, but all four are strict exact and already credited through build/semantic_report.json hidden_exact.
Systemic blocker found: 18 base objects emit a header-inline _distance3d whose bytes differ from January's selected action_vehicle copy. It also fails pair-link against action_vehicle's NODUP hand copy, and none of those objects is Matching. This alone stops king and object_lights from becoming complete objects.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/LEDGER.md
@@ -0,0 +1,53 @@
# source/bitmaps/bitmap_drawing
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/bitmap/cand_static.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/fix_bitmap.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/bitmap/lab_c10.c"
]
## production_changes
No data change.
Optional zero-credit ownership fix only: `static word const bitmap_bevel_sequence_indices[...]` (scratch/w/data_gap/bitmap/cand_static.c) plus , "static": true on symbols.json line 13458 (_bitmap_bevel_sequence_indices). Verified 7/7 exact, and its storage then agrees with cachebeta. The lab_*.c files are lab-only and must NOT be landed.
## evidence
January's .data is 2644 B and ours is 2628 B. contribs.json (module 392, size 2644, flags c0400040) proves the first 16 bytes belong to bitmap_drawing itself: 0xFF x10 followed by 0x00 x6, before the public _translation_table at +16.
Nothing ever references that prefix:
- all January split objects: zero references (resolved-address scan);
- the /Od build: zero, although the same 16 bytes sit at 0x9f0e00 in front of translation_table at 0x9f0e10, which is referenced;
- HEK sapien.exe: zero, with the same bytes at 0xA8CCB0.
The pattern is unique board-wide, so it is not a header static. It is not a public and must be an initialised file static.
Neither HCEX.pdb nor HCEX_Release.pdb has a bitmap_drawing module, and the atlas has only function names. The lab shows that a 10-byte char[], 5-short or 3-long prefix all give the identical layout, because VC7 16-aligns the 2592-byte table. So the type cannot be determined from bytes.
## blockers
The 2026-09-20 owner ruling holds every unnamed or unconfirmed global. Name and type of the 16-byte static are unattested, and inventing them would be fake extent-filling.
## reopen_criteria
A first-party source for the name and type of the initialised static that precedes translation_table: a PDB, source or HEK/PC symbol record for bitmap_drawing. Then define it in bitmap_drawing.c, re-split symbols.json at file_offset 2995832 with the real static name, and verify .data at 2644 B/c0400040 plus object_audit.
## task notes
Expected credit: +1,973 data bytes.
- +1,773 comes from the three Case A single-section entries (periodic 96, king 136, object_lights 1,541); scratch/tools/progress_calc.py takes data from 2,579,142 to 2,580,915, with 38 credits and 0 revoked.
- +200 is path_structure_bsp's ordinary objdiff credit after the Case B reconstruction.
- Code is unchanged and there are no function regressions.
The Case A entries and the ownership/source patches are independent. The entries verify against today's production objects, and I re-checked their snapshots under the proposed symbols/storage changes; they are unchanged.
Integrator order: apply the patches, then csplit plus ninja, then rerun the verifier and progress. The combined symbols.json edits (34 ops, scratch/w/data_gap/fix_all.txt) have been tested with a scratch csplit (cfg_all_split). They change exactly 4 split objects out of 833.
Tool caveat for other workers: tools/campaign/gate.py copies --source to scratch/_gate_PID.c, so a shadow header placed beside the candidate is silently ignored. scratch/w/data_gap/sgate.py compiles in place and honours shadow headers; I confirmed this with a #error test.
Code under-credit is already handled: objdiff scores periodic_function_build_table, transition_function_build_table, bitmap_copy and bitmap_draw_line below 100%, but all four are strict exact and already credited through build/semantic_report.json hidden_exact.
Systemic blocker found: 18 base objects emit a header-inline _distance3d whose bytes differ from January's selected action_vehicle copy. It also fails pair-link against action_vehicle's NODUP hand copy, and none of those objects is Matching. This alone stops king and object_lights from becoming complete objects.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/LEDGER.md
@@ -0,0 +1,67 @@
# source/game/game_engine_king
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/king/cand.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/king/game/game_engine_king.h",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/patches/king.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/fix_king.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/semantic_data_matches_additions.json"
]
## production_changes
(A) Case A credit, +136 B. Append the _king_engine entry from scratch/w/data_gap/semantic_data_matches_additions.json to config/semantic_data_matches.json:
unit source/game/game_engine_king
symbol _king_engine
allow_incomplete_unit true
measurements size 136, relocation_count 23, normalized_sha256 4009f5b6117e9f00cb001a23b9a56b9dc51a694206c442ce715e2bed2d5465a7
(B) Optional zero-credit ownership correction, matching the oddball/slayer precedent:
- git apply scratch/w/data_gap/patches/king.patch:
- the 22 engine-table functions become static;
- their prototypes move from game_engine_king.h into the .c prototype block as static prototypes;
- `static struct king_globals king_globals = { 0 };` replaces the public definition;
- `extern struct king_globals king_globals;` is removed from the header.
- Add , "static": true to symbols.json lines 2302, 2305-2312, 2314, 2316-2320, 2323-2326, 2328-2330 (the 22 functions) and 22989 (_king_globals). The exact names are in scratch/w/data_gap/fix_king.txt and the edits are in patches/symbols_json_all.patch.
(C) Keep the status NonMatching.
## evidence
The .data _king_engine section (136 B, 23 relocations) resolves identically under coff_compare, with equal flags c0400040. January's name relocation targets '??_C@_04PJOEONHN@king?$AA@', which is UNDEF in January and folded to game_engine. That triggers the objdiff 3.3.1 '$' defect, and the section scores 98.25%.
The code is 29/29 strict exact. The base object_audit shows PASS.
cachebeta publics lack the 22 table functions and _king_globals, and 0 UNDEF references exist anywhere. game_engine_king.h is included only by game_engine_king.c.
The ownership candidate compiles with the shadow header through sgate.py at 29/29 EXACT, and the only warning is the pre-existing C4700.
Against a scratch csplit with 23 static flags (only this object changes): object_audit PASS at 39/39 symbols, the _king_engine and _king_globals sections are equal, and PDB storage shows 0 disagreements.
The objdiff mini report is unchanged.
## blockers
The object cannot be Matching. The surplus _distance3d COMDAT is DIFFERENT from January's selected action_vehicle copy (x87 order), and provider_link fails with LNK2005 against the action_vehicle NODUP hand copy. This is pre-existing and board-wide: 18 objects are affected and none of them is Matching. The data entry therefore needs allow_incomplete_unit.
## reopen_criteria
Flip to Matching after distance3d's COMDAT equals January's selected copy, or the owner rules on the action_vehicle NODUP provider.
## task notes
Expected credit: +1,973 data bytes.
- +1,773 comes from the three Case A single-section entries (periodic 96, king 136, object_lights 1,541); scratch/tools/progress_calc.py takes data from 2,579,142 to 2,580,915, with 38 credits and 0 revoked.
- +200 is path_structure_bsp's ordinary objdiff credit after the Case B reconstruction.
- Code is unchanged and there are no function regressions.
The Case A entries and the ownership/source patches are independent. The entries verify against today's production objects, and I re-checked their snapshots under the proposed symbols/storage changes; they are unchanged.
Integrator order: apply the patches, then csplit plus ninja, then rerun the verifier and progress. The combined symbols.json edits (34 ops, scratch/w/data_gap/fix_all.txt) have been tested with a scratch csplit (cfg_all_split). They change exactly 4 split objects out of 833.
Tool caveat for other workers: tools/campaign/gate.py copies --source to scratch/_gate_PID.c, so a shadow header placed beside the candidate is silently ignored. scratch/w/data_gap/sgate.py compiles in place and honours shadow headers; I confirmed this with a #error test.
Code under-credit is already handled: objdiff scores periodic_function_build_table, transition_function_build_table, bitmap_copy and bitmap_draw_line below 100%, but all four are strict exact and already credited through build/semantic_report.json hidden_exact.
Systemic blocker found: 18 base objects emit a header-inline _distance3d whose bytes differ from January's selected action_vehicle copy. It also fails pair-link against action_vehicle's NODUP hand copy, and none of those objects is Matching. This alone stops king and object_lights from becoming complete objects.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/LEDGER.md
@@ -0,0 +1,77 @@
# source/math/periodic_functions
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/periodic/cand_c.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/patches/periodic_functions.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/fix_periodic2.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/periodic/cand_d.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/semantic_data_matches_additions.json"
]
## production_changes
(A) Case A credit, +96 B. Append the _global_periodic_functions_enum_strings entry from scratch/w/data_gap/semantic_data_matches_additions.json to config/semantic_data_matches.json:
unit source/math/periodic_functions
symbol _global_periodic_functions_enum_strings
allow_incomplete_unit true
measurements size 96, relocation_count 20, normalized_sha256 8fdd4bf9e8be002fe8938ee0849dfec244c27138f7b77eff28ec8ba7b4538614
(B) Zero-credit authenticity/ownership fix, verified:
- git apply scratch/w/data_gap/patches/periodic_functions.patch. It:
- deletes the invented struct periodic_functions_globals (with byte pad[3]) and the `#define function_tables_initialized periodic_functions_globals.function_tables_initialized` alias;
- adds `static boolean function_tables_initialized = FALSE; static byte *transition_function_tables[NUMBER_OF_TRANSITION_FUNCTIONS] = { 0 }; static byte *periodic_function_tables[NUMBER_OF_PERIODIC_FUNCTIONS] = { 0 };` in that order;
- makes periodic_function_build_table static and adds its static prototype.
- symbols.json edits:
- line 23079 becomes { "file_offset": 4547004, "flags": 0, "name": "_function_tables_initialized", "static": true },
- insert after it { "file_offset": 4547008, "flags": 0, "name": "_transition_function_tables", "static": true },
- then { "file_offset": 4547032, "flags": 0, "name": "_periodic_function_tables", "static": true },
- add , "static": true to _periodic_function_build_table.
- All of these are in patches/symbols_json_all.patch.
(C) The status must stay NonMatching because of the park.
## evidence
The .data section (96 B, 20 relocations) resolves identically, with flags c0300040 on both sides. The 'noise' and 'linear' literals are UNDEF in January (folded to other objects), and every relocation targets a '?$AA' literal, so objdiff 3.3.1 scores the section 70.83%.
The unit's six unparked functions are strict exact.
Whole-object audit finding: our .bss is 8-aligned and January's is 4-aligned. VC7 8-aligns a section only when it holds an object of 64 B or more, and our invented 76-byte struct triggers that.
What January actually had:
- The assert text is the bare '!function_tables_initialized'.
- HCEX.pdb lists File Statics periodic_function_tables (unsigned char *[12]) and transition_function_tables (unsigned char *[6]).
- HCEX also lists `static void periodic_function_build_table(short, unsigned char *)`, and cachebeta does not list it as a public.
Measured results:
- cand_c against a scratch csplit (only this object changes): 6/6 exact, .bss flags equal (align 4/4), 46/46 January symbols, 0 PDB storage disagreements.
- The only remaining difference is the parked function, whose bytes are unchanged (af8e65f5, the parked.json base).
- Control cand_d uses uninitialised statics and gets name-hash order with a 73-byte section, which is wrong. That proves January's `= 0` initialisers (declaration-order layout).
- _fast_ftol surplus is IDENTICAL to January's selected copy and provider_link PASSes.
- objdiff rows are unchanged.
## blockers
@periodic_function_build_variable_period_x_table@4 is parked (a two-instruction EDX/ECX scheduling tie), so the object can never be Matching until that park closes.
## reopen_criteria
Flip to Matching and drop allow_incomplete_unit only if the parked fastcall builder becomes strict exact under that park's own reopen criteria.
## task notes
Expected credit: +1,973 data bytes.
- +1,773 comes from the three Case A single-section entries (periodic 96, king 136, object_lights 1,541); scratch/tools/progress_calc.py takes data from 2,579,142 to 2,580,915, with 38 credits and 0 revoked.
- +200 is path_structure_bsp's ordinary objdiff credit after the Case B reconstruction.
- Code is unchanged and there are no function regressions.
The Case A entries and the ownership/source patches are independent. The entries verify against today's production objects, and I re-checked their snapshots under the proposed symbols/storage changes; they are unchanged.
Integrator order: apply the patches, then csplit plus ninja, then rerun the verifier and progress. The combined symbols.json edits (34 ops, scratch/w/data_gap/fix_all.txt) have been tested with a scratch csplit (cfg_all_split). They change exactly 4 split objects out of 833.
Tool caveat for other workers: tools/campaign/gate.py copies --source to scratch/_gate_PID.c, so a shadow header placed beside the candidate is silently ignored. scratch/w/data_gap/sgate.py compiles in place and honours shadow headers; I confirmed this with a #error test.
Code under-credit is already handled: objdiff scores periodic_function_build_table, transition_function_build_table, bitmap_copy and bitmap_draw_line below 100%, but all four are strict exact and already credited through build/semantic_report.json hidden_exact.
Systemic blocker found: 18 base objects emit a header-inline _distance3d whose bytes differ from January's selected action_vehicle copy. It also fails pair-link against action_vehicle's NODUP hand copy, and none of those objects is Matching. This alone stops king and object_lights from becoming complete objects.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/LEDGER.md
@@ -0,0 +1,76 @@
# source/objects/object_lights
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/lights/cand.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/lights/objects/object_lights_rendering.h",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/patches/object_lights.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/semantic_data_matches_additions.json",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/fix_lights.txt"
]
## production_changes
(A) Case A credit, +1,541 B. Append the _lights_section entry from scratch/w/data_gap/semantic_data_matches_additions.json to config/semantic_data_matches.json. It is a single-section entry:
unit source/objects/object_lights
symbol _lights_section
allow_incomplete_unit true
measurements size 1541, relocation_count 1, normalized_sha256 527b43a173f528444fa4423af92d7d58a83f2afe7fc1661b6d297401b2760f71
plus the reason text in the file.
(B) Optional zero-credit ownership correction, verified:
- git apply scratch/w/data_gap/patches/object_lights.patch:
- static on cluster_get_first_light, cluster_get_next_light, light_attenuation and should_render_lights;
- the should_render_lights prototype moves from object_lights_rendering.h into object_lights.c's prototype block;
- lights_render_diffuse and lights_render_specular now test `if (should_render_lights())` instead of the inline predicate.
- Add , "static": true to the symbols.json lines for _should_render_lights (5138), _light_attenuation (5150), _cluster_get_first_light (5152) and _cluster_get_next_light (5153). This is in patches/symbols_json_all.patch.
(C) Keep the status NonMatching.
## evidence
The .data section (1541 B, 1 relocation) resolves identically under coff_compare, with equal flags c0400040.
January's only relocation targets the '?$AA' literal 'render_lights'. January leaves it UNDEF (folded to another object) while we define it. That triggers the objdiff 3.3.1 '$' name defect, and the section scores 99.74%.
The code is 43/43 strict exact. object_audit on the base shows PASS, 71 symbols.
Ownership:
- cachebeta publics lack all four functions, and 0 UNDEF references exist.
- January emits should_render_lights with zero references, so it must be a static that is used inline.
- /Od build:
- lights_game_globals is at 0xded68c.
- The only reader of render_lights is should_render_lights at 0x78dab0.
- Its callers are 0x78c6c0 (lights_render_diffuse, which calls rasterizer_environment_diffuse_lights_begin and then should_render_lights) and 0x78c950 (lights_render_specular).
- Lab results:
- static without a call leaves should_render_lights UNWRITTEN (42/43).
- With the /Od call shape it is 43/43 EXACT and all four symbols are emitted static.
- render.c (13/13) and render_objects.c (22/22) are the only other consumers of the header. With the edited shadow header, every function row and every section is unchanged.
- Against a scratch csplit (only this object changes): object_audit PASS, 71/71 symbols, and the _lights_section snapshot is unchanged.
progress_calc: the three Case A entries together add +1,773 data with 0 revoked.
## blockers
The object cannot be marked Matching because of pre-existing surplus. The header-inline _distance3d and _object_get_bounding_sphere COMDATs differ from January's selected action_vehicle copies, and provider_link fails on both (LNK2005 against the NODUP hand copies). A census found 18 base objects emitting this non-identical _distance3d, and none of them is Matching. That is why the data entry needs allow_incomplete_unit.
## reopen_criteria
Drop allow_incomplete_unit and flip to Matching once real_math.h distance3d and object_get_bounding_sphere emit January's selected bytes (the action_vehicle x87 order), or the owner rules on the NODUP providers.
## task notes
Expected credit: +1,973 data bytes.
- +1,773 comes from the three Case A single-section entries (periodic 96, king 136, object_lights 1,541); scratch/tools/progress_calc.py takes data from 2,579,142 to 2,580,915, with 38 credits and 0 revoked.
- +200 is path_structure_bsp's ordinary objdiff credit after the Case B reconstruction.
- Code is unchanged and there are no function regressions.
The Case A entries and the ownership/source patches are independent. The entries verify against today's production objects, and I re-checked their snapshots under the proposed symbols/storage changes; they are unchanged.
Integrator order: apply the patches, then csplit plus ninja, then rerun the verifier and progress. The combined symbols.json edits (34 ops, scratch/w/data_gap/fix_all.txt) have been tested with a scratch csplit (cfg_all_split). They change exactly 4 split objects out of 833.
Tool caveat for other workers: tools/campaign/gate.py copies --source to scratch/_gate_PID.c, so a shadow header placed beside the candidate is silently ignored. scratch/w/data_gap/sgate.py compiles in place and honours shadow headers; I confirmed this with a #error test.
Code under-credit is already handled: objdiff scores periodic_function_build_table, transition_function_build_table, bitmap_copy and bitmap_draw_line below 100%, but all four are strict exact and already credited through build/semantic_report.json hidden_exact.
Systemic blocker found: 18 base objects emit a header-inline _distance3d whose bytes differ from January's selected action_vehicle copy. It also fails pair-link against action_vehicle's NODUP hand copy, and none of those objects is Matching. This alone stops king and object_lights from becoming complete objects.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/data_gap/LEDGER.md
@@ -0,0 +1,75 @@
# source/ai/action_charge
## verdict
OBJECT_COMPLETE_CANDIDATE
## newly_exact_functions
[
"_action_charge_perform"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\action_charge\\candidate.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\action_charge\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\action_charge\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\action_charge\\candidate_gate.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\action_charge\\candidate_audit.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\action_charge\\candidate_provider_link.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\action_charge\\melee_block.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\action_charge\\od_perform.txt"
]
## production_changes
1) Apply scratch/w/action_charge/production.patch to source/ai/action_charge.c. It is a unified diff, CRLF kept, and changes ONLY the body of action_charge_perform. `patch -p1 --binary` on a copy of production reproduces scratch/w/action_charge/candidate.c byte-for-byte. No header, symbols.json, parked.json, config flag or tool change. Helper copies are untouched, including the real_random rename package.
2) What the patch changes:
(a) Removes the `unit_index` and `target_range` locals, as in the /Od build.
(b) Four 2D aggregate copies `x = *(real_vector2d *)&v`: target_alignment_vector, the melee alignment_vector plus its facing_vector fallback, and state_data->leap_alignment_vector.
(c) Adds the /Od dead leap-arm code: RTC alignment_vector with the normalize2d fallback chain to *global_forward2d, and a nested block { boolean success = FALSE; real minimum_velocity; real_point3d target_point = prop->body_position; aim_vector/vertical/horizontal; target_point.z += 0.4f; success = projectile_aim_ballistic(...); if (success) {...} }.
(d) Adds named results closing_velocity (suicide arm) and alignment (dot_product2d).
(e) Uses the RTC names vector_to_target, estimated_target_position, target_relative_velocity, target_alignment_vector, alignment_vector and minimum_velocity, and /Od declaration order.
3) No park to retire: config/parked.json has no action_charge entry.
4) After the full ninja build, the stable sweep and owner acceptance of the review items (see blockers), flip config/config.json `source/ai/action_charge.c` (index 470) from NonMatching to Matching.
5) Add a matching-log doc disclosing the rule-24 casts per site, with the /Od addresses listed in LEDGER.md section 6.
## evidence
Baseline: `[size 3264!=3248, sha]`, 32 real regions. Candidate: gate `EXACT 3248 _action_charge_perform` and `== exact 22 residual 0 unwritten 0`. relocdiff 84/84 with 0 differing rows. object_audit PASS: 37 January symbols, 0 differ, surplus identical to production. provider_link SELECTED-PROVIDER LINK PASS. pdb_storage 0 disagreements. surplus_identity 2/2 IDENTICAL. Data 256/256 (100% in build/report.json). fake_match_scan 0 leads. /W3 clean. coffsecs against build/base: only the perform .text and .debug$S sections differ; every other section's bytes, flags and COMDAT selection are identical.
Mechanism:
- Lane B's owner_ruling_struct_copy body rebuilt on this tree (sc0) leaves one diff only: dot #2 term order at +0x8e1..+0x8f8 (January i,k,j; ours k,j,i).
- A dead-local instrument (diagnostic only) shows the key is a count of IL locals and that POSITION IS INERT: D=6/7 exact at function top, melee block, suicide arm or tail; D=0..5 not.
- The /Od build (fn 0x430170) has exactly 7 named slots that sc0 lacks: -0x3e, -0x88 (RTC alignment_vector), -0x8d, -0xac (RTC target_point), -0xf4, -0xf8, -0x110.
- The two RTC aggregates are dead code in the /Od build too. Measured: VC7 inlines and eliminates that dead block with ZERO byte trace (s4), so January's bytes cannot exclude it. Every /Od-attested exact set requires it.
- Constants were read from the /Od data: 0.4f and 0.3f. The fallback global 0xa31df4 = global_forward2d, derived from pointer-table offsets.
- Map of subsets that are exact (LEDGER section 3): at least 2 of {success, dead boolean, 3 named reals}, but not both 3D-dot reals. The full /Od set fails only on the fadd at +0x944.
Strip tests:
- Load-bearing casts: C1, C2 and C5 (local-to-local copies from vector_to_target). Every undecorated alternative is residual.
- Byte-inert casts: C3, C4 and C6 (member-wise copies are equally exact), each with the /Od single-base-load L,L,S,S struct-copy pattern.
- `= FALSE` on success is byte-inert and kept because /Od stores it at 0x430d83.
- Each named real alone is byte-inert; dropping both is residual.
- A cast-free control (production scalar copies with the same locals, D=0..7) stays at [size 3264] with 32 real regions and does not respond to the count, so the aggregate copies are required.
## blockers
Owner review is needed before admission:
(1) Rule-24 aggregate-copy casts.
- C3, C4 and C6 meet the 2026-09-21 per-site admission as it stands: byte-inert, /Od L,L,S,S at 0x431203 / 0x430f04 / 0x430d28, prefix layout.
- C1, C2 and C5 are load-bearing aggregate copies that cannot be strip-tested. This is the same class as the owner-admitted action_obey.c:1259 copy (commit 3ba2eb91).
(2) The /Od-attested dead leap-arm code (RTC alignment_vector/target_point) and the named result locals. They produce no bytes of their own and are proven only through the M8 IL-count key (the dot #2 term order). An earlier Astra review dismissed them on the premise 'absent from January bytes'; that premise is now measured false.
(3) Which exact subset of /Od locals January had cannot be decided from bytes. The alternatives are listed in LEDGER section 3.
Not a blocker for this object: nodup_census shows action_charge.obj is the only NODUP definer of `_real_random`. That is the provider worker's question, and it blocks objects.obj, not this object. Measured: deleting the real_random rename package keeps 22/22, with only a COMDAT selection change 1 to 2.
## reopen_criteria
If the owner rejects the dead-code or cast evidence, keep the current production body (fuzzy, zero credit) and hold candidate.c as the exact candidate. Reopen with an owner ruling on (i) the load-bearing aggregate-copy casts C1/C2/C5 and (ii) authentic /Od dead code / named locals attested only through the IL-count key. Do not re-run dead-local sweeps or declaration-order permutations. The count map is recorded in LEDGER sections 2 and 3.
## task notes
`_action_charge_perform` is now strict exact in scratch, and so is the whole object (22 of 22 functions). Nothing tracked was edited; ninja, configure and git were not run. The fix has three parts:
1. Lane B's 2D aggregate-copy body.
2. Authentic dead code and named result locals from the /Od build (halo_cache_symbols.exe).
3. The /Od (RTC) local names.
The key finding is that the compiler removes the /Od dead code without leaving a byte, but January's dot #2 term order proves those local declarations existed. Admission depends on the owner accepting that dead code and the aggregate-copy casts; see blockers.
Probe harness: scratch/w/action_charge/probe.py with v1.py to v5.py; outputs are in scratch/w/action_charge/p/.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\action_charge\LEDGER.md
@@ -0,0 +1,34 @@
# ai/actor_looking::_actor_look_idle_find_prop
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_actor_look_idle_find_prop"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\cand_final.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\cand_final.obj",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\c1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\LEDGER.md"
]
## production_changes
Apply C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_looking\production.patch to source/ai/actor_looking.c. It keeps the file's CRLF line endings, and `git apply --check` passes. The patch spells every `(real)cos(...)` / `(real)sin(...)` in the file through real_math.h's existing `cosine()` / `sine()` inline helpers, 15 sites in all: 4 in actor_looking_test_validity, 4 in actor_look_idle_find_prop, 4 in actor_look_find_random_vector (sine/cosine of pitch and yaw) and 5 in actor_look_update (4 look-delta cosines plus the stationary-facing cosine). In the stationary-facing block of actor_look_update, the local `real cosine` becomes `real stationary_cosine`, because a local named `cosine` would hide the helper; its 4 uses are renamed to match. No header, symbols.json, config, park or data change. After the patch the object newly emits `_cosine` and `_sine` COMDATs (16 B, 0 relocs each), the same systemic header-inline class the base build already emits in 31 objects. Both are section_infos_equal to January's selected provider (build/split actor_combat.obj), and provider_link.py passes in both orders for both. The _point_from_line3d guard passes. The integrator still needs to run the normal full build, stable-verdict sweep and full COMDAT ownership sweep. Rule 6 requires zero regressions from that sweep before new `_cosine`/`_sine` emissions can be admitted, and the reviewer should confirm those COMDATs are acceptable while `_actor_look_update` (also a caller of `cosine()` under this patch) stays non-exact. For the smallest possible diff, C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_looking\c1.c changes only find_prop's 4 sites. It gives the same exact result and the same surplus, but leaves the TU spelling cos two ways. No parks to retire: config/parked.json has no actor_looking entry.
## evidence
Before: `_actor_look_idle_find_prop` was a residual at 608 padded / 602 meaningful bytes with 20 relocs, and the object_audit reported 2 DIFF sections. January differs from ours at +0x8d, `cmp ebx,[_ai_debug+0x38]` against our `mov eax,[..]; cmp ebx,eax`, and has a near jg at +0xdf where ours is short. /Od evidence: find_prop is at 0x45dbd0 in halo_cache_symbols.exe. All four cosines there, and every cos/sin in the /Od TU (update 0x45f938..0x45f9c4 and 0x4611d3, find_random_vector 0x45d795..0x45d7dd), are calls to float->float wrappers. Cosine is 0x455220 and sine is 0x455320; each pushes a 4-byte float argument and returns in st0, and they chain to 0x4553b0 = (float)cos((double)x). So the first-party source spells cosine()/sine(), not (real)cos(). Mini-lab mechanism (scratch/w/actor_looking/mini): m11 reproduces our compare split. m13 shows January's fold in straight-line code, which rules out alias and operand-order explanations. m26 is m11 with a real_math-style `cosine()` and gives January's exact `fcos; cmp esi,[g]; fstp; jne`. Production gate on C1/C5/cand_final: `_actor_look_idle_find_prop` is EXACT, and the +0xdf jg width closes with the same change, confirming lane A's 'one root cause' hypothesis. Final gate: 15 exact, 1 residual, 0 unwritten. find_random_vector and test_validity stay exact under sine()/cosine(). object_audit on cand_final: FAIL(1), and the only DIFF is _actor_look_update (base was FAIL(2)); 80 January symbols, 0 differ. pdb_storage: 0 disagreements. Data is 100% (report.json 1700/1700). /W3 shows only the existing C4013 for game_time_get, which an earlier lane (150K w3) measured as byte-inert. Negatives recorded: name-count oracle K=0..40 at three anchor points is byte-inert for both residuals; the /Od-attested inverted state test and loop-top `valid = FALSE` are inert; nine lab probes (L1, L2, V1, V2, J6, J7, J8, K1, K3) pin the mechanism on the x87 value carried into the join block.
## blockers
The whole object is blocked only by _actor_look_update (see the next unit). find_prop itself has no remaining blocker.
## reopen_criteria
n/a for find_prop. It is strict exact once the patch lands; re-verify with the full ninja build and stable_verdicts after integration.
## task notes
New lever, useful beyond this object: spelling a transcendental through a real_math.h `__inline` helper such as `cosine()`, rather than calling `(real)cos()` directly, changes codegen even when the helper is fully inlined. With the direct call, IL-level tail-merging carries the x87 value into the join block, and a global load in that block then stays unfolded (`mov r,[g]; cmp reg,r`). The inline-helper form folds, matching January's `cmp reg,[g]`, and in this case it also fixed a distant jg width anomaly. Evidence source: /Od calls to the float wrapper at 0x455220. Other residuals that show January folding a load or compare right after an x87 phi join may be the same (real) cos/sin vs cosine/sine spelling issue; a census of `(real)cos(`/`(real)sin(` across non-exact functions is cheap. Tools written in the slug: score.py (gate plus per-function REAL region count), mk.py (edit-script probes), namesweep2.py (lab name-count oracle), mini/run.py (minimal TU lab), shadow_census.py, msize.py, warn.py.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_looking\LEDGER.md
@@ -0,0 +1,33 @@
# ai/actor_looking::_actor_look_update
## verdict
NO_PROGRESS
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\lab\\Vb.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\lab\\Va.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\od_update.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\aln_update_base.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\aln_Vb.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\actor_looking\\LEDGER.md"
]
## production_changes
None beyond the patch in the find_prop unit. Its update hunks (cosine() and the stationary_cosine rename) are byte-inert for update: sha 0ecf37ee is unchanged, still 4704 padded against January's 4720. Do NOT land lab/Vb.c. Its `*(real_vector2d *)&...` view copies are /Od-attested (integer mov pairs at 0x46127d..0x4612c8, RTC/HCEX names desired_facing, desired_aiming, stationary_facing), but the house rule admits a new view cast only with a strictly exact caller.
## evidence
January is 4720 padded / 4714 meaningful with 159 relocs; the candidate is 4704 / 4690 / 159, with the same 0x58 frame and the same relocation multiset. real_regions: 119 REAL of 197. I read the /Od update at 0x45f5d0 in full against our source and tested every /Od-attested difference on top of C5. All were byte-inert for update: cosine() at the 4 look-delta sites and the stationary site (C2, C3); `if (looking_free)` in the primary block (U1); `can_look = TRUE` initialiser plus the chained asleep assignment (U2); the override case's chained `succeeded = change_facing [= face_exactly] = TRUE` (U4); the dead facing_optional/facing_locked stores (U5); /Od dot_product2d argument order alone (Vc). January's third secondary branch tests [ebp-4]=can_look, so the later build's looking_free there is not January. Va/Vb (/Od aggregate view copies, plus the /Od dot argument order in Vb) reproduce January's pointer-based 2D copy reads and make both inline dot products x87-operand-identical to January (Jan 0x1037..0x105b). They are still 4688 padded, REAL 131, because of the register-renaming cascade. The name-count oracle was also inert (K=1..24 inserted before update's definition).
## blockers
The remaining difference is register colouring and slot allocation, beginning at +0x1b1 and +0x32f. January colours primary_priority to edi, the constant-zero pin to ebx, and reload webs of the facing/aiming CSE pointers ([ebp-0x14]/[ebp-0x10]) to ebx across the secondary aim case and across update_facing into the stationary block. That forces secondary_aim_valid into memory at [ebp-7] and puts facing_locked at [ebp-8], shared with face_exactly. Ours gives pp ebx, zero edi, keeps secondary_aim_valid in bl, reloads the pointers into edx/edi per use, and swaps the aiming_cosine/primary_priority homes (-0x20/-0x24). The stop rule fired after 9 evidence-based shapes; none moved this colouring.
## reopen_criteria
Reopen on any of: (1) a source construct that gives the facing/aiming CSE pointers their own long-lived webs, for example a genuine January local or expression; verify with the lane A allocator replay (claude-lane-a-ai-core-20260920 scratch/res8/destination2 regsim/dbg32c) that it flips pp and zero between edi and ebx and makes secondary_aim_valid spill; (2) a further header-helper spelling difference (the lever that closed find_prop) found at another update site through the /Od callee map; (3) an owner decision to admit the /Od-attested stationary view copies (lab/Vb.c) once update is otherwise exact. Do not repeat U1, U2, U4, U5, Vc, the cosine() probes or the name-count sweeps.
## task notes
New lever, useful beyond this object: spelling a transcendental through a real_math.h `__inline` helper such as `cosine()`, rather than calling `(real)cos()` directly, changes codegen even when the helper is fully inlined. With the direct call, IL-level tail-merging carries the x87 value into the join block, and a global load in that block then stays unfolded (`mov r,[g]; cmp reg,r`). The inline-helper form folds, matching January's `cmp reg,[g]`, and in this case it also fixed a distant jg width anomaly. Evidence source: /Od calls to the float wrapper at 0x455220. Other residuals that show January folding a load or compare right after an x87 phi join may be the same (real) cos/sin vs cosine/sine spelling issue; a census of `(real)cos(`/`(real)sin(` across non-exact functions is cheap. Tools written in the slug: score.py (gate plus per-function REAL region count), mk.py (edit-script probes), namesweep2.py (lab name-count oracle), mini/run.py (minimal TU lab), shadow_census.py, msize.py, warn.py.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\actor_looking\LEDGER.md
@@ -0,0 +1,38 @@
# source/ai/actor_perception
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_actor_perception_refresh_danger_zone",
"_actor_perception_aiming_vector_test_blockage"
]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/actor_perception/production.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/actor_perception/final_nostatic.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/actor_perception/fallback.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/actor_perception/symbols_static.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/actor_perception/final_full.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/actor_perception/final_full.obj",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/actor_perception/build_final.py",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/actor_perception/LEDGER.md"
]
## production_changes
PACKET A: danger zone. Admissible under standing rulings. Apply scratch/w/actor_perception/fallback.patch to source/ai/actor_perception.c. It makes two changes: (1) delete the TU-private `#define/#undef REAL_MATH_EXTERNAL_POINT_FROM_LINE3D` pair; (2) replace the body of actor_get_perception_knowledge with the single-result form from knowledge_single_result.txt, and add `_actor_combat_status_investigate = 2,` to the TU-local actor combat status enum. The new body uses `short knowledge_type = NONE;`, a prop block, a `knowledge_type == NONE && orphan` block, then `if (knowledge_type == NONE) {...}` and one return. It uses struct actor_datum/prop_datum, prop_acknowledged, _ai_unit_effect_* and _actor_knowledge_*. Gate result: 43/1. The danger zone becomes EXACT 1472/50 and the knowledge function stays EXACT 240. Blockage bytes are unchanged, so its park does not drift. Newly emitted surplus: _point_from_line3d and __real@42340000. The _point_from_line3d COMDAT is byte-identical to action_charge's copy, and provider_link --baseline PASSes. It is allowed by owner ruling 20260921 #1: January's actor_perception.obj references _point_from_line3d as UNDEF. Do NOT run --forbid-emitted-symbol _point_from_line3d on this unit. PACKET B: blockage. Needs the owner's ruling on claude_structural_owner_packet_20260923.md section 5 (aggregate view copy). Apply production.patch INSTEAD OF fallback.patch; it contains A plus B plus packet C. The alternative final_nostatic.patch is A plus B without C. The patch replaces actor_perception_aiming_vector_test_blockage with blockage_final.txt, the /Od-shaped body: header normalize2d, magnitude2d, dot_product2d, point_from_line3d, negate_vector3d and magnitude_squared2d; explicit `else blockage = 0;`; an `if (blockage > 0)` gate; the hes chain `if (<0.36f) { /*comment*/ } else if (<1.21f) 1; else 0;`; HCEX parameter names; and `source_planar_direction = *(real_vector2d const *)source_vector;`. If B lands, retire the config/parked.json entry for source/ai/actor_perception::_actor_perception_aiming_vector_test_blockage (class unclassified). New surplus from B: _normalize2d, _scale_vector2d and _dot_product2d (provider action_charge), and _negate_vector3d (provider action_obey). All four are byte-identical to January's selected copy and provider_link PASSes; they are the ruling-5 all-inlined class. PACKET C: storage. This is optional and codegen-inert; it is included in production.patch. Make 5 January file-static symbols static in source: actor_perception_qsort_compare_optional_props, actor_perception_refresh and actor_perception_refresh_test_object (prototype and definition), actor_emotion_unopposable_retreat, and last_refresh_overflow_warning_time. Apply symbols_static.patch, which edits symbols.json lines 332, 352, 369, 371 and 22318 in place with `, "static": true`, then run a csplit-only regen. All five are absent from cachebeta publics and no other split object references them. The scratch csplit regen changes only source/ai/actor_perception.obj out of 833 objects. After that, object_audit on final_full.obj PASSes. Integrator after any packet: full ninja build plus stable per-function sweep. Only actor_perception.c and symbols.json change, and no header is touched.
## evidence
Baseline: gate 42/2. Blockage [sha] 400/10; danger zone [size 1504!=1472, sha]. Danger zone: Lane A attributed the whole residual to two inline decisions: pfl3d inlined by January, and actor_get_perception_knowledge(actor_index, NONE) called out of line by January but auto-inlined by us. Lab wv1-3: wrapping the site in 1 to 3 extra ifs does not flip the call, so the lever is the callee's cost rather than the site budget. January's callee has `cmp ax,0xffff; jne` at 0xcb, the `knowledge == NONE` sentinel test after the orphan assignment. The HCEA lift (opus5 knowledge_final.c) and /Od 0x470910 (`or edx,-1; mov [ebp-8],dx` ... `cmp [ebp-8],-1`) attest a single-result sentinel form. That spelling is byte-identical to production's goto-done body but costlier to the inliner. With the suppression macro removed, c2_knowledge_sr.c gates 43/1 with the danger zone EXACT, and both the knowledge calls and the 3x __real@42340000 inline match January. This refutes Lane A's 'no source spelling, needs #pragma auto_inline' proof. Blockage: /Od body found at 0x4710a0 through code refs to 0.36f/1.21f (odfloat.py). RTC names source_planar_direction, friend_vector and friend_perpendicular_vector; the callees are normalize2d, magnitude2d, dot_product2d, point_from_line3d, negate_vector3d and magnitude_squared2d. It has an explicit zero store at 0x4711d0, an `if (blockage>0)` gate, and an empty first hes arm (explicit jmp at 0x4711fb). HCEX gives the parameter names (already the ones in actors.h). With field copies, b1 fixes the k-range layout and leaves only the normalize2d reciprocal tail: [size 384!=400]. The /Od 0x4710c6 aggregate copy (one pointer load, both dwords loaded before both stores) closes it: b2 gates 44/0. Castless field-copy variants F1, F2 and F3 all stay at 384!=400, so the copy is load-bearing. Strip test: removing the 5 pointer view casts leaves code bytes and relocations identical (objeq.py), so those casts pass. M8 dead-local oracle bo1-7: inert. Both alternative hes spellings give [sha]. Final gates: final_full 44/0, final_nostatic 44/0, fallback 43/1. object_audit against a scratch split regen with symbols_static: PASS. provider_link --baseline: PASS for all 6 new surplus. surplus_cand.py: every new COMDAT satisfies section_infos_equal with January's provider. Data: build/report.json 2068/2068 at 100%.
## blockers
(1) Blockage only: the aggregate 3D->2D view copy `*(real_vector2d const *)source_vector` needs the owner ruling queued as claude_structural_owner_packet_20260923.md section 5. It is attested per-site by /Od, 2D is a prefix layout of 3D, the function is strict exact, and no castless spelling matches, so it cannot meet ruling 6's byte-inert criterion. Without that ruling, land fallback.patch: the danger zone is exact and blockage stays parked unchanged. (2) Whole object, pre-existing and identical at build/base: actor_perception emits SELECT_ANY _distance3d and _distance_squared2d. Our action_vehicle.c has NODUP hand copies of both, so provider_link FAILs with LNK2005 in both orders. Our _distance3d is also not byte-identical to January's selected copy. This is the same class as the object_admission_rejections entries for leaf_map and biped_limp_noodle; it needs the board-level action_vehicle distance3d provider repair. (3) fake_match_scan flags one review lead: empty-then-else at hes < 0.36f. It is /Od-attested and the alternatives are not exact; disclose it in the ledger.
## reopen_criteria
Object completion reopens when the owner admits the section-5 aggregate view copy (or first-party evidence shows a castless January spelling that reproduces the aggregate copy) and the action_vehicle _distance3d/_distance_squared2d provider repair lands with zero regressions. Then apply production.patch plus symbols_static.patch, run a csplit regen, and re-run object_audit and provider_link over all surplus.
## task notes
Key new law-level finding: VC7's per-callee inline cost depends on the callee's SPELLING, not its bytes. Production's byte-identical goto-done body of actor_get_perception_knowledge was cheap enough to auto-inline at the constant-NONE sites. January's single-result sentinel spelling is not, which closes the danger zone without the pragma Lane A thought was required. That is another false impossibility proof retired. Tooling written under the slug: kc.py (reloc census), splice_blockage.py, surplus_cand.py (surplus identity for a candidate obj), undef_refs.py, object_audit_t.py (SPLIT_ROOT override), make_symbols_patch.py (in-place symbols.json edit), odfloat.py (find /Od functions by float-constant code refs). The scratch csplit run used build/tools/csplit.exe with a scratch config dir; its output is in split_out/ and it did not touch build/split. No tracked file was edited. The existing units rejection entry for _point_from_line3d in object_admission_rejections.json looks stale after owner ruling 20260921 #1; flagged only, since it is outside this unit.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/actor_perception/LEDGER.md
@@ -0,0 +1,77 @@
# source/bink/bink_playback::_bink_query_analog_controller_buttons
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\bink_playback\\L3_q3.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\bink_playback\\S1_q3_static_bss.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\bink_playback\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\bink_playback\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\bink_playback\\G2_fc_q3.fn",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\bink_playback\\xbe.py"
]
## production_changes
None to apply unconditionally. The only exact body needs an owner ruling.
If the owner re-rules in favour of the else-if split (the earlier n3a/n5 'q3' candidate), apply:
- C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\bink_playback\production.patch. This is a unified diff of source/bink/bink_playback.c (CRLF). It changes only the START/BACK test at about line 1028 from the `||` form to `if (start==1) {button_pressed= TRUE;} else if (back==1) {button_pressed= TRUE;} else {analog loop}`.
- The full candidate file is scratch/w/bink_playback/L3_q3.c. The combined candidate with the storage fix below is S1_q3_static_bss.c.
- Then retire the park config/parked.json entry {unit: source/bink/bink_playback, function: _bink_query_analog_controller_buttons, class: register-allocation}.
If the ruling stays no:
- Keep the park.
- Correct its class and evidence. It is NOT a register-allocation tie: the production `||` body is refuted as January's source (evidence below). The recorded reopen criterion cannot be met with the artifacts we have, because both unoptimized first-party builds contain only a bink stub.
## evidence
Full log: scratch/w/bink_playback/LEDGER.md.
(1) Baseline, gate --all: 23 exact / 1 residual (`_bink_query_analog_controller_buttons [size 96!=80, sha]`). In the production build the constant 1 is pinned in BL, the boolean is spilled to [ebp-1] and an EBP frame appears. January keeps the boolean in BL and compares against immediates.
(2) /Od readout (halo_cache_symbols.exe): bink_playback_active (0x4d19c0) is `xor al,al; ret`, and 0x4d1900..0x4d1a50 contains only stubs. The real PC player at 0x4d1a50 (called from the ending.bik caller 0x61b360) is a Gearbox BinkOpen/D3D loop with no gamepad scan. HCEA also has only bink_playback_stub.obj. So there is no unoptimized witness.
(3) Lab name-count oracle: 0 to 80 dummy names inserted before the definition all stay at 96. Not a TU name-count effect.
(4) Lab mechanism probes:
- Changing only the analog compare to ==2, or only the start/back compares to ==2, frees BL (80 bytes).
- Signed-char compare operands are inert.
- So the contest is the boolean web against the constant-1 web for EBX, and it is decided by a single reference either way.
(5) New primary-artifact evidence: the Oct 12 2001 prototype builds (Downloads/Halo - Combat Evolved (Oct 12, 2001 prototype)/Halo 2276/, read as data only with xbe.py and scan_xbe*.py).
- 2276betaP.xbe file offset 0x1b6100 matches January byte for byte (call rel32 masked).
- 2276P.xbe (identical to oct-default.xbe), file offset 0x14f180, calls the getter with `mov ecx,esi`. A lab __fastcall emulation gives: the `||` form pins (96 bytes, G1_fc_or); the q3 form matches 2276P byte for byte (G2_fc_q3).
- 2276defaultP.xbe VA 0x2c700 inlines the getter. A lab in-TU getter emulation reproduces it exactly, but `||` and q3 compile identically there, so that build is neutral (P1/P2).
- Result: across the four compiled instances of this function, `||` matches 1 of 4 and the else-if split matches 4 of 4.
(6) Whole-object audit of the q3 candidate (L3_q3.obj):
- gate: 24/0/0, _point_from_line3d guard passed.
- object_audit: PASS (59/59 symbols).
- surplus_identity: 0 candidate-only code COMDATs.
- provider_link: SELECTED-PROVIDER LINK PASS for all 6 surplus symbols (3 D3D tables and 3 __real literals).
- report.json: data 1701/1701 (100%).
- fake_match_scan: only the owner-approved int 3.
## blockers
1. The owner ruling that rejected q3 is still in force: commit 26601453 and astra research14 hold that 'identical optimized CFG plus allocation does not identify the source arms'. Its reopen criterion (an unoptimized first-party witness of separate START/BACK arms) cannot be met with current artifacts, since both the /Od build and HCEA carry only a bink stub. The new October 2276P/betaP agreement is still allocation-based evidence, so it goes to the owner rather than satisfying the criterion literally.
2. Object-level storage: see the second unit entry. That fix is needed for whole-object completion whatever the ruling.
3. `_code_001b5850` keeps an address-derived name (documented anonymous 16-byte `ret` leaf; exhaustive identity search found no name). It is absent from PDB publics and unreferenced, but making it static makes VC7 drop it, so it must stay external.
## reopen_criteria
Land q3 (production.patch) only on an explicit owner ruling that accepts the cross-build evidence: the `||` form is refuted in two calling contexts, and the else-if split reproduces January, 2276betaP and 2276P exactly. Otherwise, reopen only if a first-party unoptimized build containing the real bink_playback.c body appears (not the PC/HCEA stubs). Do not re-spend on spellings: 10 prior shapes, plus this run's name-count sweep (0 to 80), operand-type probes and context emulations, are all recorded.
## task notes
No slug directory existed, so this run started fresh; prior shapes were taken from the committed ledgers (w2, n3a, n4 and n5 in the opus5-150k worktree) and none were repeated. No tracked file was edited and ninja, configure and git were not run. The Oct 2001 prototype XBEs and halo_cache_symbols.exe were read as data only.
Tools written in the slug directory:
- mkvar.py builds a variant by swapping in a .fn body and gating it.
- namesweep.py is the lab-only name-count oracle.
- disobj.py disassembles one function from an object, with relocations.
- xbe.py, scan_xbe.py and scan_xbe2.py parse XBE sections and sweep for the byte compares.
The October 2276 prototype function locations are recorded in the ledger. They are reusable as a second compilation context: 2276P uses a register-argument call convention and 2276defaultP inlines the getter across modules. Both could discriminate other held allocation rows.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\bink_playback\LEDGER.md
@@ -0,0 +1,45 @@
# source/bink/bink_playback (storage classes)
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\bink_playback\\storage.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\bink_playback\\S0_static_bss.c"
]
## production_changes
Apply C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\bink_playback\storage.patch. It is two unified diffs:
(a) source/bink/bink_playback.c (CRLF): add `static` to the four .bss definitions bink_pointer_blocks, bink_bitmap, bink_texture and bink_pointer_block_count (about lines 404-412).
(b) config/symbols.json: edit lines 23299, 23300, 23301 and 23304 in place to append `, "static": true` for _bink_pointer_blocks, _bink_bitmap, _bink_texture and _bink_pointer_block_count. Do not re-serialize or sort the file.
Then regenerate the bink_playback split (csplit) and run the full build and report. Verify that object_audit shows 0 differing symbols and that data stays at 100%.
## evidence
pdb_storage.py flags these four symbols (plus _code_001b5850) as absent from cachebeta publics. A scan of every build/split and build/base object finds zero UNDEF references to the four, and no other source TU names them. By the storage-truth rule they were file-static in January.
S0_static_bss.c (production plus the four statics) is byte-inert: gate 23/1 unchanged, and object_audit differs only in storage class 2 vs 3 against the split still generated from the unedited symbols.json. S1_q3_static_bss.c gives 24/0 with the same four storage-only differences, which the symbols.json edit plus regen removes.
_code_001b5850 is left unchanged, because a static unreferenced function is not emitted (tested in S2: UNWRITTEN).
## blockers
Needs integrator symbols.json edits plus a csplit regen (worker may not edit tracked files or run ninja). Not verified under a regenerated split or an objdiff 3.3.1 report.
## reopen_criteria
n/a (mechanical admission fix; verify after regen).
## task notes
No slug directory existed, so this run started fresh; prior shapes were taken from the committed ledgers (w2, n3a, n4 and n5 in the opus5-150k worktree) and none were repeated. No tracked file was edited and ninja, configure and git were not run. The Oct 2001 prototype XBEs and halo_cache_symbols.exe were read as data only.
Tools written in the slug directory:
- mkvar.py builds a variant by swapping in a .fn body and gating it.
- namesweep.py is the lab-only name-count oracle.
- disobj.py disassembles one function from an object, with relocations.
- xbe.py, scan_xbe.py and scan_xbe2.py parse XBE sections and sweep for the byte compares.
The October 2276 prototype function locations are recorded in the ledger. They are reusable as a second compilation context: 2276P uses a register-argument call convention and 2276defaultP inlines the getter across modules. Both could discriminate other held allocation rows.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\bink_playback\LEDGER.md
@@ -0,0 +1,80 @@
# source/physics/collisions
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_collision_move_point"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\collisions\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\collisions\\final\\collisions.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\collisions\\final\\matrix_math.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\collisions\\final\\real_math_planes.h",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\collisions\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\collisions\\sweep.py",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\collisions\\gate_local.py"
]
## production_changes
Apply C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\collisions\production.patch with `patch -p1 --binary`. It is verified to apply cleanly to e9e62b78 and keeps CRLF line endings. The resulting files are identical to scratch/w/collisions/final/*. The patch changes three files and leaves real_math.h UNTOUCHED:
(1) source/math/real_math_planes.h: append the `__inline boolean valid_real_plane3d(real_plane3d const *plane) { return valid_real_normal3d(&plane->n) && valid_real(plane->d); }` definition, with a comment, after the line_from_planes3d prototype. The real_math.h:672 prototype stays.
(2) source/math/matrix_math.c: add `#include "real_math_planes.h"` after `#include "real_math.h"`, and delete the plain `boolean valid_real_plane3d(...)` definition at lines 1073-1079. This half is mandatory. With the current NODUP definition, the new collisions COMDAT gives LNK2005 in both link orders. With the patch, matrix_math.obj is byte-identical in every symbol section and its _valid_real_plane3d COMDAT becomes selection ANY.
(3) source/physics/collisions.c: at the 0x3CE assert, replace the hand-expanded `valid_real_normal3d(&collision->plane.n) && valid_real(collision->plane.d)` with `valid_real_plane3d(&collision->plane)`. This is byte-inert, keeps to the house rule and is /Od-attested. Also move `real_point3d position;` from function scope back into the success-arm block, ahead of new_clip_indices. This reverts Lane B's try_13 scope move and is load-bearing.
After the ninja build, check build/report.json. _collision_move_point has 5 internal jump-table relocations, like the strict-exact _object_get_features_in_sphere, which objdiff 3.3.1 already under-credits at 93.8%. If either stays below 100%, add switch-label semantic_matches.json entries:
- move_point: 4752 padded, 226 relocations, normalized sha256 8b2f29007193d3aacd830e10fa99886acd6a3bc1e9cd604483343340f6117e2c.
- object_get_features_in_sphere: 544, 13 relocations, d5292109fb39f3a5537512cc61c6863cac9580ef6f2783e92cc40044e30d8325.
No symbols.json or config.json change. No park to retire, because move_point is unparked and has no admission rejection.
## evidence
Baseline at e9e62b78 was 19/20. move_point had 2 REAL regions: the single Lane B transposition at 0x595/0x598 inside the struct copy `clip_plane = collisions[new_clip_indices[0]].plane`. January's order is L0 S0 L1 S1 L2 L3 S2 S3; ours was L2 S2 L3 S3.
MECHANISM, from oracle probes on the real function:
- Remove the case-1 `valid_real_plane3d(&clip_plane)` call (pg): the copy flips to January's order.
- Route the same address to a TU-visible helper (pg3/pg5): January's order again. pg5 leaves 0 REAL regions.
- Keep the external callee but pass a member address (pg4): no change.
- Conclusion: VC7 treats &clip_plane passed to an OPAQUE external prototype as an escape, so pointer load L3 cannot pass store S2. A callee whose body is visible does not capture.
DOUBLE ATTESTATION that January's valid_real_plane3d was an inlinable function:
(a) At 0x485-0x4E8 January INLINES its body at the in-loop assert: magnitude_squared-1 fabs<eps, then the d exponent test.
(b) At the same site the /Od build makes ONE call, valid_real_plane3d(&collision->plane) at 0x7A5833, to fn 0x6C7730, whose body is `valid_real_normal3d(&p->n) && valid_real(p->d)`.
(c) January calls it out of line at case 1 and at render_camera_mirror. The copy order at 0x57e requires a non-capturing, visible callee.
PACKETS TESTED with a local mirror-tree board sweep (sweep.py; control mirror identical to build/base):
- Broad real_math.h inline, the January-like placement: collisions 20/20, but LOSES _bitmap_copy and _rasterizer_frame_statistics_draw. This is position-inert across 4 positions; k-variants are worse.
- _bitmap_copy is restorable by dropping the duplicate bitmap_delete prototype.
- frame_statistics_draw is not restorable: no count from -6 to +30 name-count units works.
- FINAL focused-header packet: **447 TUs compiled, 446 identical to build/base. The only delta is collisions: +_collision_move_point strict and +surplus _valid_real_plane3d. ZERO regressions.**
CANDIDATE collisions.obj:
- 20/20 strict; move_point has 0 REAL regions and sha equals January's.
- object_audit PASS (44/44 January symbols).
- pdb_storage: 0 disagreements.
- surplus_identity: all identical, including the new _valid_real_plane3d, which is section_infos_equal to January's matrix_math copy.
- Pair link of packet collisions with packet matrix_math: PASS in both orders.
- report.json data: .data, .rdata and .bss at 100% (590/590).
- fake_match_scan: 0 leads on the changed collisions.c and real_math_planes.h.
STRIP TESTS:
- Header half removed: 19/20.
- Function-scope `position`: 19/20 (the count-keyed 0x17b/0x17f preheader order).
- Hand-expanded loop site: still 20/20, so byte-inert.
## blockers
Pre-existing and unrelated to this packet, since they also fail on the current build/base collisions.obj. scratch/tools/provider_link.py FAILs (LNK2005 in both orders) for two surplus COMDATs whose January providers are NODUP hand copies:
- _plane3d_negate: plain definition at source/effects/decals.c:896, provider decals.obj.
- _point_in_sphere: plain definition at source/ai/path_obstacles.c:191, provider path_obstacles.obj.
This is the known NODUP-provider category and needs an owner decision. decals.c is closed for this lane.
Secondary: objdiff 3.3.1 under-credits the jump-table functions (see production_changes).
Disclosure for review: the `position` scope choice is keyed to the declaration count. Under this packet block scope is exact. Lane B chose function scope under the old header, and the later /Od build's RTC shows function scope, which is weak evidence from a later revision.
## reopen_criteria
The object becomes OBJECT_COMPLETE_CANDIDATE when the owner rules on the NODUP hand copies. The pattern is the one this packet applies to matrix_math: delete the plain definitions in decals.c:896 and path_obstacles.c:191 so the header __inline provides the COMDAT. Then provider_link.py source/physics/collisions must pass for all surplus symbols, with the full sweep still at 0 regressions. The semantic_matches entries are also needed if objdiff stays below 100% on move_point and object_get_features_in_sphere.
If the owner prefers January's layout (inline in the broad real_math.h), that route first needs a genuine missing-declaration recovery for rasterizer_frame_statistics.c. Its draw function is exact only at the current single-point name count, and the broad-header variant shifts it; see LEDGER step 6.
## task notes
New general law, worth a memory entry. VC7 treats `&local` passed to an OPAQUE external prototype as an escape: a later pointer load cannot be hoisted above a store to that local. A callee whose BODY is visible in the TU does not capture, even when that call site is not inlined.\n- Tell: January hoists the last load of a struct copy above the previous store (L2 L3 S2 S3) where we keep L2 S2 L3 S3.\n- Census tool: scratch/w/collisions/copyscan.py. January has 15 such 4-dword copies and many 3-dword ones. Only move_point, actor_look_update, draw_bitmap_in_rect and the two biped functions differ in copy order from our build.\n- Candidate lever for other residuals whose January callee was a header inline that the reconstruction declares only as a prototype, such as the NODUP hand-copy helper family.\n\nTools for header packets, which never touch tracked files:\n- scratch/w/collisions/sweep.py: mirror-tree sweep of all 447 TUs, about 2-3 minutes at 4 jobs.\n- mkmirror.py\n- gate_local.py: compile with header overrides placed first on /I. The override must shadow both \"math/real_math.h\" and \"real_math.h\".\n- objdelta.py, cmp_obj.py, pairlink.py, comdat_sel.py.\n\nThis also confirms the owner's earlier real_math_planes.h precedent. Adding a body to the broad real_math.h regresses name-count-fragile TUs (bitmap_copy, frame_statistics_draw), and a focused header avoids that.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\collisions\LEDGER.md
@@ -0,0 +1,37 @@
# source/camera/dead_camera
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_dead_camera_new",
"_dead_camera_update"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\dead_camera\\final_prod.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\dead_camera\\final_full.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\dead_camera\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\dead_camera\\symbols_static.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\dead_camera\\owner_update_bug.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\dead_camera\\full_object.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\dead_camera\\LEDGER.md"
]
## production_changes
(1) LANDABLE NOW: apply scratch/w/dead_camera/production.patch to source/camera/dead_camera.c (HEAD sha de77a780 -> final_prod.c sha 708198eb). It does three things: (a) `static struct dead_camera_constants const dead_camera_constants` (file-static, per cachebeta.pdb); (b) dead_camera_new rewritten to the /Od-attested shape: `observer` declared with its initializer; locals `pitch` and `switch_timer` only; direct stores `camera->distance = real_local_random_range(2.f, 6.f);` and `camera->facing.yaw = real_local_random_range(0.f, 2.f * _pi);`; `pitch = real_local_random_range(...); camera->facing.pitch = -pitch;` placed BEFORE `camera->timer = dead_camera_constants.dead_timer;`; the switch_timer if/else stays as it is; `camera->player_index = local_player_get_player_index(local_player_index);` with no local; `camera->unit_index = unit_index == NONE ? player_get(camera->player_index)->dead_unit_index : unit_index;`. Result: _dead_camera_new strict EXACT (277 meaningful / 288 padded), with the 3 other rows unchanged. (2) Apply scratch/w/dead_camera/symbols_static.patch to config/symbols.json. It edits line 14063 in place, with no re-serialisation: `{ "file_offset": 2452200, "flags": 0, "name": "_dead_camera_constants", "static": true },`. Then regenerate the split only (csplit-only) so that split storage becomes 3. Without it, object_audit reports storage 2/3. (3) OWNER-GATED (authentic January bug; brief says report, don't land): apply scratch/w/dead_camera/owner_update_bug.patch on top of (1). It deletes the `else { next_unit_index = (long)result; }` arm and its old comment, and adds a BUG comment above the uninitialised `long next_unit_index;`. Result: _dead_camera_update EXACT (1235/1248), so the object is 4/4. full_object.patch = HEAD -> final_full.c (sha 7186c901) in one step. Attach the new /Od evidence below to the existing owner packet row: docs/object_matching_logs/claude_structural_owner_packet_20260923.md, section 4, `_dead_camera_update`. (4) Only after the ruling and the regen: flip config.json source/camera/dead_camera.c status NonMatching -> Matching. There are no parks to retire and no semantic entries. The provider-link FAIL on _valid_real_vector3d_axes2 (bored_camera NODUP hand copy) is pre-existing and needs no change here.
## evidence
NEW EVIDENCE 1: cachebeta.pdb publics (pdb_storage.py) show 0x256AE8 `_dead_camera_constants` is NOT public, so it is file-static in January. Hypothesis: VC7 alias analysis lets the load of a file-static const aggregate whose address is never taken be hoisted above the camera-> pointer stores. January's R1 is exactly that load hoist (`mov edx,[const]` before fchs/fstp). Measured with the notation U=mov edi,[ebp+0x10], L=const load, N=fchs, A=add esp, S=pitch fstp, C=cmp, T=timer store, J=je. January is U L N A S C T J. With an external constant, every pitch form gives the wrong order: forms a/b/c give U N S L A C T J, d gives N L S U, e gives N L U A C S. With `static`, forms a and b reproduce January exactly. The factorial is in stfac/: 5 pitch statement forms x 16 contexts (distance/yaw locals, switch_timer local, player_index local, unit local). Forms a and b are EXACT in all 32 of their contexts, and c/d/e stay residual, so the context is inert. NEW EVIDENCE 2: /Od readout of halo_cache_symbols.exe. dead_camera_new is at 0x522a20 (found via the 0x3ef1463b/0x3f8cbe4c literals); see od_new.txt. It shows direct distance/yaw stores and the pitch store BEFORE the timer store. It also shows camera->player_index stored directly and then reloaded, and a ternary temp for unit_index: named locals get frame slots before temps, which update's /Od frame proves, so a named watched_unit local is excluded. NEW EVIDENCE 3: /Od dead_camera_update at 0x522c90 (od_update.txt) has `mov [ebp-8],-1` at the top of the switch block, i.e. `long next_unit_index = NONE;` and no else arm. That is the later build's FIX for January's uninitialised local. The lab u_bug.c and final_full.c confirm that the uninitialised form makes _dead_camera_update EXACT. The n1 lane's v1/v2 already proved that no defined initialiser elides the store. Section-4 audit on final_full.obj: gate is exact 4/4 and the _point_from_line3d guard passes. object_audit shows all 6 January sections ok; the only difference is _dead_camera_constants storage 2/3, and a split patched to storage 3 (split_static_sim.obj) gives section_infos_equal True for all 4 functions. Data .rdata is 48/48 at 100% in report.json and the bytes are unchanged. The 8 surplus COMDATs and literals are byte-identical to the HEAD base object, and surplus_identity reports 0 non-identical. provider_link FAILs only on _valid_real_vector3d_axes2 against bored_camera's NODUP copy. HEAD fails identically, and 6 Matching camera objects carry the same condition. fake_match_scan reports 0 leads. Full probe log: scratch/w/dead_camera/LEDGER.md.
## blockers
(1) Owner ruling needed for the authentic January uninitialised read in _dead_camera_update. It is already listed in claude_structural_owner_packet_20260923.md section 4, and this run adds the first-party /Od fix evidence. Without the ruling the object stays 3/4: _dead_camera_update remains a 1-instruction residual at 0x176, `mov eax,[ebp+0x10]` vs `mov eax,esi`. (2) The integrator must apply the symbols.json `"static": true` change and regenerate the split (csplit-only) for full symbol-storage agreement. (3) The pre-existing provider-link conflict with bored_camera's NODUP _valid_real_vector3d_axes2 is not caused by dead_camera; there is Matching precedent in 6 camera objects.
## reopen_criteria
_dead_camera_new: closed (EXACT with production.patch). Reopen only if a header or symbols change regresses it: verify with gate plus tools/sig.py, and the R1 signature must stay U L N A S C T J. _dead_camera_update: an owner decision on the section-4 original-bug class. If the owner rejects it, the row is permanently unreachable in defined C (n1 v1/v2 proofs), so park it with this evidence.
## task notes
New decoded law for this row: whether a const aggregate is external or file-static changes VC7 emission order. A load from a file-static const aggregate whose address is never taken is hoisted above pointer stores; an external const is not. Check cachebeta.pdb publics (pdb_storage.py) whenever an R1-style 'dependency-free order' residual involves a const global load. This row had been classed as an unreachable emission-order tie across 9 prior shapes. The /Od statement order alone did not close it (it moved U only), while static + /Od order closes it. Tools written: scratch/w/dead_camera/tools/sig.py (R1 order signature), gen.py (factorial generator), fchs_scan.py, definers.py. Backslash hazard: the Bash tool collapses doubled backslashes inside heredoc Python, so an early batch had a single-backslash __FILE__ literal. It was fixed in 127 files and only ever affected the literal relocation, never codegen. Optional hygiene not done (it needs a header edit and is expected to be byte-inert): replace the TU-local struct dead_camera_command with observer.h's struct observer_command, per the n1 orchestrator proposal.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\dead_camera\LEDGER.md
@@ -0,0 +1,93 @@
# source/text/draw_string :: _bitmap_draw_string (whole-object closure)
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/final_full.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/production.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/bug_body_after_ownership.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/bug_body_only_vs_production.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/cand2.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/symbols_json.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/LEDGER.md"
]
## production_changes
APPLY ONLY AFTER AN OWNER RULING that admits this BUG (original) body. The brief says to report it and not land it.
(1) Preferred: land the ownership packet first (next unit), then run `git apply scratch/w/draw_string/bug_body_after_ownership.patch` from the worktree root. Alternatively, `git apply scratch/w/draw_string/production.patch` does both at once; the result is byte-identical to scratch/w/draw_string/final_full.c (CRLF).
The new body of _bitmap_draw_string follows the /Od topology:
- the format switch sets `boolean valid_format` TRUE for the 5 listed formats and FALSE by default;
- `if (valid_format)` wraps the rest;
- inside, `if (!bounds)` runs set_rectangle2d(&adjusted_bounds, MAX(0,bounds->x0), MAX(0,bounds->y0), MIN(bitmap->width,bounds->x1), MIN(bitmap->height,bounds->y1)) and then `bounds=&adjusted_bounds`, under a /* BUG (original) */ comment giving the safe corrected alternative;
- the same clip is applied to `clip` via adjusted_clip;
- then draw_string(bitmap_draw_character, bounds, NULL, clip, 0, string).
(2) Retire the park: in config/parked.json, delete the entry `{ unit source/text/draw_string, function _bitmap_draw_string, class unclassified ... }` (lines ~781-799 in this worktree). Edit in place; do not re-serialise.
(3) After the ownership packet, csplit regen and a full ninja, set config/config.json line 424 `source/text/draw_string.c` status from NonMatching to Matching, but only if the integrator's admission audit agrees.
(4) Function-only fallback, if the ownership packet is not landed: `git apply scratch/w/draw_string/bug_body_only_vs_production.patch`, which equals cand2.c. It gates exact 28 / residual 0 against the CURRENT split with no split regen, but the object keeps its one pdb_storage disagreement.
## evidence
January (tinfo/dis_range):
- The code is: `mov [_draw_string_globals],esi`, then the switch jump table.
- `mov ebx,[ebp+0xc]; test ebx,ebx; jne 0x94`. The NULL arm reads [ebx], [2], [4] and [6] with no relocation (bounds==NULL is constant-propagated).
- The arm clips those values against bitmap[4]/[6] through MAX(0,..)/MIN(bitmap,..) into set_rectangle2d(&[ebp-8]), then does `lea ebx,[ebp-8]`.
- The layout proves the fields: rectangle2d is {y0@0, x0@2, y1@4, x1@6}, and the push order x0=MAX(0,[2]), y0=MAX(0,[0]), x1=MIN(width,[6]), y1=MIN(height,[4]) matches bounds->x0/y0/x1/y1 exactly.
NEW this run: the later first-party /Od build (halo_cache_symbols.exe fn 0x8b2090, dumped to scratch/w/draw_string/od_8b2090.txt) has the SAME inverted test `cmp [ebp+0xc],0; jne skip` followed by bounds-> reads. That is independent corroboration of the original bug.
- Its RTC descriptor names the locals adjusted_bounds [ebp-0x14] and adjusted_clip [ebp-0x24].
- A byte flag at [ebp-5] is set by the format switch and then tested.
- HCEX.pdb has no record for bitmap_draw_string.
Probes (full detail in LEDGER.md):
- cand1 (switch-case body with /Od names): gate exact 28 / residual 0.
- cand2 (/Od valid_format flag form): exact 28 / residual 0. The flag is byte-inert and attested by /Od.
- The production safe body stays 240/304 (objdiff 51.18).
Whole-object audit of final_full.obj against a simulated split (see the next unit), all passing:
- 49/49 January symbols strict-equal (coff_compare);
- object_audit PASS;
- surplus is the same 7 literal/real COMDATs production already emits, and provider_link PASS;
- surplus_identity: 0 code COMDATs;
- simulated pdb_storage: 0 disagreements;
- CL /Zs /W3: the same 13 warnings as production;
- fake_match_scan: 0 leads.
objdiff 3.3.1 scores the exact function at 87.83%. It joins the 5 already-accepted hidden_exact draw_string functions, which build/semantic_report.json accepts automatically. The .bss and .rdata sections stay at 100%.
## blockers
Policy, not bytes. The only exact form reproduces January's NULL `bounds` dereference, which is undefined behaviour. The worker brief requires owner approval for any authentic January bug, and the handoffs (opus5_100k consolidated, lane D manifest group J) list this as an owner ruling. No safe expression can produce absolute reads at 0..6 without a relocation.
## reopen_criteria
An owner ruling that admits a UB-preserving BUG (original) body. This run adds new support for such a ruling: the later /Od first-party build has the same inverted test, which is the independent lineage corroboration the playbook's original-bug gate asks for. Once admitted, apply the listed patches mechanically. No further compiler work is needed.
## task notes
This slug directory did not exist at resume, so the run started fresh; nothing was lost.
**_bitmap_draw_string**
- It is exact only with January's NULL-bounds original bug.
- Two forms were measured exact: switch-case, and the /Od-attested valid_format flag.
- New in this run: the later /Od build (0x8b2090) has the same inverted test, which corroborates the bug from an independent build. It also names the locals adjusted_bounds and adjusted_clip.
- It remains owner-gated.
**Complete-object candidate**
- final_full.c passes every audit step against a simulated regenerated split.
- It needs the owner ruling plus the ownership packet.
**Ownership packet**
- It is independently landable now at zero credit.
- It fixes the object's pre-existing storage defect: one invented external 248-byte aggregate replaced by the four HCEX-attested file statics.
**Tools and safety**
- csplit was run only into scratch directories, with a copied config.
- `git status` shows no tracked file modified.
- A `git apply` test inside a repo subdirectory was a no-op (paths outside the subdirectory are ignored). Verification was redone outside the repo.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/LEDGER.md
@@ -0,0 +1,96 @@
# source/text/draw_string (.bss ownership: split the invented _draw_string_globals into January's 4 file statics)
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/final_ownership.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/ownership.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/symbols_json.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/gen3.py",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/simcmp.py",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/object_audit_sim.py",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/simsplit1/source/text/draw_string.obj"
]
## production_changes
Landable now at zero code credit, independent of the owner ruling. Apply both parts in one commit, then run a csplit-only regen and a full ninja.
(a) From the worktree root, run `git apply scratch/w/draw_string/ownership.patch`. The result is byte-identical to scratch/w/draw_string/final_ownership.c. The patch:
- removes the `#define draw_character_software_globals/font_drawing_globals draw_string_globals` aliases and `struct draw_string_globals`;
- adds `struct font_drawing_globals`, using HCEX field names (string_list_index, current_font_index, current_flags, current_style, current_justification, current_color, tab_stop_count, tab_stops[MAXIMUM_NUMBER_OF_TAB_STOPS], highlight_start_index, highlight_stop_index, initial_indent, paragraph_indent) and `struct rasterizer_dynamic_screen_geometry_parameters multitexture_params`;
- adds `#include "rasterizer/rasterizer.h"`;
- adds four file statics in this declaration order, each with `= { 0 }`: anonymous-struct draw_character_software_globals {bitmap, encoding_shift}, text_pick_globals {pick_point, best_pick_string_index, best_pick_distance, last_string_index}, text_bounds_globals {bounds, last_font_header}, and `static struct font_drawing_globals font_drawing_globals`;
- rewrites every field reference to match;
- updates the header symbol-list comment.
(b) Run `git apply scratch/w/draw_string/symbols_json.patch`. It replaces config/symbols.json line 23271 `{ "file_offset": 4987144, "flags": 0, "name": "_draw_string_globals" },` IN PLACE with four lines:
- 4987144 `_draw_character_software_globals`
- 4987152 `_text_pick_globals`
- 4987164 `_text_bounds_globals`
- 4987176 `_font_drawing_globals`
Each line carries `"static": true`. Do not re-serialise the file.
Also required:
- relocs.json rows 82245-82255 and 82475 target file_offset 4987144 and keep working unchanged; the simulated csplit already exercised them.
- contribs.json needs no change.
- No semantic_matches, semantic_data_matches or symbol_ownership entry names _draw_string_globals.
## evidence
Name evidence:
- The January assert literals name two different globals: `draw_character_software_globals.bitmap->width` and `font_drawing_globals.tab_stop_count`.
- cachebeta.pdb has no public anywhere in 0x4C18xx-0x4C1Axx, and no other split object references the symbol, so January's storage was file-static. The current state is split 2 / ours 2 / truth 3, which is the object's only pdb_storage disagreement.
- HCEX.pdb (DIA2Dump -g/-sym/-type) lists four File Static symbols: text_pick_globals, text_bounds_globals and draw_character_software_globals (unnamed-tag structs), plus font_drawing_globals.
- The font_drawing_globals UDT gives the field names above and puts multitexture_params (rasterizer_dynamic_screen_geometry_parameters) at +0x4C. The repo's size check is 0x8C, which equals January's unreferenced tail at +0x6C..+0xF8.
- The /Od build places the same 4 objects in reverse order, while that 0x8C tail stays glued to font_drawing, so the tail belongs to font_drawing.
Layout law:
- With uninitialised statics, VC7 orders by name hash: pick 0, bounds 0x0C, dcs 0x18, font 0x20 (probe 3). That is not January's order.
- With `= { 0 }` in declaration order dcs, pick, bounds, font, VC7 places them at 0, 8, 0x14, 0x20, which is January's exact layout (probe 4, VC7 .bss law).
Verification (simulated split):
- build/tools/csplit.exe was run with a scratch copy of config into scratch/w/draw_string/simsplit*.
- With the unmodified config, the output is byte-identical to build/split.
- With the patched symbols.json, only draw_string.obj changes across the whole split (diff -rq). It defines the 4 statics (storage 3) at 0/8/0x14/0x20.
- final_ownership.obj against the new split: 27/28 functions strict-equal. The only exception is _bitmap_draw_string, which keeps the same safe-body residual as today.
- Simulated pdb_storage: 0 disagreements. object_audit fails only on that function.
- objdiff 3.3.1 gives scores identical to production (.bss 248 at 100%, .rdata at 100%, same function percentages).
- /W3: the same 13 warnings. fake_match_scan: 0 leads.
- All patches were re-applied outside the repo and reproduce the files byte-for-byte.
- `git status` shows no tracked file modified.
Note: until the split is regenerated, gate.py against the OLD split shows 23 reloc-identity rows. That is expected. Land source and symbols.json together.
## blockers
None for landing. It earns no code credit by itself, but it removes the object's only storage-truth defect, so the draw_string object is admission-clean apart from the owner-gated _bitmap_draw_string body.
## reopen_criteria
n/a. After landing, confirm with a full ninja plus `python scratch/tools/pdb_storage.py source/text/draw_string` (expect 0 disagreements) and gate.py (27 exact plus the unchanged _bitmap_draw_string residual).
## task notes
This slug directory did not exist at resume, so the run started fresh; nothing was lost.
**_bitmap_draw_string**
- It is exact only with January's NULL-bounds original bug.
- Two forms were measured exact: switch-case, and the /Od-attested valid_format flag.
- New in this run: the later /Od build (0x8b2090) has the same inverted test, which corroborates the bug from an independent build. It also names the locals adjusted_bounds and adjusted_clip.
- It remains owner-gated.
**Complete-object candidate**
- final_full.c passes every audit step against a simulated regenerated split.
- It needs the owner ruling plus the ownership packet.
**Ownership packet**
- It is independently landable now at zero credit.
- It fixes the object's pre-existing storage defect: one invented external 248-byte aggregate replaced by the four HCEX-attested file statics.
**Tools and safety**
- csplit was run only into scratch directories, with a copied config.
- `git status` shows no tracked file modified.
- A `git apply` test inside a repo subdirectory was a no-op (paths outside the subdirectory are ignored). Verification was redone outside the repo.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/draw_string/LEDGER.md
@@ -0,0 +1,82 @@
# source/tag_files/files_windows
## verdict
OBJECT_COMPLETE_CANDIDATE
## newly_exact_functions
[
"_file_get_size"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\files_windows\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\files_windows\\files_windows.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\files_windows\\a1.obj",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\files_windows\\a1.gate.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\files_windows\\a1.audit.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\files_windows\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\files_windows\\od_get_size.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\files_windows\\od_file_error.txt"
]
## production_changes
1) Apply C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\files_windows\production.patch to source/tag_files/files_windows.c. It is a unified diff with a/ b/ paths and CRLF preserved, and `git apply --check` passes. The result is byte-identical to scratch/w/files_windows/files_windows.c (sha256 aa3dac72...). It makes 2 TU-private edits:
(a) file_get_size gets the single-exit shape from the /Od build: `struct file_reference_info const *info = file_reference_get_const_info(file);` as the declaration initializer; `boolean result = FALSE;` declared between full_path and attribute_data; `if (GetFileAttributesExA(...)) { *size = attribute_data.nFileSizeLow; result = TRUE; }`; `if (!result) { file_error(file, "file_get_size"); }`; then `return result;`.
(b) Static file_error gains `unsigned long error_code = GetLastError();` after the info declaration and passes error_code to error() in place of the inline GetLastError() call.
No header, symbols.json, config.json or relocs change.
2) Retire the config/parked.json park {unit: source/tag_files/files_windows, function: _file_get_size}. The parked bytes change, so the park must go in the same change.
3) Run the usual full ninja build and stable-verdict sweep. Only this TU changes and file_error is static, so nothing outside files_windows.obj is affected.
4) Optional ledger text for docs/object_matching_logs is in scratch/w/files_windows/LEDGER.md.
Optional recoveries (byte-inert, not needed for completion, not in the patch):
- p5.c/a3.c: January definition order plus close/get_position/set_position/get_eof/set_eof/create/delete/get_last_modification_date calling file_error(). With this, the emission order matches January 27/27.
- p6.c: file_error(function_name, file) parameter order as in the /Od build.
## evidence
Baseline: 26 exact, 1 residual (_file_get_size), 0 unwritten. The only difference was a callee-saved permutation: January has file in ESI and info in EBX; ours had them swapped.
/Od build: halo_cache_symbols.exe fn 0x8aef00 is file_get_size. Its frame order is info, full_path, result (char, initialised FALSE after the full_path memset), then data. The body is `if (GetFileAttributesExA) { *size = ...; result = TRUE; } if (!result) file_error(...); return result;`.
/Od build: fn 0x8ae8f0 is file_error. It stores GetLastError() into a named local slot [ebp-0xc] after info [ebp-8]. The Stian lift also holds XGetLastError() in a local, which corroborates this but is not byte proof.
The cause was found with Lane A's dbg32c tracer, copied to lab/dbg with a new breakpoint pair at 0x1072a527/0x1072a54c. That C2.dll code shows how priority is computed:
- The weight of a block is the number of webs referenced in it.
- A web that stays live through a block without referencing it loses that block's weight.
- m0 weights: entry 5, assert-fail 1, post-assert 2, error block 3. That gives info 17 against file 13, so info is coloured first and takes ESI (ESI wins ties over EBX).
- With the error_code local, the inlined file_error adds a web to the error block. Combined with the /Od single-exit result web, the error block weight reaches 5 and file is coloured first. That reproduces January's allocation.
Ablations, full-TU gate:
- Both changes: 27/27 EXACT.
- Single-exit only (a4): 26/1.
- error_code local only (a5): 26/1.
Both changes are required together.
Audit of a1.obj (brief section 4):
- gate --all --forbid-emitted-symbol _point_from_line3d: 27 exact / 0 residual / 0 unwritten.
- object_audit: PASS. 55 January symbols, 0 differ. The production base audit was FAIL(1), for get_size only.
- pdb_storage: 0 disagreements.
- surplus_identity: 0 candidate-only code COMDATs.
- provider_link: SELECTED-PROVIDER LINK PASS. The 6 surplus literals and D3D tables are the same as production.
- All 34 non-.text sections are byte- and relocation-identical to build/base. report.json data is already 1348/1348.
- objdiff-cli 3.3.1 on scratch project odproj/: code 4062/4062 (100%), data 100%, 27/27 functions.
- fake_match_scan: 0 leads.
- No entry for this unit in config/object_admission_rejections.json.
Negatives recorded (do not repeat):
- Name-count oracle, N=0..40: negative.
- Explicit sibling error block: no change.
- Const casts and non-const parameter: no change.
- Assert macro forms (?:, do-while, if/else, halt): no change.
- Parameter order, TU reorder and sibling file_error calls on their own: no change.
- Seven result-shape variants without error_code: residual.
- Call-valued result forms (v03, v06, mF) flip the allocation but leave result in BL.
## blockers
None blocking object completion. Owner disclosure: the error_code local in file_error is required for the match (strip test: without it the object stays at 26/1). It is a real, used local, not a dummy or decoration, and the first-party /Od build has it as a named slot. In that later PC build it is also used by an extra FormatMessage call. The effect is through the inlined helper's IL web, not the name-count oracle: file_error is defined after file_get_size, and the N=0..40 name sweep was negative. If the owner rejects the local, the fallback is to keep the park, with this ledger as the reopen evidence.
## reopen_criteria
Not applicable if landed. If the owner rejects the error_code local, reopen only with other first-party evidence of how January's file_error or its error block was shaped. The tracer (lab/dbg/prio2.py) needs the error-block weight to reach 5 while the post-assert block stays at weight 2.
## task notes
The previous run left no slug directory, so this run started fresh.\n\nFindings reusable elsewhere:\n(1) VC7 C2 compiles functions in definition order, except that a function calling a not-yet-defined TU function is deferred to the end of the TU. This was verified on our own object, and it recovers January's definition order from its emission order.\n(2) The allocator's block weight is the number of webs referenced in the block (C2 0x1072aac5..0x1072ab3a). A web that stays live through a block without referencing it loses that block's weight. A helper's locals, once inlined, therefore shift caller register allocation.\n(3) lab/dbg/prio2.py is a retargetable per-block priority tracer: gencfg.py WT/unit edited for this worktree. It runs the stock CL under Lane A's debugger and nothing on disk is patched.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\files_windows\LEDGER.md
@@ -0,0 +1,69 @@
# source/game/game
## verdict
NO_PROGRESS
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\game\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\game\\cand_declinit.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\game\\lab_order_before_setter.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\game\\lab_refresh.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\game\\lab_unproto_before.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\game\\namesweep.py",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\game\\xbe_find.py",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\game\\pe_find.py",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\game\\pe_dis.py"
]
## production_changes
None. source/game/game.c stays byte-identical to production, there is no production.patch, and the park config/parked.json 'source/game/game:_game_set_game_variant_from_name' stays as it is. Optional ledger-only note for the integrator: append the new evidence summary from C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\game\LEDGER.md to the park or to a docs/object_matching_logs ledger for game.obj. Lane-wide hazard to publish in the worker brief: in Git Bash, `gate.py --cflag /X` is rewritten by MSYS path conversion to `C:/Program Files/Git/X`. cl then ignores it as a source file (warnings D4024/D4027) and gate.py prints no warning. Use MSYS_NO_PATHCONV=1, otherwise every flag probe run from Git Bash is void.
## evidence
Baseline gate: 26 exact, 1 residual; the residual is `_game_set_game_variant_from_name [size 48!=96, relocs 2!=4, sha]`. object_audit on build/base game.obj: every other January section and symbol is ok, all data is ok, and the only candidate-only surplus is .drectve. So this one function is the whole-object blocker.
I read the earlier ledgers first: Lane B game_REPORT (30 shapes and flags), opus5 n1 (6 in-tree shapes and 11 lab forms), the rejected-hypotheses row and the park. None of those spellings was repeated.
New probes. Every one still gives 48 B / 2 relocations with the null test folded away:
(1) setter marked `__inline` and compiled with /Ob1. Re-run with MSYS_NO_PATHCONV=1; plain /Ob1 reproduces Lane B's 64 B.
(2) Name-count oracle sweep, 400 compiles: 0-199 opaque struct tags inserted before from_name, and 0-199 after the includes. A sanity #error insertion confirmed the text is really compiled.
(3) /Og-, /Ot /Og- /Ob2 and /Os /Og-: all of them switch inlining off, so no flag gives inlining without global optimisation.
(4) from_name moved before the setter. C2 defers it and emits it last, the way our game_tick is deferred, but it still folds.
(5) Setter shaped like the HEK sapien one, with a network refresh that uses the pointer again after a call.
(6) Declaration+initialisation form: `struct game_variant variant = *get(&temporary_variant, name);`.
(7) `__forceinline` setter (diagnostic only; forced inlining is banned anyway).
(8) Implicit declaration of the setter: impossible, cl stops with error C2371. An old-style `void game_set_game_variant();` declaration with from_name before the setter or at the end: both fold.
New cross-build evidence (executables read as data only):
- /Od halo_cache_symbols.exe: from_name at 0x58daf0 has ONE aggregate, `variant` (RTC descriptor, 216 B), and calls callee(name, &variant). This is a later signature and says nothing about January's second local.
- HEK sapien.exe: from_name at 0x618340 has one local and keeps `lea edx,[esp+8]; test edx,edx; jne` with a `push edx` memset arm.
- October 2276P.xbe, an LTCG build: from_name at 0x7da90 has `sub esp,0xd0` (two aggregates), keeps the copy and keeps `lea ecx,[esp+8]; test ecx,ecx`.
- All January images (exe, xbe, _CG, _instrumented) and October 2276betaP contain the same from_name body, byte for byte.
- The January PDB module 309 compile record for game.obj is FE/BE 13.00.9254, C, PentiumPro, the same as rasterizer.obj, so the toolchain is ours.
Conclusion: the dead frame-address test survives in five independent builds, including a link-time re-optimisation, while VC7.0 /O2 folds every spelling tried in 55+ experiments across lanes. That points to a front-end (source/IL) construct we cannot reproduce with admissible source, rather than to compiler state, build order or flags.
Side observation, with no byte or audit effect: our object's section order differs from January's. January's order matches the source order being the address order; our game_tick, at source line 433, is deferred to the end.
## blockers
Every admissible spelling ends at the same VC7 13.00.9254 result: the inlined setter's `&variant == NULL` test is folded, the intermediate copy is forwarded straight to the global, and the second 0x68 aggregate is dropped. That is 48 real bytes against January's 92, and the relocations are missing `_csmemset` REL32 @0x3a and one `_game_variant_global` DIR32 @0x35. Adding the dead branch by hand, an emission anchor, volatile, pragmas, asm or forced inlining would all be fake matching and are banned.
## reopen_criteria
Reopen only for one of these:
(a) A first-party January or October fragment of game.c, game.h or game_engine.h showing from_name, the setter, the getter declaration or the game_variant type.
(b) A front-end (C1 IL) level explanation of which node for the setter argument C2 does not treat as a constant frame address, for example from an undocumented C1 IL dump switch. Any such theory must turn the fold off in the plain one-local form (Lane B's lab_a) with no volatile, asm, pragma, dead branch or emission anchor.
(c) A live C2.dll trace of the inline-plus-fold pass that shows a gating condition natural source can reach.
Do not re-spend on name counts, source order or deferral, flags, inline keywords, call-site prototype forms, extra uses of the formal, the declaration+initialisation copy, or anything already listed in the Lane B and n1 ledgers.
## task notes
Resume check: scratch/w/game/ did not exist, so nothing from the cut-off run was lost for this unit.
Tracked files were not touched, and no ninja, configure or git-mutating command was run.
Lane-wide hazard: any gate.py --cflag sweep run from Git Bash without MSYS_NO_PATHCONV=1 silently compiles with the default flags. Other workers' flag-based conclusions from this lane may need a re-run.
The game.obj emission order differing from January's (game_tick deferred) is not checked by object_audit or the gate. If it is ever fixed by moving the function in the source, the move must first be checked against the declaration-count / name-count blast radius.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\game\LEDGER.md: 8 probes plus 3 cross-build readouts (/Od cache-symbols build, HEK sapien, Oct 2276P LTCG) and the PDB compile-record check, each recorded as it happened. Also the MSYS --cflag hazard and the section-order observation. Verdict: NO_PROGRESS, park kept, production unchanged.
@@ -0,0 +1,37 @@
# source/interface/interface
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_interface_get_weapon_hud_index"
]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/interface/cand_interface.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/interface/cand_interface_variantA.h",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/interface/production.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/interface/production_variantA_header.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/interface/symbols_static.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/interface/LEDGER.md",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/interface/gate_before.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/interface/gate_after.txt"
]
## production_changes
Apply only after the owner rules on the header question below. (1) scratch/w/interface/production.patch changes source/interface/interface.c: adds a `static long interface_get_weapon_hud_index(real *flashlight_power);` prototype in the '/* ---------- prototypes */' section; makes the definition `static`; makes render_debug_profile and interface_splitscreen_render `static` in both prototype and definition (January PDB-public False for both); drops `static` from the six profile_* data definitions (profile_game/frame/graph_value_count and _values; January PDB-public True for all six). The function body is unchanged. (2) scratch/w/interface/symbols_static.patch edits config/symbols.json lines 3535, 3539 and 3540 in place, adding `"static": true` to _interface_get_weapon_hud_index, _render_debug_profile and _interface_splitscreen_render. Do not re-serialize the file. Then regenerate csplit so the split storage becomes 3. (3) HEADER, owner ruling needed. Variant A (house-rule clean) also applies scratch/w/interface/production_variantA_header.patch, which removes `long interface_get_weapon_hud_index(real *flashlight_power);` from source/interface/interface.h. That regresses source/rasterizer/rasterizer_frame_statistics:_rasterizer_frame_statistics_draw (4176 B, EXACT today) through a pure declaration-count tie, so do not land it without an adjudication. Variant B leaves interface.h untouched (production.patch alone). It is exact with zero blast radius, but it keeps a public prototype for a function that is static in January. MSVC reports that extern-then-static redeclaration only at /W4 (C4211 'redefined extern to static'), and ISO C89 6.1.2.2 calls it undefined behaviour. So it is not self-admissible. (4) On landing either variant, retire the park entry config/parked.json 'source/interface/interface:_interface_get_weapon_hud_index'. Its reopen criterion is met: an independently attested source construct (January storage class) naturally selects the integer copy. (5) Separate and pre-existing: add a config/semantic_matches.json entry for unit source/interface/interface, function _render_debug_profile_stall_tick (strict exact: 272 padded bytes, 19 ordered relocations, normalized SHA-256 b5fe1f5e4c15b0b75271f037521af3e976d5a0b8a797f3563f4cb21ee11402b7). objdiff 3.3.1 reports 67.27% because our object carries compiler-local $L jump-table labels ($L29019..$L29023, $L29348, $L29353) and the split object has none. With that entry the object reports 18/18.
## evidence
January target: 448 B, 20 relocs, normalized SHA 0673f867b219cdbd399685310736ae3de8263479cb2104edd5c56f48930c6549. Production's only difference was the final `*flashlight_power = flashlight` at both duplicated exits: January copies through integer registers (mov eax,[ebp+8]; mov edx,[ebp-4]; mov [eax],edx), ours used x87 (fld/fstp). Lab work (scratch/w/interface/lab*.c, compiled with the unit's cflags): VC7 uses x87 exactly when the copy goes from a direct local/param/global into an indirection, and uses integer moves otherwise. Address-taken locals, /Op, declaration order (probe p1, /Od-attested order), a 4-byte array and a second source store are all inert. The decisive lab (lab15): identical bodies give an integer copy when the function is `static` and x87 when it is extern. An EXACT board function confirms this: the static object_damage_shield in damage.c copies `*shield_damage_reference = shield_damage` through integer registers with no null check. The January storage class is double-attested: _interface_get_weapon_hud_index is absent from the cachebeta publics (pdb_storage.py: PDB-public False), and HCEX.pdb gives `static long interface_get_weapon_hud_index(float *)`. Probe p2 (static, prototype moved to interface.c) gives EXACT 18/18, 144/144 instructions, no normalized differences. p3 (profile_* public), p4 (render_debug_profile and interface_splitscreen_render static) and p5 (original header kept) each also give 18/18. Final candidate cand_interface.c: gate 18/18 exact, and the _point_from_line3d emitted-symbol guard passes. Audit: object_audit shows every January-owned section ok, and the only symbol DIFFs are the three storage rows that symbols_static.patch fixes (split=2 until csplit is regenerated; the profile_* storage now matches). The .data profile table and all .rdata literals are strict EQUAL. Surplus is unchanged from production: sine/cosine COMDATs are IDENTICAL to actor_combat and provider_link passes for all 17 surplus symbols. fake_match_scan: 0 leads. objdiff 3.3.1 mini-project (scratch/w/interface/odproj): matched_code 5695/5966 vs production 5251, 17/18 functions at 100%; the remaining one is the pre-existing stall_tick under-credit. Header blast radius: the 18 other interface.h consumers were gated with the prototype removed; 17 give identical per-function rows, and rasterizer_frame_statistics_draw regresses (see blockers).
## blockers
The object is function-complete (18/18 strict) and passes every section-4 audit once the symbols.json static flags are applied. What remains is a policy conflict. The clean form removes the now-wrong public prototype from interface.h. That regresses _rasterizer_frame_statistics_draw (4176 B) through a pure declaration-count tie. The name-count oracle, used as oracle only and never landed, shows the function is exact only at exactly today's count: net -3..+44 was tested and only 0 works. Measured weights: extern variable = 1, `void f(void)` = 2, one-named-parameter prototype = 3. The genuine missing interface.h prototypes are interface_dispose, interface_dispose_from_old_map and interface_draw_fullscreen_overlays (imported by January game.obj and render.obj), and they only come in weight 2, so no genuine packet balances the removed 3. Making the model_* field signedness consistent was inert. The zero-regression form keeps the extern header prototype over a static definition (C4211 at /W4, undefined in C89), so it needs an owner ruling.
## reopen_criteria
Land when the owner either (a) accepts Variant B (keep the interface.h prototype over the static definition; C4211 extern-to-static), or (b) accepts Variant A together with an adjudicated rasterizer_frame_statistics_draw count-tie loss, or (c) a genuine, independently evidenced declaration of net weight +3 is found in the rasterizer_frame_statistics.c prefix (its own headers or TU) that keeps its draw function exact after the interface.h prototype is removed. The integrator must then run a full ninja build and a per-function regression sweep; aggregate counts can hide swaps.
## task notes
Root cause: VC7 copies a real from a local into `*param` through integer registers only when the function is static. Otherwise it uses x87. January's function is file-static; our source declared it extern. Making it static is the attested storage class, not decoration, and no other bytes change. Every probe, lab and oracle run is logged in the LEDGER with its files under scratch/w/interface/ (lab*.c + lab.py, dmg/, blast/ with per-consumer before/after gate outputs, odproj/ objdiff mini-project, copy_census.txt of board-wide real copies to out-pointers). The oracle dummy declarations under blast/rfs_*.c are measurement only and must never be landed. No tracked file was edited, and no ninja/configure/git-mutating command was run (only `git apply --check`).
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/interface/LEDGER.md
@@ -0,0 +1,115 @@
# source/cseries/profile
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_compare_profile_sections"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\profile\\final.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\profile\\final.obj",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\profile\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\profile\\storage_only.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\profile\\symbols.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\profile\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\profile\\od_551e50.txt"
]
## production_changes
This lands as two separate packets. Apply both patches with `git apply`; each passes `--check` (rc=0), and CRLF line endings are kept.
PACKET A: storage metadata from cachebeta.pdb publics. It can land NOW. No owner question is involved, and nothing in the object regresses.
(1) Apply scratch/w/profile/storage_only.patch to source/cseries/profile.c. It removes `static` from:
- the compare_profile_sections prototype (prototypes section, about line 414);
- the compare_profile_sections definition (about line 1586);
- `header_strings[...]` (line 433);
- `format_strings[...]` (line 439).
(2) Apply scratch/w/profile/symbols.patch to config/symbols.json. It edits four lines in place; do not re-serialize the file.
- line 1630 `_compare_profile_sections`: remove "static": true
- line 22481 `_header_strings`: remove "static": true
- line 22482 `_format_strings`: remove "static": true
- line 22952 `_profile_globals`: add "static": true
(3) Regenerate csplit.
Steps (1) and (2) must land together (atomic). Evidence: cachebeta.pdb lists these PUBLIC symbols at the matching addresses: _compare_profile_sections @0007E840, _header_strings @002DCD30, _format_strings @002DCD38. _profile_globals is not public, and only profile.obj references it. Regenerating the split into scratch changed only profile.obj's four storage classes; the other 832 objects are byte-identical.
PACKET B: the comparator body. It needs the OWNER RULING first; do not land it before that ruling.
scratch/w/profile/production.patch equals Packet A plus these body changes in compare_profile_sections:
- `int result = 0;` becomes `int result;`;
- add `else\n\t\t\t\t\tresult = 0;` after the `first_average<second_average` arm;
- add the same line after the `recent_elapsed_timebase<` arm;
- add a comment-only `/* BUG (preserved for exact matching): ... */` block in the default arm. The comment changes no bytes.
On landing, retire the park in config/parked.json: the entry with unit source/cseries/profile, function _compare_profile_sections (about lines 224-24x). Retire it in the same commit, because moved parked-function bytes fail PROGRESS.
There are no header edits and no other translation units are touched. _profile_frame_get_value's objdiff score of 91.46% is already credited through build/semantic_report.json (accepted_ledger/hidden_exact, internal jump-table labels), so it needs no semantic_matches entry.
## evidence
BASELINE: gate --all gives 43 exact / 1 residual. _compare_profile_sections is [size 304!=288, sha].
JANUARY BYTES:
- Every mode returns a constant block with no live result register.
- The invalid-mode path runs display_assert(844), then system_exit(-1), then `mov eax,[ebp+8]`. [ebp+8] is the dead first-parameter home, the same slot the average mode reuses as its fidiv spill.
- Production's `result = 0` initializer keeps a live EDI result. That adds push/pop and brings the size to 304.
/Od FIRST-PARTY (halo_cache_symbols.exe fn 0x551e50, dump in scratch/w/profile/od_551e50.txt):
- result is at [ebp-4], and its /RTCu shadow byte is at [ebp-0x15].
- The single epilogue calls _RTC_UninitUse("result").
- The explicit `result = 0` stores in the average and total modes are both present.
- There is a single exit.
- The compiler emits that check only when a path can leave `result` unset, so the original source declares `int result;` with no initializer.
- HCEX.pdb has no record for this function.
PROBES:
- P1: /Od shape. Size and relocations are now equal (288/8); only the second x87 compare differs. /Od's SSE comisd output is consistent with `<`, and January's fxch/test ah,5 bytes decide it.
- P2: `first_average<second_average` gives 44/44 EXACT.
- P3: the same body with `int result = 0;` stays non-exact because of the live register. No defined default value can reproduce `mov eax,[ebp+8]`.
- P4/final: dropping `static` on the 3 PDB publics changes no bytes and still gives 44/44 EXACT.
- P5: the storage-only packet, measured alone, gives 43/1 (same as baseline).
WHOLE-OBJECT AUDIT (final.obj):
- Against the current split: sections all ok, and object_audit fails only on 4 storage rows, which are split metadata.
- I ran csplit.exe with an edited copy of symbols.json into scratch/w/profile/split, then ran object_audit against that output with audit_with_split.py. Result: OBJECT AUDIT PASS. All 133 symbols match (name, storage, section, offset) and every January-owned section is ok.
- fn_equal: 128/128 January-owned sections equal against both the current and the regenerated split.
- provider_link against the regenerated split: PASS. The 13 surplus items are pooled literal COMDATs.
- surplus_identity: 0 code COMDATs.
- report.json: data 100%.
- fake_match_scan: 7 leads, the same as production (inherited QUERY_TIMEBASE asm); nothing new.
- No object_admission_rejections entry exists for this unit.
HISTORY: this exact body was found before and held, in opus5 w3 (p1) and in Lane D refresh (needs-ruling, exact_under_ruling). The Storage/PDB-public correction and the regenerated-split audit are new in this run.
## blockers
Only one blocker remains: an owner policy ruling. astra_90pct_rejected_hypotheses_20260920.md item 8 is a user exclusion that names "_compare_profile_sections's uninitialized return". claude_structural_owner_packet_20260923.md section 4 asks the owner whether to admit the RTC-attested rows as a first class; this function is one of them. No ruling appears in docs/ or memory.
Points for the ruling:
- The uninitialized read only happens after the fatal system_exit(-1), so no reachable path returns an indeterminate value.
- The first-party /Od build attests _RTC_UninitUse("result").
- The idiom already has a precedent: bitmaps _bitmap_format_to_a8r8g8b8 was accepted with it (w2 verifier R5).
- The BUG comment follows the methodology's original-bug policy.
Packet A (the storage metadata) is not blocked.
## reopen_criteria
If the owner lifts item 8 for RTC-attested post-fatal-assert uninitialised results:
- land Packet B (production.patch) together with Packet A;
- retire the park;
- run a full ninja build and the stable sweep.
The object should then be COMPLETE.
If the owner rejects it: keep Packet A landed and keep the natural 304B body parked. No further spelling search is warranted, because probe 3 shows that no defined default value can match. Reopen only on a changed owner policy.
## task notes
This run started fresh: scratch/w/profile/ did not exist, so the earlier cut-off run left nothing to recover for this unit.
New in this run:
(1) cachebeta.pdb publics show the storage metadata is wrong in 4 places:
- `_compare_profile_sections`, `_header_strings` and `_format_strings` were EXTERNAL in January;
- `_profile_globals` was static.
Correcting these is a separate, independently landable fix.
(2) An emulated split regeneration (csplit run into scratch only; no tracked file or build/ touched) proves the whole-object audit PASSES with final.c.
The comparator body itself was already found and held by earlier lanes. It stays behind the owner's item-8 ruling. The helper scripts are in scratch/w/profile/: storage_table.py, xref_census.py, emulate_audit.py, audit_with_split.py, split_compare.py, fn_equal.py and provider_link_regen.py.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\profile\LEDGER.md
@@ -0,0 +1,57 @@
# interface/progress_bar :: _draw_gravy_layer
## verdict
NO_PROGRESS
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\progress_bar\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\progress_bar\\g1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\progress_bar\\optional_gen_mask_coord.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\progress_bar\\st1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\progress_bar\\labv.py",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\progress_bar\\lab\\xvt2.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\progress_bar\\jcmp.py"
]
## production_changes
None required. Do not apply any change for credit.
OPTIONAL, zero credit (authenticity only): C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\progress_bar\optional_gen_mask_coord.patch (candidate file scratch/w/progress_bar/g1.c). It adds a gen_mask_coord prototype to progress_bar.c's own prototype block, replaces the four TEXCOORD1 SetVertexData2f calls in draw_gravy_layer with gen_mask_coord(0,0 / 640,0 / 640,480 / 0,480, progress), and drops the mask_position local. VC7 inlines the calls. The bytes of _draw_gravy_layer, _gen_mask_coord, _progress_bar_draw and _draw_gravy_base_layer are verified identical to production, and gate stays at 53 exact / 1 residual / 0 unwritten.
No park to retire. No park should be added unless the owner wants one; the function stays an honest residual.
For the integrator, storage evidence outside this function's scope: pdb_storage shows that draw_fullscreen_blur, set_blur_texture_coordinates, draw_gravy_screen_layer, progress_bar_screen_initialized and progress_bar_rendering_enabled were file-static in January (absent from cachebeta publics), but ours are external. Probe st1.c made blur, set_blur and rendering_enabled static: all code stays EXACT, but object_audit (which uses the split's storage=2) then flags them. Making draw_gravy_screen_layer static drops its emission (UNWRITTEN), because it has no in-TU user. Not proposed as a change: it needs an owner ruling on split-vs-PDB storage.
## evidence
Baseline gate: 53 exact, 1 residual (_draw_gravy_layer, 336 vs 352, sha), 0 unwritten. Frame 0x1c on both sides; relocation multiset 22/22.
There is no /Od readout: the later build replaced the progress screen (fn 0x666670, string 'the old loading progress screen has been replaced with glowy halo gravy'). HCEX.pdb has none of these functions.
New findings:
(1) The 16 bytes come from January holding each rect corner in a NON-rematerialisable variable. Each corner is loaded into scratch, copied to a carrier (e.g. mov ebx,ecx; push ebx) and stored to a spill home. x0 is reloaded from home [ebp-4] at vertex 4. Ours loads rect.* directly and re-reads rect.x0.
(2) Lab harness scratch/w/progress_bar/labv.py (lab/v0 reproduces production bytes exactly) shows the cause. VC7 applies cross-call CSE and remat of rect fields only because generate_gravy_rect (defined in the TU) has a no-capture summary. With generate extern, or passing rect to an unknown function (gesc/gext), CSE disappears. Adding corner variables assigned at first use (xvt2) then reproduces January's exact carrier+home signature.
(3) The summary is robust to every source-level perturbation tested: definition order (l2/l3/tsa/tsl), K&R and void* prototypes and prototype-scope tags (knr/m1/m2), casts (cast1/2), a real[4] array (arr1/2), a union (u1_full), __inline to_screen or generate (ti/gi), an empty inlined helper taking &rect (emp0/1), pointer-alias defs (e1), register (e3), dead stores (va/ds1), point-struct forms (f5/f6), direct D3DDevice_* calls (vh/vi/vj) and a nested block (ve).
(4) January's emission order is a clean callee-before-caller source order (to_screen, generate, create_noise, gen_cloud, gen_mask, draw_layer_int, ...). So generate was compiled first, and the missing summary has no found source cause. Even without the summary, the lab allocation stays reversed: 640-mask gets ebx and the corners get homes, while January homes 640-mask and carries the corners (xnv1-4).
(5) The orphan public gen_mask_coord(x,y,m) reproduces every TEXCOORD1 value (480*0.033333335f folds to 16.f). Its inlined use (g1) is byte-inert.
Real-TU probes: s1, g1, t1, t2, u1_full, st1. Every probe is in scratch/w/progress_bar/LEDGER.md.
## blockers
The remaining gap is an IL fact: corner values that VC7 cannot rematerialise from rect, with a specific register allocation. Every admissible spelling found collapses to production bytes. The only lab route that shows the pattern (no no-capture summary for generate_gravy_rect) has no source-level justification. It also still allocates differently (640-mask vs corners for ebx). Pragmas, fillers and forced escapes are rejected by house rules and were not tried as landings.
Object-level: the function residual blocks completion. The pdb_storage static-vs-external disagreements (5 symbols) and the D3D inline-copy storage rows (split 2 vs ours 3, where ours matches the PDB) are separate from this function.
## reopen_criteria
Reopen if any of these appears:
(a) A genuine source construct (not a pragma, filler or dummy escape) that removes generate_gravy_rect's no-capture summary at the draw_layer_int call while generate and to_screen stay exact.
(b) A measured VC7 lever that blocks remat or forward substitution of a local copied from an address-taken, non-escaping struct.
(c) A C2 allocator replay (lane-a scratch/res9/alloc-oracle) that identifies which IL gives the corner webs priority over the 640-mask web. With the summary removed, January then follows from t2-style corner variables.
(d) First-party source or map evidence of draw_layer_int's body, or of the in-TU user of the January-static draw_gravy_screen_layer (that user is also needed for storage completeness).
## task notes
Tools written for reuse: scratch/w/progress_bar/mk.py (swaps the function body into a copy of the unit and gates it), lab.py/labv.py (standalone lab compile with progress_bar's exact cflags; head/body/tail composition; the LABHEAD, GENTAIL and TSTAIL env switches), jcmp.py (aligned instruction diff against January with call targets normalised), vwscan.py (board scan for the carrier+home pattern; draw_gravy_layer is unique). No tracked files were edited and no ninja, configure or git commands were run. The six recorded prior negatives (S1, S3, gA, gC, gD, gH) were not repeated as landings; only S1 was re-measured once to confirm the record.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\progress_bar\LEDGER.md
@@ -0,0 +1,28 @@
# source/items/projectiles:.data(_projectile_update_section)
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\projectiles\\semantic_data_entry.json"
]
## production_changes
Append the JSON object in scratch/w/projectiles/semantic_data_entry.json to config/semantic_data_matches.json. It is a single-section entry: unit source/items/projectiles, symbol _projectile_update_section, allow_incomplete_unit true (same as the leaf_map precedent), measurements size 1548, relocation_count 6, normalized_sha256 7bfaf5fb0b0c9e0593153885a81788bc8b118a9651a60a8a94e5ffd9fd203cf2. If you prefer not to use allow_incomplete_unit, hold the entry until the object is complete. It credits exactly the 1548 unmatched data bytes (total_data 2092 minus matched 544).
## evidence
The January .data section [54] (owners _projectile_update_section @0 and _effect_marker_names @1528, both static, flags 0xc0400040, 6 DIR32 relocations) matches both build/base and the candidate. section_infos_equal returns True, and section_info_resolved against the symbols.json image addresses gives an identical snapshot. object_audit shows .data 1548/1548 with flags and alignment equal. pdb_storage reports 0 disagreements. Cause of the undercredit: January's split leaves the folded literals ??_C@_06IKLLLDHL@normal?$AA@ and ??_C@_07GFBFDLBM@gravity?$AA@ undefined (relocations @1528 and @1544) while our object defines them, and objdiff 3.3.1 has the known '$'-literal name defect. The rdata section is already at 100%.
## blockers
None for the data itself. The only open question is the integrator's choice between allow_incomplete_unit now and waiting for object completion.
## reopen_criteria
Re-measure the snapshot if the .data owners, string literals or symbols.json addresses change.
## task notes
No tracked files were edited and no ninja, configure or git-mutating commands were run; only `git apply --check` was used, and it is read-only. The mechanism is new: the x87-versus-integer emission order after an inlined point_from_line3d depends on whether a C parenthesis node wraps the t argument's load. The paren node survives a copy through a local, and it cannot be reached by casts, local-count changes (M8 or name-count) or constant folds. This probably explains the ai_debug `ai_debug_attractor_label_height` precedent too, and similar held paren sites such as collisions.c:1572, players.c:3037 and bipeds.c:4029. The board scan (scratch/w/projectiles/scan_copy.py, output scan_copy.txt) lists 19 January 'COPY,x87,filler' sites against 58 'COPY,filler,x87' sites, which gives a census of where paren-shaped sources may hide. The provider-link failures (3 NODUP providers) and the non-identical surplus _distance3d already exist in production base and block whole-object admission independently of _projectile_new.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\projectiles\LEDGER.md
@@ -0,0 +1,34 @@
# source/items/projectiles:_projectile_new
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\projectiles\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\projectiles\\cand_h1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\projectiles\\cand_h2.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\projectiles\\lab_pfull.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\projectiles\\lab_localrhsparen.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\projectiles\\od_projectile_new.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\projectiles\\LEDGER.md"
]
## production_changes
Optional source-quality fix, zero credit. Apply scratch/w/projectiles/production.patch to source/items/projectiles.c. The patch keeps CRLF line endings and `git apply --check` passes. It replaces the hand-expanded point_from_line3d, which breaks house rule 6, with the call the /Od build attests: `point_from_line3d((real_point3d *)&projectile->object.translational_velocity, &projectile->object.forward, definition->projectile.initial_velocity, (real_point3d *)&projectile->object.translational_velocity);`. It also drops the `real initial_velocity;` local. The object comes out byte-identical to build/base (only .debug$S differs). _projectile_new keeps sha 9d8477d2, which is the park base, so the park measurements stay valid and there is no build risk: build/base already emits _point_from_line3d, and projectiles is one of the 17 objects the owner relaxed. cand_h2.c is a second byte-identical option: it also drops the `attachments` and `underwater` locals to match the /Od local set. Keep the _projectile_new park, but you may update its evidence text as follows. Mechanism: January emits the x87 point_from_line3d chain one slot earlier, relative to the scenario_location_underwater argument lea/push, only when the initial_velocity load is wrapped in a parenthesis node. Reclassify it as instruction-scheduling, with a held exact candidate pending an owner ruling. Do not land lab_pfull.c or lab_paren.c: they fail the strip test.
## evidence
Baseline: gate 29 exact / 1 residual. The alndiff has 142/142 instructions and 16/16 relocations; the frame is 0xc on both sides. The only real difference is two emission swaps at +0x13b..0x148. January emits `fld st(0); fmul [esi+0x24]; lea eax,[esi+0x50]; push eax; lea ecx,[esi+0x48]; fadd [esi+0x18]; push ecx`. Ours puts lea before fmul and push ecx before fadd. /Od readout: projectile_new is at 0x69b3a0, found by the 'cont' imm32. The velocity update there is a real call to 0x42e0d0, which is point_from_line3d (v*t+p, returns result), with args (&velocity, &forward, scale, &velocity). The /Od scale multiplies initial_velocity by a later-build difficulty factor from 0x699b60, which January lacks. Precedent from the board scan (scan_copy.py): ai_debug.c code_00039990 has the same inline-then-call site. Its landed header comment records that the bare t argument is residual and the parenthesised one is EXACT. Probes, all recorded in LEDGER.md: inline point_from_line3d with the field as t (P1), the /Od local set (P7), pointer casts (P9), 1-7 unused declarations (P8), extra used locals for the M8 law (P14), `(real)` cast (P5), local copy as t (P6), `x*1.f` and `TRUE?x:0` folds (P13), and parens on the arming, detonation, p or v expressions (P11). All are byte-inert. A `double` t changes the instruction selection (P2); three field reads reload via aliasing (P3). Only an outer paren around the initial_velocity load reaches January: `(definition->projectile.initial_velocity)` and `((definition)->projectile.initial_velocity)` are all 30 EXACT, and so is `initial_velocity = (definition->projectile.initial_velocity);` passed through a local. `(definition)->...` and `(initial_velocity)` at the call stay residual. HCEX DIA2Dump lists only the enregistered param. The October PDB has publics only. No first-party string or name was found for an accessor macro.
## blockers
(1) Policy: the only spelling that closes the function is a decoration-only parenthesis around the initial_velocity load, or an inferred macro with no first-party-attested name. Owner ruling 2026-09-21 #4 and the brief's strip test reject it, so this needs an owner ruling. (2) Even if (1) were admitted, whole-object admission is blocked by pre-existing provider-side problems that affect production base equally. provider_link reports SELECTED-PROVIDER LINK FAIL (3) because NODUP hand copies exist for _distance3d (action_vehicle), _real_random (action_charge) and _valid_real_vector3d_axes2 (bored_camera). In addition, surplus _distance3d is not byte-identical to January's selected copy in action_vehicle.obj: they use a different x87 order (fld st(2)/fmulp st(3) against fld st(0)/fmul st(1)). January projectiles.obj references _distance3d as an undefined external.
## reopen_criteria
Reopen if the owner admits a parenthesised or named accessor at this site. The ready candidate is lab_pfull.c (or lab_localrhsparen.c): it scores all 30 EXACT and object_audit PASS. Also reopen if first-party evidence turns up a macro or expression that wraps the initial_velocity load, for example a later source line or an assert string. For whole-object completion, the provider NODUP copies of _distance3d, _real_random and _valid_real_vector3d_axes2 must also be converted to shared-header inlines (owner decision under the NODUP provider link law), and the _distance3d emitted body must equal action_vehicle's January copy.
## task notes
No tracked files were edited and no ninja, configure or git-mutating commands were run; only `git apply --check` was used, and it is read-only. The mechanism is new: the x87-versus-integer emission order after an inlined point_from_line3d depends on whether a C parenthesis node wraps the t argument's load. The paren node survives a copy through a local, and it cannot be reached by casts, local-count changes (M8 or name-count) or constant folds. This probably explains the ai_debug `ai_debug_attractor_label_height` precedent too, and similar held paren sites such as collisions.c:1572, players.c:3037 and bipeds.c:4029. The board scan (scratch/w/projectiles/scan_copy.py, output scan_copy.txt) lists 19 January 'COPY,x87,filler' sites against 58 'COPY,filler,x87' sites, which gives a census of where paren-shaped sources may hide. The provider-link failures (3 NODUP providers) and the non-identical surplus _distance3d already exist in production base and block whole-object admission independently of _projectile_new.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\projectiles\LEDGER.md
@@ -0,0 +1,34 @@
# source/rasterizer/rasterizer_geometry
## verdict
OBJECT_COMPLETE_CANDIDATE
## newly_exact_functions
[
"_uncompress_int32_to_real_vector3d"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_geometry\\cand.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_geometry\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_geometry\\cand.obj",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_geometry\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_geometry\\probes\\t7.txt"
]
## production_changes
1) Apply C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\rasterizer_geometry\production.patch to source/rasterizer/rasterizer_geometry.c. The patch keeps the file's CRLF line endings. `git apply --check` exits 0, and `patch --binary -p1` reproduces cand.c byte for byte. The only change is the body of uncompress_int32_to_real_vector3d: it adds `real value;` after `real_vector3d v;` and makes each int-to-real conversion its own statement (`value = (real)(long)(compressed<<21);` then `v.i = (value * (1.0f/1048576.0f) + 1.0f) * (1.0f/2047.0f);`; the same for j with <<21, and for k with <<22, 1/2097152 and 1/1023). The `compressed >>= 11` shifts, the constants and the by-value return are unchanged. No header, prototype, symbols.json or ABI change. 2) Retire the park in config/parked.json for unit source/rasterizer/rasterizer_geometry, function _uncompress_int32_to_real_vector3d, class instruction-scheduling (the entry near line 516). Run `python -B -m tools.campaign.unpark --write` after the full ninja, or remove the entry by hand. 3) Optional, following the lane's convention: flip config/config.json `source/rasterizer/rasterizer_geometry.c` (index 96) from NonMatching to Matching. This is a status edit and earns no credit. 4) Optional closeout ledger in docs/object_matching_logs, copied from scratch/w/rasterizer_geometry/LEDGER.md. If the owner prefers the arithmetic spelling from the later /Od build, use probes/t7.txt instead: the same staged temp plus `&0x7ff` masks and divisions. It is also EXACT.
## evidence
Baseline: gate reported 18 EXACT and 1 residual [sha]. January and ours both have 49 instructions; the only difference was that January emits `fadd __real@3f800000` before `shl eax,0x16`. Lab mechanism (probes/labptr.c, x1/x4/x5): VC7 places exactly 3 integer filler instructions between dependent x87 ops. January's fmul(i) group holds one integer node that emits no bytes, and adding a visible integer op in that position reproduces January's order. What moves the i-component fadd one slot earlier is staging the int-to-real conversion as its own statement stored to a real (t4). Every natural staging of that conversion is EXACT: a reused temp, three temps, in-place v.i staging, a temp initialised at its declaration, the temp plus /Od masks and divisions, and a temp for i only (t4-t9). More than 15 unstaged spellings stay residual, including every recorded negative plus the /Od masks and divisions alone. Also inert: double intermediates (they would emit qword constants, which January's relocations refute), casts, multiply or split shifts, copy variables, struct, array and pointer variants, 1-33 dummy declarations (oracle only), 1-8 unused locals, 1-8 live copies, and the /G5 /G6 /G7 /GB /Op /Ot /Os /Oi /QIfist /arch:SSE flags. Whole-object audit on cand.obj: gate 19/19 EXACT (gate_cand.txt). object_audit.py PASS: all 44 January symbols match, every January-owned section is ok, and the candidate-only surplus is identical to the production base (literal COMDATs plus the owner-admitted _fast_ftol). surplus_identity: _fast_ftol is IDENTICAL to January's selected copy in actor_combat.obj. provider_link: every surplus symbol PASS in both link orders. pdb_storage: 0 disagreements. build/report.json: data 864/864 (100%); code 3919/4054, and this closes the missing 135 meaningful bytes. No admission rejections or semantic entries exist for the unit. fake_match_scan: 0 leads. CL /Zs /W3: no new warnings. The change touches only one function body in one translation unit, with no header or ABI change. Callers in other translation units call the extern function, so there is no blast radius.
## blockers
None mechanical. Owner-review disclosure: the later first-party /Od build (fn 0x82ea80, 0x18-byte frame = v, RTC guard and GS cookie) has no scalar local. It computes cvtsi2ss straight into divss and writes `& 0x7ff` masks and divisions. So the staged `value` temp is not attested by /Od; it is inferred from January's own bytes. Every natural staging matches and no unstaged spelling does, and the file already stages values through a reused real (compress_real_to_int16: `z = (real)floor(z * 32767.5f)`). `value` is assigned and read three times, so it is not a filler or dummy local. The strip test removes it back to the parked residual, which is why it is disclosed for owner judgement.
## reopen_criteria
If the owner rejects the staged conversion temp, keep the park. Reopen only on first-party evidence of January's statement shape for this function, such as an earlier /Od build or source fragment. Do not respell with casts, parentheses or double constants; all are measured inert (see LEDGER.md P1-P14).
## task notes
Resumed fresh: scratch/w/rasterizer_geometry/ did not exist, so no earlier probes were lost. Every probe was appended to LEDGER.md as it ran. The mechanism finding may generalise to other x87/integer interleave residuals. VC7 fills exactly 3 integer slots between dependent x87 ops, and a code-invisible node (such as a conversion staged into a named real) shifts the integer stream by one slot. The research harness is scratch/w/rasterizer_geometry/probe.py, mkvar.py and lst.py. production.patch is CRLF-preserving; apply it with `git apply` or `patch --binary`.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\rasterizer_geometry\LEDGER.md
@@ -0,0 +1,71 @@
# __rasterizer_environment_fog_screen_begin (rasterizer/xbox/rasterizer_xbox_environment_fog)
## verdict
FUZZY_IMPROVED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_xbox_environment_fog\\candidate_production.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_xbox_environment_fog\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_xbox_environment_fog\\candidate_diag_DE_R3.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_xbox_environment_fog\\LEDGER.md"
]
## production_changes
Apply scratch/w/rasterizer_xbox_environment_fog/production.patch to source/rasterizer/xbox/rasterizer_xbox_environment_fog.c. It was checked with git apply against the current production file, and the result is byte-identical to candidate_production.c (== c8.c, CRLF preserved). No header, config, symbols.json or parked.json change for this function, and there is no park to retire (the function is not parked). The patch carries:
(1) The structural-lane fixes from c0: the shadowed previous_camera_matrix local (it was an uninitialised read), D3DCLEAR_TARGET_A, animation_period predicates, short offset / OR base-z, short width and height, (real)tan, scope moves, vsh_constants__texscale_1, nested chicago verify, and skinning count-first.
(2) New in this lane, each backed by January's bytes:
(a) near_density==far_density written as if/else instead of an early return (single-exit). VC7 jump-threads it to the same bytes, and it raises the alpha-site inline budget from 14 to 15, which equals the real_alpha_to_pixel32 cost. January's inline expansion of the helper follows with no header change, so ruling A is retired.
(b) `word flags = global_window_parameters.fog.screen->flags;` for the two local flags.
(c) `clear_z_buffer = !(A && B) && (A || (B && opaque_model_count > 0) || (water && draw_water))`, written with TEST_FLAG.
(d) The colour pointer declared in each arm, `real animation_time = animation_times[index];`, and (1.0f - animation_time) written in place.
(e) texture_transforms typed as real_vector4d[LAYERS][2] and filled by two calls to this TU's own set_real_vector4d.
(f) The first-time reseed as `real v = real_local_random(); layer_state = &...; layer_state->u = real_local_random(); layer_state->v = v;`.
(g) The rotation as `window->rotation -= (real)atan2(vector.j, vector.i) * -screen->rotation_multiplier;` (January shows fchs;fsubr). This spelling and `-= -(real)atan2(...)*mult` produce identical bytes; this one was picked. Reviewer: please check the unary-minus placement.
Zero strict credit: the function stays residual. Gate after the patch: exact 26 / residual 1 / unwritten 0, and only the screen_begin row changes.
## evidence
Baseline gate: residual 3904 [size 4000!=3904, relocs 232!=235, sha]. Candidate c8: [size 4032!=3904, relocs 240!=235, sha]. The only relocation-multiset difference from January is +5 _csmemcpy (the float-bit temps). The alpha helper's assert literals, display_assert/system_exit 7/7, 0.0/1.0 literals and the three previous_camera_matrix references now all match. By metric.py (slot-normalised LCS), unmatched January instructions go from 277 on the production base to 105 in c8. c8 adds no new text or COMDAT symbols (41/41 match the production base).
Key measurements:
- Probe harness probe.py: the alpha-site budget is 14 in c0; removing either early return raises it to 15.
- Cost lab costlab.py: real_alpha_to_pixel32 inlines at budget-15 points and not at 14, so its cost is exactly 15.
- In c1 the near==far path bytes, `pop esi;pop ebx;mov [flag],0;mov [model],0;pop edi;...;ret`, equal January 0x97f..0x993.
- The live C2 frame-table instrument (p2/, retargeted from Lane C / the structural lane) shows previous_matrix+projection_scale and the scale*cos temp now join January's entries.
Diagnostic c7_DE (adds rulings D and E plus the set_real_point2d change): [sha] only, size 3904==3904, relocs 235==235, frame 0x184 vs 0x180, 58 unmatched instructions. Every probe is recorded in LEDGER.md sections P0..P22 and FINAL.
## blockers
Strict exactness still needs owner rulings, plus residuals with no source form found yet.
Rulings needed:
- D: read float bits in place (`*(long *)&x`) in the five %f[%x] error dumps. January reads the float memory directly. Precedent: units.c:11494, which the /Od build attests. The /Od fog-screen body is a dx9 stub, so there is no per-site /Od attestation.
- E: the texscale [0][3] term is `dot_product3d((real_vector3d const *)&camera.position, &camera.forward)` in January; lab_dot.c reproduces January's x,z,y evaluation order exactly. This is a new point-to-vector view cast under rule 24. Precedent: decals.c:891, matrix_math.c:902/914, collisions.c:1400.
- R3: layers typed real_point2d with set_real_point2d at the offset reseed sites matches January's lea/int-copy packet. It would newly emit a _set_real_point2d COMDAT from a caller that is not exact, which ruling 5 forbids; provider_link passes.
Residual machine differences in the best diagnostic:
- Frame +4. vector joins the double-temp entry instead of January's phase/point entry. Decoding C2 0x1076b3ba/0x107159fb shows the merge conflict check is symmetric, so no source form has been found.
- Register picks: width/height/layer_count (January ecx/edx, ours edx/edi); the offset>0 index prologue (cx vs ax); the skinning pointer reload (ax vs cx); the stats block.
- Order differences: projection_scale multiply order (tan*far*aspect vs tan*aspect*far; declaration order and three spellings are inert); the green x87 term (2t computed first in January; five spellings inert); screen-constant zero-store interleave; point-setup store order; texture loop-bound load position; first-time add esp/fstp order.
Object-level blockers outside this function: the 7 SDK wrapper storage mismatches (the fix is proposed in the next unit), and the _real_local_random surplus COMDAT. That COMDAT differs from its NODUP provider effects.obj and fails the provider link with LNK2005, and it already fails on the production base.
## reopen_criteria
Reopen if any of these happen:
- Owner rulings on D and E (and R3, or an exact caller) arrive. Then resume from candidate_diag_DE_R3.c and attack the frame +4 first, using the frame-table instrument (ft.sh) plus conflict-set logging at C2 0x1076b564/0x107159fb.
- New first-party evidence (for example a later build that has a fog-screen body) shows the scope or form of vector, phase or point, the projection_scale expression, or the skinning store form.
- A decoded C2 rule explains January's vector/phase/point overlay alongside the separate double-temp entry.
## task notes
Tools written in the slug:
- probe.py (inline-budget probe) and costlab.py (helper-cost calibration).
- metric.py (slot-normalised distance) and lay.py (/FAsc named layout).
- ft.sh plus p2/ (live C2 frame table, retargeted to this worktree).
- xf.py (source transforms), object_audit_split.py (audit against a scratch split), and cfg/ + split/ (the scratch csplit).
I disassembled C2.Dll as data only, to read the slot-merge rule; nothing was patched. No tracked file was edited, and no ninja, configure or git-mutating command was run. The temporary git init used for patch-apply testing was inside a scratch subdirectory and has been deleted.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\rasterizer_xbox_environment_fog\LEDGER.md. Every probe is logged there (P0-P22 and FINAL): hypothesis, the January-byte evidence, the change and the measured result. Main result: the "alpha inline" owner blocker (ruling A) was a .c-local defect. The early return on the near==far density path cost one inline-budget step. Rewriting it as a single-exit if/else gives the same bytes on that path and lets real_alpha_to_pixel32 inline exactly as January does. Four more January-attested fixes landed in the admissible candidate:
- the flags word local;
- the decoded clear_z expression;
- the colour-arm pointer and time local;
- the texture transforms built by this TU's set_real_vector4d.
Also the first-time reseed order and the rotation sign form. Admissible candidate c8: the only relocation difference from January is +5 csmemcpy (ruling D), and slot-normalised unmatched instructions drop from 277 to 105. The diagnostic that adds rulings D and E and the set_real_point2d change reaches [sha]-only at size 3904==3904. Also proposed: an admission fix in symbols.json (7 SDK wrappers marked static), verified by a scratch csplit.
@@ -0,0 +1,42 @@
# rasterizer_xbox_environment_fog object: SDK D3D wrapper symbol storage (split metadata)
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_xbox_environment_fog\\cfg\\symbols.json",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_xbox_environment_fog\\split\\source\\rasterizer\\xbox\\rasterizer_xbox_environment_fog.obj",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\rasterizer_xbox_environment_fog\\object_audit_split.py"
]
## production_changes
In config/symbols.json, edit these 7 lines IN PLACE, without re-serialising: append `, "static": true` before the closing brace on lines 5766, 5767, 5769, 5774, 5779, 5780 and 5781. By file_offset: 1396688 _D3DDevice_SetRenderState, 1397120 _D3DDevice_SetTextureStageState, 1397232 _IDirect3DDevice8_SetRenderState@12, 1397920 _IDirect3DDevice8_SetTextureStageState@16, 1398464 _IDirect3DDevice8_SetVertexData2s@16, 1398480 _IDirect3DDevice8_Begin@8, 1398496 _IDirect3DDevice8_End@4. Then regenerate the split (csplit only). The exact edited copy is scratch/w/rasterizer_xbox_environment_fog/cfg/symbols.json.
## evidence
object_audit on both the production base and c8 flags these 7 January symbols with storage split 2 vs ours 3. pdb_storage.py shows none of them is a cachebeta.pdb public, so file-static is January's truth. The same entries in other objects already carry "static": true (for example lines 5591-5599 and 5830-5840), and precedent ledger rasterizer_xbox_sdk_names_and_private_reciprocal_20260906 covers the same fix. A scratch csplit (build/tools/csplit.exe -i cachebeta.exe -p cfg -o split) changed only the fog split object out of 833, and all 58 of its sections are section_infos_equal to build/split. With that split, object_audit_split.py reports 78/78 January symbols matching for both the production base and c8, leaving screen_begin as the only failure. Gate rows are unaffected because section bytes and relocations are identical.
## blockers
On its own this does not complete the object. screen_begin is still residual (see the first unit), and the _real_local_random surplus COMDAT still fails the selected-provider link against effects.obj (NODUP). That second item needs an owner decision under the NODUP provider link law.
## reopen_criteria
Apply it together with any future screen_begin closure. If the integrator's full build shows any objdiff or progress regression from the storage change, revert it and record the result.
## task notes
Tools written in the slug:
- probe.py (inline-budget probe) and costlab.py (helper-cost calibration).
- metric.py (slot-normalised distance) and lay.py (/FAsc named layout).
- ft.sh plus p2/ (live C2 frame table, retargeted to this worktree).
- xf.py (source transforms), object_audit_split.py (audit against a scratch split), and cfg/ + split/ (the scratch csplit).
I disassembled C2.Dll as data only, to read the slot-merge rule; nothing was patched. No tracked file was edited, and no ninja, configure or git-mutating command was run. The temporary git init used for patch-apply testing was inside a scratch subdirectory and has been deleted.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\rasterizer_xbox_environment_fog\LEDGER.md. Every probe is logged there (P0-P22 and FINAL): hypothesis, the January-byte evidence, the change and the measured result. Main result: the "alpha inline" owner blocker (ruling A) was a .c-local defect. The early return on the near==far density path cost one inline-budget step. Rewriting it as a single-exit if/else gives the same bytes on that path and lets real_alpha_to_pixel32 inline exactly as January does. Four more January-attested fixes landed in the admissible candidate:
- the flags word local;
- the decoded clear_z expression;
- the colour-arm pointer and time local;
- the texture transforms built by this TU's set_real_vector4d.
Also the first-time reseed order and the rotation sign form. Admissible candidate c8: the only relocation difference from January is +5 csmemcpy (ruling D), and slot-normalised unmatched instructions drop from 277 to 105. The diagnostic that adds rulings D and E and the set_real_point2d change reaches [sha]-only at size 3904==3904. Also proposed: an admission fix in symbols.json (7 SDK wrappers marked static), verified by a scratch csplit.
@@ -0,0 +1,81 @@
# source/saved games/saved_game_files
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/saved_game_files/production.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/saved_game_files/production_storage.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/saved_game_files/production_bug.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/saved_game_files/final/saved_game_files.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/saved_game_files/final/saved games/saved_game_files.h",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/saved_game_files/final/symbols.json",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/saved_game_files/cand.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/saved_game_files/LEDGER.md"
]
## production_changes
There are two independent patches. Both pass `git apply --check` against the current worktree. production.patch is the two combined.
(A) production_storage.patch: ADMISSIBLE NOW. It gains zero code credit and changes no code bytes. It is an ownership correction.
- source/saved games/saved_game_files.c: add `static void enumerate_memory_units(\n\tvoid);` at the end of the private `/* ---------- prototypes */` block, after add_new_entry_to_mapfile. Make the definition `static void enumerate_memory_units(`.
- source/saved games/saved_game_files.h: delete the public `void enumerate_memory_units(\n\tvoid);` prototype. Keep enumerate_memory_units_test.
- config/symbols.json line 7376: edit IN PLACE to `{ "file_offset": 1788672, "flags": 32, "name": "_enumerate_memory_units", "static": true },`. Do not re-serialize the file. Keep CRLF.
- Then regenerate the split only (csplit), then run the full ninja build and the stable sweep.
Expected result: saved_game_files stays 45/1, with an object byte-identical to build/base apart from .debug$S. The 6 other includers of the header are byte-identical in code, data and relocations.
(B) production_bug.patch: NEEDS THE OWNER ORIGINAL-BUG RULING. Do NOT land it without that ruling. It is already queued in claude_structural_owner_packet_20260923.md section 4.
- In saved_game_files_enumerate_available_to_local_player_index, replace `long number_of_available_profiles = 0;` with the methodology `BUG (preserved for exact matching)` comment plus `long number_of_available_profiles;`.
- Insert `number_of_available_profiles = 0;` immediately after `number_of_entries = count_enumerated_profiles_in_mapfile(memory_unit_index);`.
If both are applied under the ruling:
- retire the park entry for _saved_game_files_enumerate_available_to_local_player_index in config/parked.json (around line 1208, via tools.campaign.unpark);
- promote config/config.json line 398 `source/saved games/saved_game_files.c` from NonMatching to Matching after the full build, sweep and audit.
Three functions are proven external and must NOT be made static, even though pdb_storage flags them: _saved_game_files_take_mapfile_mutex, _saved_game_files_release_mapfile_mutex and _saved_game_files_enumerate_default_files. They are absent from the cachebeta publics, but a lab test showed VC7 /O2 does not emit an unreferenced static function (it came out UNWRITTEN). Because January has them, they were external.
## evidence
Baseline gate: 45 exact / 1 residual.
The only difference from January:
- ours: `push edi; xor edi,edi` in the prologue;
- January: `push esi; push edi` at +0x7c and `xor edi,edi` at +0x94, inside the outer-mutex success branch;
- January's mutex-failure tail at +0x156 is `mov dx,word ptr [ebp+8]` then a store to *number_of_profiles.
[ebp+8] is player_index's parameter home. It is reused only on the success path for number_of_entries. On the failure path January therefore stores an indeterminate count. The only defined value in that slot is the caller's player_index.
The /Od readout of fn 0x8646c0 (od_enumerate_available.txt) zeroes the accumulator [ebp-8] twice: at the declaration (0x8646e6) and again right after the count (0x864769). January's xor sits at that second position. This agrees with the Lane D 20260922 finding that the declaration initialiser was a later fix.
The prior ledgers (checkpoint 20260908, next150_n1 20260915, lane_d manifest 20260922) already refuted the alternatives from the bytes: declaration init, zero in both arms, store inside the success branch, self-store. I did not repeat them. Reusing player_index as the counter contradicts /Od, whose player_index slot is never written, and would be nonsensical source, so I did not compile it.
Results:
- cand.c (BUG form): `== exact 46 residual 0 unwritten 0`.
- final/cand2 (BUG form plus the static fix, compiled with a shadow-header gate copy, verified with an #error probe): 46/46.
- /W3: no C4211 or C4013.
- object_audit against a simulated static split: PASS, 152 symbols, 0 differ. Against the current split it differs only by the expected storage 2/3 on _enumerate_memory_units.
- surplus_identity: 0 code COMDATs.
- provider_link: PASS on all 11 surplus symbols (8 pooled literals, 3 stock D3D SDK tables).
- fake_match_scan: 0 leads.
- report.json data is already 100% (.bss 540, .data 8, .rdata 5608).
- Header blast radius on game, player_ui, ui_widget, virtual_keyboard, player_profile and playlist_profile: identical rows and identical section bytes and relocations. Only .debug$S and internal $L numbering differ.
- cachebeta publics census: _enumerate_memory_units is referenced only inside this object and is absent from the publics, so it is file-static in January.
Full log: scratch/w/saved_game_files/LEDGER.md.
## blockers
The only remaining gap is policy, not machine code. The exact function needs the owner's original-bug ruling to preserve the indeterminate count on the outer-mutex failure path (house rules 20/21). It is queued as group A in claude_lane_d_refresh_manifest_20260922.json and in claude_structural_owner_packet_20260923.md section 4. I found no ruling on it in docs/ through 20260924. Until that ruling, only production_storage.patch can land, and the object remains 45/46.
## reopen_criteria
Land production_bug.patch only if the owner approves preserving this authentic January defect (BUG comment already written), or if authoritative evidence appears for a defined original failure result. Any defined source must reproduce +0x156 `mov dx,word ptr [ebp+8]`, which the recorded negatives show is unreachable without the uninitialised count.
## task notes
Fresh start: scratch/w/saved_game_files/ did not exist before this run. Production saved_game_files.c is byte-identical to the Lane D candidate.c of 20260922, so that lane's probe_pc_od (residual) and probe_ub_ruling (EXACT) results still hold. I re-measured them at this base.
Integrator note on header edits: gate.py copies the source into scratch/ before compiling, so an edit to a shared header cannot be tested through it. I used scratch/w/saved_game_files/gate_shadow.py, which compiles from final/, so the shadow `saved games/saved_game_files.h` there takes precedence.
pdb_storage will keep reporting the three unreferenced functions as disagreements after the storage patch. They are proven external: a static unreferenced function is not emitted.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/saved_game_files/LEDGER.md
@@ -0,0 +1,85 @@
# source/structures/structures
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_sphere_intersects_cluster_portal"
]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/structures/production.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/structures/final.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/structures/final_body.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/structures/final.obj",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/structures/LEDGER.md",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/structures/od_sphere_portal.txt",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/structures/hcex_debug_sphere_ppc.txt"
]
## production_changes
Apply C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/structures/production.patch. It uses a/ b/ prefixes and `git apply --check` is clean. It touches only source/structures/structures.c and replaces the whole body of sphere_intersects_cluster_portal (lines 328-406). There are no header, config, symbols.json or parked.json changes, and no park to retire because the function was never parked. The new body:
(1) renames the third parameter `position` to `point` (the HCEX.pdb name).
(2) Declares `real plane_distance = plane3d_distance_to_point(<nested collision-bsp plane lookup>, point);`.
(3) Uses one nested test: `if (fabs(plane_distance) < radius && distance_squared3d(point, &portal->centroid) < (bounding_radius + radius) * (bounding_radius + radius)) { ... }`.
(4) Declares these inner-block locals in /Od order: `double t = -plane_distance;`, `real_point2d projected_points[MAXIMUM_VERTICES_PER_CLUSTER_PORTAL];`, `real_plane3d const *plane = TAG_BLOCK_GET_ELEMENT(&global_bsp3d_get()->planes, portal->plane_index, real_plane3d);`, `short projection = projection_from_vector3d(&plane->n);`, `short projection_sign = projection_sign_from_vector3d(&plane->n, projection);`, `real_point3d projected_centroid3d;`, `real_point2d projected_centroid2d;`, `short vertex_index;`.
(5) Calls `point_from_line3d(point, &plane->n, t, &projected_centroid3d);` as a separate statement, then `project_point3d(&projected_centroid3d, projection, projection_sign, &projected_centroid2d);`.
(6) Keeps the existing vertex projection loop.
(7) Ends with `if (convex_hull2d_test_circle(...)) { return TRUE; }` inside the block and one `return FALSE;` at the bottom.
After applying: run a full ninja build and the stable per-function sweep. The change is .c-local, so only structures.obj should move. Expected gate is 17/17. HOLD FOR RULING: `double t` is the load-bearing construct. docs/common_constants.md says to use `double` only where an API requires it; see blockers.
## evidence
Baseline gate: exact 16, residual 1: `_sphere_intersects_cluster_portal [size 416!=432, sha]`.
The w2 v5 body, measured again on current production (P01), gives 432/11 [sha] with 182/182 instructions. It differs from January only in three single-slot integer/x87 interleaves (movzx ax,al; push eax; lea eax,[ebp-0xc]).
/Od readout FOUND. The later build's version is at halo_cache_symbols.exe 0x8a0670; the earlier od_cone.txt was a different function (0x8a0c90), not this one. It gives:
- the statement order;
- a real standalone point_from_line3d call;
- `short` projection and `short` sign (movzx ax,al; mov word);
- the plane pointer local;
- the RTC names projected_points, projected_centroid3d and projected_centroid2d, which HCEX.pdb corroborates along with the parameter name `point`.
P02 is the full /Od shape: bytes identical to P01, so declaration order, nested vs standalone call and plane vs normal local are all inert. P03, a named sqrt local, is also inert.
Oracle labs all left the interleave unchanged:
- k=1..7 unused in-function dummy locals;
- k=1..16 TU-level declarations;
- k=1..7 used alias locals (these flip the M8 +0x5d term but not the interleave);
- a named `real` negated-distance local;
- hand-written rows.
The interleave is not a name-count, declaration-count or M8 tie. The exact sibling collision_prism_test_point (same two statements, float `-distance`) shows the separated dup/int/fmul form our build emits.
Hand-derived slot model (F04): add one no-code node between each lazy `fld st(0)` and its fmul. Applied to our schedule, this reproduces January's order exactly. That predicts a per-use double-to-real conversion, the units throw_grenade_release FP-intermediate-type lever.
Lab TG1 (`double lab_t = -plane_distance;` with a `(real)` cast) was EXACT. Strip test TG2 (no cast) was also EXACT, so the cast is inert. TG3 (a `double plane_distance` in the outer scope) changes the frame and is not January. C1 and C2 (declaration at block top vs. assignment just before the call) both gate the whole TU at exact 17 / residual 0.
Final final.c audit:
- gate 17/17 EXACT; relocdiff 11/11 with 0 differing rows.
- object_audit PASS: all 26 January symbols and sections match, and the surplus list is identical to production's.
- surplus_identity: 12/12 COMDATs identical to January's selected copies.
- pdb_storage: 0 disagreements.
- Data is already 100% in build/report.json (2292/2292).
- fake_match_scan: 0 leads.
- No rejections or parks for the unit.
- /W3 adds one C4244 (double to real argument) and two C4244 (short to boolean). The build uses the default warning level.
## blockers
(1) OBJECT LINK: provider_link.py fails in 2 places (LNK2005 in both orders).
- `_project_point3d` conflicts with the NODUP hand copy in source/ai/path_obstacles.c (`#define project_point3d project_point3d_inline` plus an ordinary definition).
- `_projection_sign_from_vector3d` conflicts with the NODUP hand copy in source/effects/decals.c.
The current production build/base structures.obj fails identically, so this is pre-existing and provider-side, not caused by this change. The recorded fix is an owner decision to make these provider copies header inlines (see NODUP provider link law; nodup_census reports NODUP in path_obstacles.obj and decals.obj). For project_point3d only, the decals.c precedent `#define REAL_MATH_EXTERNAL_PROJECT_POINT3D` would suppress the COMDAT. That was not measured and needs an owner decision.
(2) POLICY RULING: the exact form depends on `double t = -plane_distance;` (strip test: `real t` does not match; the `(real)` cast is inert and was dropped).
- Against: docs/common_constants.md limits `double` to genuine API or vararg needs, and the later /Od build passes -plane_distance directly with no 8-byte slot.
- For: owner-landed production precedents with the identical idiom, source/ai/actor_moving.c:2136 `double t = -distance_along_step;` (commit ea507d5b) and source/units/units.c:11087 `double origin_scale` (the throw_grenade_release closure). HCEX PPC (fneg plus fmadds, no frsp) is neutral.
The owner or orchestrator must admit or reject the double-t idiom here.
## reopen_criteria
If the `double t` idiom is rejected: the best non-double candidate is P02 (scratch/w/structures/p02_body.txt; /Od-attested, 432/11 [sha], 6 insertion/deletion rows in alndiff, zero credit). Reopen it only with a source construct that creates a no-code node between each lazy dup of t and its fmul, i.e. a per-use conversion, without a double variable. The object link blocker closes when the owner converts the path_obstacles.c and decals.c NODUP hand copies to header inlines, or otherwise lets structures.obj's SELECT_ANY copies pair-link. Then re-run provider_link.py and object_audit.py on the applied tree.
## task notes
The resume found almost nothing banked: LEDGER.md had only a header, and the v5.c/v5_body.txt/splice.py/od_cone.txt files were all that survived. Every step since has been appended to the ledger (P01-P03, lab oracles D1/D4/H/U/L5/L6/VA/TG1-3, C1/C2, F01-F06, audit, outcome).\n\nThe lever is new for this unit: a double-typed simple variable passed as a real inline argument. The inliner then substitutes a per-use conversion, which occupies an x87 slot and emits no code. This moves the integer ops one slot later after each lazy `fld st(0)` dup. The same mechanism explains the units throw_grenade_release closure and the actor_moving `double t` fidelity landing.\n\nCorpus tools (dupscan.py, dupctx.py, sepctx.py in the slug dir) list other January functions with the adjacent-dup form. The non-exact ones are actions/actor_moving/projectiles/bipeds/vehicles/collision_bsp/convex_polygon2d_clip_to_plane; see dupscan.txt. They may be candidates for the same lever once the owner rules on it.\n\nNo tracked file was edited and no ninja or git-mutating command was run. `git apply --check` was used on the patch only.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/structures/LEDGER.md
@@ -0,0 +1,110 @@
# source/bungie_net/network/transport_endpoint_winsock
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_connect_endpoint_process@4 (in cand_final.c only; landing needs the owner ruling)"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\transport_endpoint_winsock\\cand_final.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\transport_endpoint_winsock\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\transport_endpoint_winsock\\storage_only.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\transport_endpoint_winsock\\storage_only.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\transport_endpoint_winsock\\symbols.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\transport_endpoint_winsock\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\transport_endpoint_winsock\\october_connect_endpoint_process.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\transport_endpoint_winsock\\cand_final_audit_regen_split.txt"
]
## production_changes
There are two packets. Both pass `git apply --check` against the worktree.
PACKET 1: storage fix. Byte-inert and landable now, because it is metadata that cachebeta.pdb proves.
(a) Apply scratch/w/transport_endpoint_winsock/storage_only.patch to source/bungie_net/network/transport_endpoint_winsock.c. It makes three changes:
- The prototype `void connection_thread_list_maintenance(void);` becomes `static` and moves into the static-prototype group. The blank line before it is removed.
- The definition of connection_thread_list_maintenance becomes `static`.
- `static struct transport_endpoint_winsock_globals transport_endpoint_globals = {0};`
(b) Apply scratch/w/transport_endpoint_winsock/symbols.patch to config/symbols.json. It is two in-place line edits; do not re-serialize the file:
- line 1396: `_connection_thread_list_maintenance` gets `, "static": true`
- line 22943: `_transport_endpoint_globals` gets `, "static": true`
(c) Regenerate the split with csplit only.
(a) and (b) must land in the same change. Production against the regenerated split fails the audit with 3 errors if only one side lands.
PACKET 2: owner-gated. Do NOT land without an owner ruling.
- Instead of storage_only.patch, apply scratch/w/transport_endpoint_winsock/production.patch. It equals packet 1 plus one change in connect_endpoint_process: `struct thread_reference *thread = NULL;` becomes `struct thread_reference *thread;`, with the comment `/* BUG (original): January and October leave thread unassigned when take_mutex() fails ... A corrected build should initialize thread to NULL. */`.
- Apply it together with symbols.patch, then regenerate the split with csplit.
- On landing, retire the park config/parked.json key `source/bungie_net/network/transport_endpoint_winsock:_connect_endpoint_process@4` (around line 1527).
- Update the section 4 row of docs/object_matching_logs/claude_structural_owner_packet_20260923.md with the new evidence: October byte-identical corroboration and the bounded search of defined alternatives.
No header, config.json, relocs.json or semantic-match change is needed.
## evidence
Baseline gate --all: 29 EXACT, 1 residual (_connect_endpoint_process@4 [sha]).
What alndiff shows:
- January has no local frame. `thread` is homed in the dead parameter slot [ebp+8], stored only on the take_mutex success path (+0xdf).
- The failure path (`or ebx,-1` at +0xe4) has no store, yet +0x110 reads [ebp+8] for the `if (thread)` test.
- Ours adds `push ecx` and `mov [ebp-4],esi`. It also uses esi as a zero register in the asserts (the LAW Z knock-on) and adds `mov esp,ebp`.
Probe 1 (lab_uninit.c): dropping only the NULL initializer gives 30/30 EXACT.
Independent-build corroboration (new):
- October 2001 oct-betaP.xbe was analysed as data only with xbe_find.py and capstone.
- The string "input->thread" is at VA 0x266c80 and is used by the function at 0x84080.
- With January's 25 relocation fields masked, the two bodies are byte-identical: 0 of 0x126 meaningful bytes differ.
- So the uninitialized read is an original source property in both builds.
- HCEX.pdb has no transport_endpoint symbols. The later /Od build removed the function; its only "input->thread" hit is the sister dns_lookup_thread_proc.
Bounded search of defined alternatives, all residual:
- A (production): NULL at declaration.
- B (lab_B_else_null.c): NULL only in the else arm. VC7 hoists it to entry, so the result is identical to A.
- D (lab_D_null_before_mutex.c): NULL as a statement after connect_endpoint. The frame goes away, but an extra `mov [ebp+8],0` remains.
- Every defined form needs a NULL store that neither build contains, so this is a policy question, not a codegen search.
Storage findings:
- pdb_storage flagged `_connection_thread_list_maintenance` and `_transport_endpoint_globals`. Both are external in split and ours but absent from cachebeta publics, so they were file-static in January.
- No other split object references either one (scan of undefined symbols).
- storage_only.c: gate is 29/1 as before. secdiff shows 0 non-debug sections differ; only the two storage classes change from 2 to 3.
- Scratch csplit with the edited config copy: of 833 objects, only transport_endpoint_winsock.obj differs, and only in those 2 storage rows.
Whole-object audit of cand_final.c:
- gate: 30/30 EXACT.
- /W3 /WX: clean.
- fake_match_scan: 0 leads.
- object_audit against the regenerated split: PASS, 135 January symbols, 0 differ.
- pdb_storage emulation: 0 disagreements.
- surplus_identity: 0 candidate-only code COMDATs.
- provider_link: PASS against both the current and the regenerated split. The surplus is 3 pooled literals from transport_address and 3 D3D tables, the same set as production.
- Data in build/report.json: .bss 524 and .rdata 2040, both 100%.
- No object_admission_rejections entry exists for the unit.
- Note: 5 inherited exact functions show objdiff <100% under 3.3.1 ('$'-literal defect) but are already credited via semantic-coff in build/semantic_report.json. Expect the same path for connect_endpoint_process.
## blockers
The only blocker is an owner ruling on the original-bug row `_connect_endpoint_process@4` in docs/object_matching_logs/claude_structural_owner_packet_20260923.md section 4. Brief section 2 says an authentic January bug may be reproduced only with target and layout proof, a BUG comment and owner approval, so I reported it rather than landing it.
The playbook's 'Original bugs' gate (docs/exact_match_acceleration_playbook.md) is now fully met:
- January's control flow proves the read.
- The October build is byte-identical, so an independent build corroborates it.
- Defined alternatives A, B and D were measured and none matches.
Packet 1 (storage) has no blocker.
## reopen_criteria
No source or spelling search remains: no defined C can omit the NULL store. Reopen only for one of these:
(1) The owner approves the section 4 row. Then apply production.patch plus symbols.patch, regenerate the split with csplit, and retire the park. The object is then complete.
(2) The owner rejects it. Then land only storage_only.patch plus symbols.patch and keep the park, with the October evidence added to its note.
(3) Evidence from a new first-party build shows an initialized `thread` together with the no-store topology. That would contradict both January and October.
## task notes
This slug had no earlier ledger (a fresh start after the reset), so no prior work was lost.
Other workers can reuse two tools:
- scratch/w/transport_endpoint_winsock/xbe_find.py finds the function that references a given string in an XBE and disassembles it. It reads the file as data and never executes it.
- The October image research/oct-betaP.xbe is a practical second-build source for policy questions about original bugs.
The storage packet is independent and adds zero bytes. The integrator can land it even if the owner rejects the bug row.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\transport_endpoint_winsock\LEDGER.md
@@ -0,0 +1,35 @@
# source/interface/ui_widget_game_data_input_functions::_solo_level_select_list_update_displayed_items
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/ui_widget_game_data_input_functions/oracle/run.sh",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/ui_widget_game_data_input_functions/LEDGER.md"
]
## production_changes
None to land; keep the park. Suggested evidence-text update for the integrator: the residual is a TU-global name-count tie, not instruction scheduling. The body is already correct and matches January for some declaration count, so it is January-reachable.
## evidence
Name-count oracle (lab only, never land): K dummy `extern long` declarations make solo EXACT only at the following K. Production file, before solo: K=16,17 (0..75 swept). January-order p2.c: K=55,56 / 119,120 / 183,184, so the period is 64 and the window is 2 wide. Storage-restructured r1: K=49,50, with the same window when the dummies go before the globals block or before every #include, so every declaration in every header counts. r3 (two fewer file-scope statics): K=51,52, exactly the predicted shift of 2. Reordering solo's own locals (item_index first or last) is inert. Moving solo alone to its January slot (p1) or reordering the whole TU (p2) does not close it. The residual is the known pair of transposed back-edge reloads at +0x9a/+0x9d (mov eax,[ebp-0xc] / mov edi,[ebp+8]); frame, size and all 45 relocations already match.
## blockers
January's declaration count before solo must equal production+16/17 (mod 64), which is final.c+51/52 (mod 64). Every header in the TU contributes to that count, and reconstructing it needs January's real header and prototype set. Filler declarations would be fake matching.
## reopen_criteria
Reopen when genuine January declarations are recovered: real headers replacing the TU-local shadow structs, enums and typedef size-asserts, or missing prototypes and externs. Any such recovery that brings the prefix count into the window closes solo with zero change to its body. Confirm with oracle/run.sh (dummy names only as the measuring instrument) and re-gate. The whole board also needs re-checking, because header counts move other ties.
## task notes
The object is not complete; both functions are still non-exact. Baseline at e9e62b78 is 44 EXACT / 2 residual. The previous run had no slug directory, so this run started fresh.
1. solo is not a scheduling residual. It is a TU name-count tie that covers the whole TU (period 64, 2-wide window), measured to single-name precision. Its body is correct.
2. January's storage layout is recovered from HCEX + cachebeta evidence: static callbacks, a static table, and static-local .bss arrays. It is verified to fix every symbol and storage row of the object audit on a simulated split. It also reproduces January's function emission order and is byte-neutral for all 46 rows and for ui_widget.obj.
3. mgd stays a block-placement residual after more than 30 total shapes.
All tools are in the slug: reorder.py, mk_r.py, mk_bss.py, simgate.py, object_audit_sim.py, oracle/run.sh, layout_scan.py and lab/. The tracked tree is untouched (git status clean for source/ and config/).
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/ui_widget_game_data_input_functions/LEDGER.md
@@ -0,0 +1,117 @@
# source/units/vehicles
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_update_alien_scout_physics",
"_update_alien_fighter_physics_new"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\cand.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\cand_nofighter.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\production_linkclean.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\symbols_turret_static.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\bodies\\final2.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\dbg_scout.asm",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\cand_gate.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\cand_object_audit.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\cand_provider_link.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\vehicles\\cand_surplus_identity.txt"
]
## production_changes
Two landing options. Each patch is git-apply verified to reproduce its candidate byte for byte.
(A) FULL: `git apply scratch/w/vehicles/production.patch` on source/units/vehicles.c, which gives scratch/w/vehicles/cand.c. The gate is 39/39 EXACT.
Hunks:
1. Delete the `#define REAL_MATH_EXTERNAL_POINT_FROM_LINE3D` / `#undef` pair around the real_math.h include. January's vehicles TU saw the header __inline: `_vehicle_update` calls `_point_from_line3d` out of line at +0x268 and the fighter inlines it. vehicles is one of the owner's 17 out-of-line-reference objects.
2. `vehicle_find_pathfinding_surface_index`: replace the hand expansions (and the stale comment) with two genuine calls, both real calls in the /Od build 0x8fd970 at 0x8fda42 and 0x8fdad2:
- `point_from_line3d(&origin, global_up3d, 0.4f, &origin);`
- `point_from_line3d(&origin, &vector, result.t, position);`
The function stays EXACT and needs no casts. This is a rule-6 cleanup.
3. Fighter `_update_alien_fighter_physics_new`: `yaw = cross_product2d((real_vector2d const *)&desired_rotation.forward, (real_vector2d const *)&vehicle->object.translational_velocity)/...`. This is Lane B's held form, /Od-attested at 0x8f6cbf..0x8f6ccd. The two casts are byte-inert (strip test).
4. `update_alien_scout_physics` rewritten from bodies/final2.txt. It is a statement-for-statement /Od 0x8f80b0 transcription:
- RTC names; no pointer locals; /Od dot_product3d argument orders; /Od block-start real consts.
- A `steering_input` copy of steering.
- Two point_from_line2d calls for alpha_desire.
- Nested tail-block counters.
- The third parameter renamed powered_mass_points (the HCEX name).
Plus two deviations backed by January bytes:
- `vehicle_forward_horizontal` declaration-initialised (8-byte copy) BEFORE the cross_product3d; applied_alpha zero first.
- The leveling term as `point_from_line2d((real_point2d *)&applied_alpha, &alpha_desire, leveling_scale*(1.0f-vehicle->object.up.k), (real_point2d *)&applied_alpha)`.
It uses 28 byte-inert representation casts ((real_point3d *) x18, (real_point2d *) x6, dot_product2d((real_vector2d const *) x4)). Stripping them all leaves the section byte and reloc identical. Disclose these under ruling 6.
Caveat for (A): the newly emitted `_cross_product2d` COMDAT is IDENTICAL to January's selected copy (actor_combat) but FAILS provider_link (LNK2005). The cause is that source/ai/actor_combat.c still hand-writes `real cross_product2d(` behind `#define cross_product2d cross_product2d_inline`. So (A) is landable only together with an actor_combat provider repair, or an owner waiver.
(B) LINK-CLEAN NOW: `git apply scratch/w/vehicles/production_linkclean.patch`, which gives cand_nofighter.c. It is hunks 1, 2 and 4 without the cross_product2d hunk.
- Gate: 38 exact / 1 residual. The scout goes to EXACT. The fighter improves from [relocs 33!=32, sha] to [sha] but stays residual.
- Only row changed vs base: scout.
- New surplus `_point_from_line2d` (action_vehicle) and `_point_from_line3d` (action_charge) PASS provider_link and are IDENTICAL.
(C) SEPARATE, trivial: apply scratch/w/vehicles/symbols_turret_static.patch, then run a csplit-only regen of the vehicles split.
- It adds `"static": true` to config/symbols.json line 7052, `_update_turret_physics` (in-place edit; do not re-serialize).
- Evidence: cachebeta publics lack it (pdb_storage: PDB-public False). No January object references it. Ours is already static.
- This clears object_audit's only symbol DIFF.
Parks to retire: none. No parked.json, semantic or rejection entries exist for either function.
## evidence
Baseline (production build/base): gate 37 exact / 2 residual. Fighter [relocs 33!=32, sha]; scout [sha], frame 0xa0 vs January 0x94.
Fighter: the only cause of the reloc surplus is the TU-wide REAL_MATH_EXTERNAL_POINT_FROM_LINE3D. p_nodefine gives [sha] with 32/32 relocs. Adding Lane B's /Od-attested cross_product2d makes it EXACT (f1_held).
Scout path, measured (all in LEDGER.md):
- m15, the structural lane's best (775/775 insns, frame 0x94), leaves 12 real blocks at 4 inline-helper x87 sites S1..S4.
- t1, the /Od transcription (no pointer locals, /Od argument orders): 4 blocks.
- p1 / i2, forward 2D copy before the cross: 1 block. S2 closes; the i2 product was D-invariant, so this is statement order, not a count.
- l1 / final, leveling as point_from_line2d: 39/39 EXACT.
The l1 evidence is in January's own bytes at +0x53a..+0x557: `fld 1; fsub up.k; fmul 0.0038785094; fst [ebp-4]; fmul alpha_desire.i ... fld [ebp-4]; fmul alpha_desire.j`. This is the homed t-parameter signature of an inlined point_from_line2d/point_from_line3d.
Negatives and lab work:
- Rejected spellings: l2 (named local), c1 (cross return value), p2/p3, i1, k_a..k_d, f1..f3, final_nocast, final_b.
- Lab only, never landed: a D-sweep of dead locals and dead-call instruments localised the gap to exactly one inline temp between S2 and S3.
- q1/q2: the /Od dot_product3d argument orders are right; swapping either breaks.
- Declaration order is inert (t2a/b/c).
Checks on the final cand.c:
- gate == exact 39 residual 0 (cand_gate.txt).
- fake_match_scan: 0 leads.
- /W3 warning set identical to base apart from line numbers.
- Cast strip (cand_strip.c): both functions byte and reloc identical.
- object_audit: every January-owned section ok; the only symbol DIFF is _update_turret_physics storage.
- surplus_identity: 30 COMDATs, all IDENTICAL except the inherited _object_get_bounding_sphere.
- build/report.json vehicles data: 1744/1744 (100%).
## blockers
Whole-object admission is blocked by items OUTSIDE the two functions.
1. Provider links fail, FAIL(4):
- _cross_product2d: new with option A. actor_combat.c keeps a NODUP hand copy. Its genuine use is at /Od actor_combat fn 0x44ebb0 (call at 0x44ed45); our actor_combat.c has no cross_product2d use yet.
- _interpolate_scalar: inherited, provider player_control.
- _object_get_bounding_sphere: inherited, provider action_vehicle. Also bytes DIFFERENT vs January's selected copy; the objects.h inline differs.
- _triple_product3d: inherited, provider decals.
2. Invented private helper owners January lacks, all inherited and still used by EXACT functions:
- _vehicle_cross_product3d_target, _vehicle_dot_product3d_target and _vehicle_triple_product3d_target (_update_human_boat_physics).
- _vehicle_dot_product3d_test (_vehicle_preprocess_node_orientations).
Attempts, all stopped and recorded:
- boat1/boat2, an /Od 0x8f9a10 transcription with genuine helpers: same 784 B size, 267 vs 266 insns, 17 D-invariant blocks.
- preprocess with header dot_product3d: 47 blocks.
3. The _update_turret_physics storage needs the symbols.json static fix plus a csplit regen (patch provided).
## reopen_criteria
Once the actor_combat, player_control, decals and action_vehicle providers drop their NODUP hand copies for the header inlines (owner decision), rerun provider_link: option A should then be link-clean. For actor_combat, restore the genuine cross_product2d use from /Od fn 0x44ebb0 first. Separately, fix the objects.h object_get_bounding_sphere inline so it matches January's action_vehicle copy byte for byte.
To remove the invented helper debt, rewrite the boat and preprocess onto genuine helpers while staying exact. Start from boat1/boat2 (the /Od transcription): the remaining A/B regions are D-invariant, so look for statement-order or aggregate-copy causes (the scout S2 lesson), not local counts.
When all of that holds, rerun the section-4 audit on cand.c for OBJECT_COMPLETE_CANDIDATE.
## task notes
New levers measured in this unit, for other lanes:\n\n1. Once invented pointer locals are removed, the helper argument order in a /Od transcription is NOT inert (q1/q2). With header inlines visible, the /Od transcription alone cut the scout from 12 to 4 regions.\n2. D-invariant ('tree shape') x87 operand orders on products with no displacement-0 operand moved with the statement order of an adjacent aggregate copy that reads the same object: the forward 2D copy placed before cross_product3d.\n3. Use a dead 1-param inline call as a LAB instrument to localise a missing IL temp window between two sites. Then look in January's bytes for a helper parameter signature: an `fst [ebp-N]` homing of a factor computed once marks an inlined point_from_line t-parameter. That found the genuine point_from_line2d the later /Od build had spelled as two component statements.\n4. An explicit `(real)(...)` on an unavoidable double-to-real argument conversion is byte-relevant: removing it breaks S2/S4 and the plain form raises C4244. It is kept from production's existing spelling and disclosed. The owner may want to rule on whether such warning-silencing conversions pass the strip test.\n\nCandidate provenance:\n- Fighter: Lane B `/c/halo-worktrees/claude-lane-b-ai-physics-units-20260920/scratch/lane_b/w/vehicles`.\n- Scout base: the structural lane's m15 at `/c/halo-worktrees/claude-structural-20260923/scratch/lane/w/units__vehicles`, which was then superseded by my /Od transcription.\n\nNo tracked file was edited, and no ninja, configure or git-mutating command was run.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\vehicles\LEDGER.md. It records every probe as hypothesis, evidence, change and measured result: baseline, prior-lane evidence located, probes 1-9, the section-4 audit, the boat/preprocess side attempts, the final candidate, the split-landing option, and methodology notes.
@@ -0,0 +1,54 @@
# interface/virtual_keyboard::_virtual_keyboard_render_internal
## verdict
NO_PROGRESS
## newly_exact_functions
null
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/w1_v1_p.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/w1_E001_p.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/lab_pin1.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/LEDGER.md"
]
## production_changes
None. Keep the production 1584/95 fuzzy body. Once a body is byte-exact, a prerequisite config fix is ready: January's loop-2 bound relocation at +0x60c is spelled `_item_update_section+0` (csplit alias of `_keyboard_rect+352`). It can be retargeted in config/relocs.json to the `_keyboard_rect` file offset plus addend 352, following the Lane D precedent. It is pointless until the code is exact.
## evidence
Production is 1584/95. w1 v1 (per-arm bitmap fetch) rebuilt on the current base gives 1616/95 but still about 93 aligned diff blocks; E001 gives 1600/95.
New analysis of the NONE (-1) prologue pin. January pins -1 with `or edi,-1` at 0x3e, uses it 7 times and rematerializes it at 0x11c; ours uses immediates.
- Constant-use count: ours has 18 uses of -1 across the function and January has 18, so the count is not the difference.
- Corpus: 106 January functions pin -1 in the prologue, for example EXACT _first_person_weapon_draw, which pins purely from `!= NONE` compares plus system_exit(-1).
- ORACLE lab_pin1: replacing every -1 / 0xffffffff after the second draw_string_set_draw_mode makes our prologue pin appear (in ebx, displacing the 640 pin). So our -1 constant is one whole-function web that never wins a register.
- Replacing only one region (lab_pin2 later colors, lab_pin3 later NONEs, lab_pin4 caret/text-box/third call, lab_pin5 loops): no pin.
- Source hypotheses, all inert: lab_style (`long style` via a lab redeclaration, confirms fresh_graphs) and r1 (a caret `pixel32 caret_color` local, suggested by a later /Od build text-box function 0x68fdb0 [ebp-0x68]).
/Od (0x68fa00, 0x68fdb0) is a later redesign with no key grid, so it gives no evidence for loops 1 and 2.
## blockers
Several independent residual classes remain:
1. January splits the -1 web so the prologue part gets edi; our whole-function web never wins a register, and no source-level IL fact for this was found.
2. w1's key-IV base (+0x3c) versus the arm-layout conflict.
3. Register rotation in loop 2, the label home slot and the switch-index copy.
4. The caret width reload and swapped slots.
5. The loop-1 font-header reload and interleaving of the rect copy with argument pushes.
6. The csplit relocation alias, which relocs.json can fix once the body is exact.
## reopen_criteria
Reopen if one of these arrives. (a) A source or IL fact that separates the prologue -1 constant web from the later -1 uses; lab_pin1 is the oracle to check against, and an allocator trace (dbg32c) of the -1 web split is the suggested instrument. (b) w1's criterion: a natural switch spelling that gives the +0x3c key-IV base while keeping the per-arm fetch layout (start from w1_v1_p.c or w1_E001_p.c). Strict credit then also needs the relocs.json alias retarget.
## task notes
Neither function closed, so the object stays at 18/20 exact. object_audit on the base object shows every January data, rdata and bss section and all 37 symbols matching; only the two functions differ. Candidate-only surplus is the pooled string literals from this TU (ui\english, <unknown>, ui\shell\bitmaps\white, bitmap) and the XDK D3D tables, which already existed. surplus_identity reports 0 candidate-only code COMDATs.
The main new finding is the tail-merge survivor mechanism. It is proven on 2 EXACT functions from the corpus plus lab and real-TU probes, and should be added to the campaign laws: an IL tail merge keeps the first predecessor in the switch-END list; a flat switch keeps the source-last arm; an END that is an empty jump (nested switch or if-else wrapper) keeps the earlier arm.
It corrects two earlier ledger claims:
- w1's 'emission follows case value' is wrong for this function: s1 shows the layout follows source order.
- w3's conclusion of a 'late machine-level cross-jump' is wrong: VC7 has no machine-level suffix cross-jump, and the merge is an IL one with a new store block.
No tracked files were edited and no ninja or git-mutating commands were run. The only production artifact is the optional byte-neutral production.patch.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/LEDGER.md
@@ -0,0 +1,63 @@
# interface/virtual_keyboard::_virtual_keyboard_select
## verdict
NO_PROGRESS
## newly_exact_functions
null
## candidate_files
[
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/s2.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/hr1.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/s3b.c",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/production.patch",
"C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/LEDGER.md"
]
## production_changes
None needed for credit. Optional house-rule cleanup that does not change any bytes: scratch/w/virtual_keyboard/production.patch (unified diff against source/interface/virtual_keyboard.c). It replaces the default arm's hand-expanded `virtual_keyboard_get_character(virtual_keyboard_key_layout[row][column])` with `virtual_keyboard_get_current_character()`. Verified: all 20 function normalized hashes equal build/base, select stays 025bd53f (the park base), and object_audit output is identical to base. Keep the park. Do NOT land w1's `word buffer_size` lever (s0/s2 sha ff65b27b) while the function is not exact, because it moves the parked bytes.
## evidence
Starting point: s0 = production + w1's `word buffer_size` (1088/123). Aligned diff shows the only difference is where the merged left/right tail sits (5 instructions: `mov [cursor],eax; push 1; mov [first_key],bl; call feedback; add esp,4`) plus the jump-table entry for the right arm (412 vs 388).
/Od readout of the later build (fn 0x68ef60) gives the same left-then-right source order and the same statements.
Mechanism, decoded with /FAsc labels:
- Both builds do the same IL tail merge: a new store block, a phi into eax, and the later arm's `mov eax,ecx`.
- Only the survivor differs. Ours keeps the RIGHT arm's if-skip label ($L23111); January keeps the left arm's.
- The survivor is the first predecessor in the switch-END predecessor list. A flat switch whose END holds fallthrough code keeps the source-LAST arm.
- An END that is an empty jump block reverses this and keeps the earlier arm (January's layout).
Tested:
- Lab e1-e30 on the w3 replica:
- arm count or position, default position, extra calls, && conditions, loop wrapper, if without else, return after the switch, inline helper: all keep the last arm.
- Nested switch (e16/e17) and switch inside if-ELSE (e22): the earlier arm hosts (January's shape).
- Two EXACT corpus functions confirm it (scratch/w/virtual_keyboard/xjscan.txt):
- _virtual_keyboard_process_internal (nested switch; still hosts left when first_key is moved after the if, lab_pi1-4)
- _render_debug_add_cache_entry (switch followed by a skip-else jump).
- Real TU:
- s3b (switch wrapped in `if (keycode >= NUMBER_OF_CONFIGURABLE_VIRTUAL_KEYS) {...} else {insert}`) reproduces January's left-host layout but adds `cmp ax,0x24; jl`.
- s3a, s3c (VALID_INDEX) and s3d (unsigned range cast) also add dispatch compares; VC7 never folds the redundant range test.
- s1: swapping left/right source order changes the layout (so w1's 'emission follows case value' is wrong here) but the tail still sits after the source-last arm.
- Declaration-count oracle sweep (1-32 dummy externs, oracle only): inert.
Full probe record: scratch/w/virtual_keyboard/LEDGER.md.
## blockers
January's single range check (`add eax,-0x24; cmp eax,7; ja default; jmp [table]`) and a default arm that falls straight into the shift-reset code fix a flat switch with fallthrough END code. Every structure found that makes the switch END a jump (nested switch, if/else wrapper) adds dispatch code. The remaining devices (one-trip loop, goto) are forbidden fake matching. No lawful source handle found; the stop rule fired.
## reopen_criteria
Reopen only if one of these arrives. (a) A January-plausible select structure in which the switch END is an empty jump block but which still compiles to exactly one range check; start from s3b.c, which already gives the right layout with one extra compare. (b) A C2-level decode (for example the dbg32c watchpoint on the tail-merge predecessor list) showing another IL fact that orders the switch-END predecessors. If either makes the tail placement match, re-apply `word buffer_size` from s0/s2 and retire the park.
## task notes
Neither function closed, so the object stays at 18/20 exact. object_audit on the base object shows every January data, rdata and bss section and all 37 symbols matching; only the two functions differ. Candidate-only surplus is the pooled string literals from this TU (ui\english, <unknown>, ui\shell\bitmaps\white, bitmap) and the XDK D3D tables, which already existed. surplus_identity reports 0 candidate-only code COMDATs.
The main new finding is the tail-merge survivor mechanism. It is proven on 2 EXACT functions from the corpus plus lab and real-TU probes, and should be added to the campaign laws: an IL tail merge keeps the first predecessor in the switch-END list; a flat switch keeps the source-last arm; an END that is an empty jump (nested switch or if-else wrapper) keeps the earlier arm.
It corrects two earlier ledger claims:
- w1's 'emission follows case value' is wrong for this function: s1 shows the layout follows source order.
- w3's conclusion of a 'late machine-level cross-jump' is wrong: VC7 has no machine-level suffix cross-jump, and the merge is an IL one with a new store block.
No tracked files were edited and no ninja or git-mutating commands were run. The only production artifact is the optional byte-neutral production.patch.
ledger: C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/virtual_keyboard/LEDGER.md
@@ -0,0 +1,112 @@
# source/items/weapons
## verdict
OBJECT_COMPLETE_CANDIDATE
## newly_exact_functions
[
"_weapon_place"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\p1_weapons_c.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\p2_owner_header.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\p3_symbols_json.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\p4_action_charge_provider.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\final\\weapons.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\final\\weapons.h",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\final\\object_types.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\final\\action_charge.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\final\\symbols.json",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weapons\\LEDGER.md"
]
## production_changes
Apply scratch/w/weapons/production.patch from the worktree root with `patch -p1 --binary < scratch/w/weapons/production.patch`. This was checked on a scratch mirror and reproduces scratch/w/weapons/final/* byte-for-byte; the files are CRLF. As an alternative, copy the final/ files over the production files; their sha256 values are recorded in LEDGER.md. The patch has four parts:
(p1) source/items/weapons.c:
- `void weapon_place(` becomes `long weapon_place(`, and its terminal `return;` becomes `return weapon_index;`.
- `static` is added to the weapon_magazine_finish_reload prototype (l.449) and definition (l.1382).
- `struct weapons_globals` and its variable `data_00307140` are replaced by two statics: `static char *blurred_permutation_names[MAXIMUM_NUMBER_OF_TRIGGERS_PER_WEAPON] = {"~primary-blur", "~secondary-blur"};` and `static struct profile_section weapon_update_section = {"weapon_update", NONE, TRUE};`.
- The 4 uses are respelled, and the symbol comment block at the top is updated.
(p2) Owner header:
- source/items/weapons.h gains `struct scenario_weapon_datum;` and `long weapon_place(long weapon_index, struct scenario_weapon_datum *scenario_weapon);` (one parameter per line), next to weapon_new.
- source/objects/object_types.c drops its void weapon_place facade prototype. The weapon_data_definition slot becomes `(void (*)(long, struct scenario_object_datum *))weapon_place`, with a BUG comment above the table.
(p3) config/symbols.json, edited in place with no reordering:
- Add `"static": true` to 11 lines: _weapon_trigger_get, _weapon_magazine_get, _weapon_busy, _weapon_magazine_state_change_ok, _weapon_get_effect_object_index, _weapon_get_owner_object_index, _weapon_effect_new, _weapon_state_interruptable, _weapon_set_state, _weapon_magazine_finish_reload, _weapon_reset.
- Line 22579 `_data_00307140` becomes `{ "file_offset": 3174720, "flags": 0, "name": "_blurred_permutation_names", "static": true },`.
- Insert after it `{ "file_offset": 3174728, "flags": 0, "name": "_weapon_update_section", "static": true },`.
- Then regenerate csplit. In a scratch re-split only build/split/source/items/weapons.obj changed.
(p4) source/ai/action_charge.c, provider fix:
- Delete `#define real_random real_random_inline`, `#undef real_random`, and the hand-written `real real_random(void)` definition, so the genuine real_math.h __inline emits _real_random as SELECT_ANY. This is the reopen criterion already written for objects.obj in config/object_admission_rejections.json.
No park to retire; _weapon_place is unparked. No semantic_matches or semantic_data_matches change is needed.
After applying, the integrator must:
- Run a full ninja.
- Confirm weapons 79/79 and object_audit PASS.
- Re-gate the declaration-count sentinel `source/units/units --fn _unit_preprocess_node_orientations`, because other lanes' header packets may compose.
- Run parked_functions.
- Run `provider_link.py source/items/weapons` and expect a full PASS.
FALLBACK: if the owner rejects the object_types.c cast or the units sentinel flips, apply only p1+p3+p4. That leaves weapons.h and object_types.c untouched and gives a byte-identical weapons.obj (objcmp 0 diffs, 79/79 vs the re-split).
## evidence
Every compile was done in scratch and never touched tracked files. Details are in scratch/w/weapons/LEDGER.md.
1. Why the long return is justified. The HCEX.pdb weapons compiland, read by our own MSF7 parser, gives weapon_place type LF_PROCEDURE returning 0x12 (long). All other placement callbacks return void. In the /Od build the function reloads [ebp+8] into eax before its epilogue. January keeps weapon_index in EBX and ends with `mov eax,ebx`. With the long return, _weapon_place is strict exact: 208 B padded, 4 relocs, normalized sha 4e5e6cfe4629e367..., the same value research12 recorded.
2. The research12 units regression no longer happens. A shadow-header harness (blast.py + objcmp.py) swaps each consumer's weapons.h include for a scratch copy via gate --edits. The control run reproduces all 19 direct includers' build/base objects exactly.
- With the owner header plus the object_types edits, 18 consumers keep every function section, non-text section and non-label symbol identical. Units stays 189/189 and object_types 31/31.
- The only gain is _weapon_place. The only other differences are internal $L label renumbers.
3. Storage classes. Per cachebeta publics, 12 weapons symbols are file-static in January, and HCEX lists weapon_magazine_finish_reload as LPROC (static). No other split object references any of them.
- Making finish_reload static is byte-inert (objcmp 0 section diffs).
- The 11 functions need `"static": true` in symbols.json. A scratch csplit with the patched symbols.json changes only weapons.obj; the control csplit reproduces build/split exactly.
4. Data. HCEX has a file-static LDATA `blurred_permutation_names`, type LF_ARRAY of char* (8 B), and no globals struct. So the `weapons_globals` struct is invented; two loose statics replace it.
- The layout is 8 + 0x5F8 at +8 with 8-byte alignment, the same as before. The projectiles.obj precedent shows .data follows definition order.
- The profile section name is not in HCEX SHIP, so it gets the descriptive sibling name `weapon_update_section`.
5. Final candidate against the re-split target:
- gate-equivalent 79/79.
- object_audit PASS: 98 January symbols, 0 differ, every section ok.
- 0 PDB-storage disagreements.
- surplus_identity: 17 COMDATs, all identical.
- A scratch objdiff 3.3.1 report scores .data and .rdata 100% and _weapon_place 100%.
- /W3 warnings are the same as before in all three edited C files.
- fake_match_scan finds 0 leads.
6. Provider link. The inherited _real_random LNK2005 against action_charge's NODUP hand copy fails on production base too. With p4, action_charge stays 21/22 with zero function-section differences, its _real_random is byte-identical and now SELECT_ANY, and the VC7 pair link weapons+action_charge gives no LNK2005 in either order.
## blockers
None on the byte or object side once p1–p4 and the csplit regeneration are applied. Three owner judgements go with the package:
(a) The object_types.c function-pointer cast and BUG comment bind a long-returning function to the void datum_place slot. The return type is authentic, but the call is not defined ISO C. The fallback p1+p3+p4 avoids the cast with an identical object.
(b) The p4 action_charge provider fix follows the reopen text of the objects.obj rejection. Without p4, weapons fails selected-provider linkage only through that inherited _real_random conflict.
(c) `weapon_update_section` is a descriptive name that follows the sibling files' convention; it is not an authenticated January name.
Note also that header edits add up across packets: the units sentinel must be re-gated after all lanes' header packets land.
## reopen_criteria
If the integrated build shows units `_unit_preprocess_node_orientations` or any other consumer function moving after p2, drop p2 and land p1+p3+p4; that gives the same weapons.obj bytes. If the owner rejects p4, weapons.obj becomes FUNCTIONS_EXACT_OBJECT_BLOCKED on the _real_random NODUP provider only; all 79 functions and all data still land. Reopen the profile-section name only if a first-party source gives January's real identifier.
## task notes
Resumed after the cut-off. The earlier run's probe 1 and its HCEX re-attestation were reused, not redone.
New findings this session:
- Research12's rejection, a units declaration-count loss from the weapons.h owner prototype, does not reproduce at base e9e62b78. All 19 weapons.h consumers were checked section by section.
- The object had three whole-object gaps that nobody had recorded: 11 csplit storage-class errors (symbols.json lacks static), a static function we left external, and an invented globals struct under a placeholder name. All three are fixed and proven with a scratch csplit.
- The inherited _real_random NODUP provider conflict has a measured, zero-regression provider fix in action_charge.c.
Tools written under scratch/w/weapons/ that other workers can reuse:
- blast.py: shadow-header blast test using gate --edits include swapping.
- objcmp.py: whole-object section and symbol diff.
- gate_vs.py: gate against any target object.
- object_audit_splitroot.py: object_audit with a SPLIT_ROOT override.
- w3.py: /W3 warning census.
- mod_syms.py: HCEX per-module PROC/DATA global versus local lister.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\weapons\LEDGER.md
@@ -0,0 +1,108 @@
# source/effects/weather_particle_systems
## verdict
FUNCTIONS_EXACT_OBJECT_BLOCKED
## newly_exact_functions
[
"_weather_particle_system_render"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weather_particle_systems\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weather_particle_systems\\final_weather_particle_systems.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weather_particle_systems\\fin2.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weather_particle_systems\\fin2.obj",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weather_particle_systems\\body_fin.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weather_particle_systems\\LEDGER.md",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\weather_particle_systems\\od_render.txt"
]
## production_changes
This changes one file only: source/effects/weather_particle_systems.c. Apply scratch/w/weather_particle_systems/production.patch with `git apply` in the worktree. The patch is LF and applies under core.autocrlf=true. As an alternative, copy scratch/w/weather_particle_systems/final_weather_particle_systems.c over the file; it is byte-identical to the patched result, and I checked this in an isolated autocrlf repo.
The patch makes three edits:
(a) Line 309: `static real const one_over_char_max = 1.f/255.f;` becomes `real const one_over_char_max = 1.f/255.f;`. January's split object has this symbol as storage 2 (EXTERNAL), and it is a public in the cachebeta PDB. Nothing else in the TU uses it, and no other source file or PDB public defines it.
(b) The body of weather_particle_system_transform_clip_planes_to_box becomes `plane_distances[plane_index] = dot_product3d(&planes[plane_index].n, (real_vector3d const *)box_position);`. The /Od build attests that this site is a real call to dot_product3d. The function stays EXACT.
(c) The body of weather_particle_system_render is rewritten to follow the first-party /Od topology (halo_cache_symbols.exe 0x57fb00):
- Names come from the RTC descriptors and HCEX: structure, build_sprite_data, box_offsets, clip_planes, clip_plane_d_transforms, weather_polyhedra_indices/count, box_deltas, viewer_box, box_delta, box, plane_evaluations, position, viewer_to_particle.
- Box 0 is handled as `box_count = 0`, then `box_offsets[box_count]`, then `box_count++`.
- `real far_clip_distance` is assigned after weather_polyhedra_find.
- viewer_box is set with a chained assignment followed by `+=`.
- particle is declared inside a `while (particle_index!=NONE)` loop body.
- The loop uses `box_offset = NULL`, with `box_index<box_count && !box_offset` plus a break.
- Particle distances use plane3d_distance_to_point, with no hand expansion.
- `real maximum_distance = MIN(...)` is declared after position, viewer_to_particle and depth.
- position = particle->position + box_offset, particle operand first as in /Od.
- The polyhedron test has no named plane local.
- The visibility flag starts TRUE and is set FALSE.
- The sprite scale is written `runtime_one_over_sprite_width*(2.f*particle->radius)`.
- viewer_to_particle is computed with explicit statements, as /Od shows.
- `short unused_count = 0;` is added at type scope after `long particle_index;`. It matches /Od dead slot ebp-0x24.
Nothing else changes: no header, config, symbols.json, parked.json, semantic override or admission row. The unit has no park to retire. Optionally, copy scratch/w/weather_particle_systems/LEDGER.md into docs/object_matching_logs/ as the unit ledger.
## evidence
Starting point: the production gate was 19 exact and 1 residual (render, [sha]). The prior-lane ra.c body, spliced onto the current tree as cand_ra.c, still had 5 x87 rows: 4 at the inner inline transform site and 1 at position.x. Those are the same rows the w1 and w3 lanes recorded.
New evidence from the /Od build (fn 0x57fb00, no prior lane had used it):
- The RTC descriptors plus a full frame census give January's local inventory and declaration topology. Every gap in the frame is an /RTCs guard.
- There is one extra dead `short` at ebp-0x24. It is initialised to 0 and never read.
- The build computes viewer_to_particle explicitly, although vector_from_points3d is a real function in that build (0x42e3a0, called by distance_squared3d 0x42dc80).
- It calls plane3d_distance_to_point and dot_product3d as real functions.
od2 (/Od-faithful body, without the dead short, still using vector_from_points3d):
- position.x, the j-term row and the sprite row all closed.
- 3 rows remained, all in the inner-site term order.
LAW M8 lab (dead-local instrument, lab only):
- On od2, D=2 closes the function and D=8 equals D=0.
- 2 dead locals anywhere in the function close it, so the key is a count over the whole function.
- Uninitialised, unused declarations are inert.
- With explicit viewer_to_particle (v1), D=5 or D=1 closes it. Removing the inline temps of the later site shifts the key, and the inner-site key has period 4.
- v1 plus the /Od dead short (`short X = 0;`) is STRICT EXACT. So the two /Od-attested inventory facts together reproduce January's count key.
Final fin2, whole-TU gate: exact 20, residual 0, unwritten 0. Render is EXACT at 1680 padded bytes, 47 relocations.
Controls, all whole-TU:
- Without unused_count: render residual.
- With production's hand-expanded transform body: render residual.
- Without the (real_vector3d const *) cast: still 20/20, so the cast is byte-inert and only avoids C4133.
- Without the redundant `&& !box_offset`: still 20/20 (inert); I kept the /Od form.
Other checks:
- CL /Zs /W3 gives the same warning set as production.
- fake_match_scan: 0 leads.
- object_audit on fin2: every January-owned .text and data section is ok, and 30/30 January symbols match (fin1 differed only in the one_over_char_max storage, which fin2 fixes).
- report.json data was already 949/949; code goes from 4114/5779 to 5779/5779, a gain of 1665 meaningful bytes.
- The surplus list is identical to the production base object's. provider_link with --baseline=build/base reports no new surplus.
- surplus_identity: 13 of 14 header COMDATs are IDENTICAL.
## blockers
1. Object blocker, already present in production and outside this unit:
- The _real_local_random surplus COMDAT is DIFFERENT from January's selected copy. Ours is the header __inline body ending `pop ecx`; January's copy in effects.obj uses a named result and ends `add esp,4`.
- provider_link FAILs with LNK2005 in both link orders, against effects.c's NODUP hand-written real_local_random.
- The production base object fails the same way. new_particle's existing real_local_random() use causes it, not this candidate.
- The fix belongs to the owner, in real_math.h and/or effects.c (the known NODUP provider class). A named-result header body was previously measured to regress units (lane w1, tree D).
2. Owner acceptance needed for three first-party-attested but unusual forms:
- The DEAD declaration `short unused_count = 0;`. /Od-attested (slot ebp-0x24, init-only). It is load-bearing for the M8 count key. This is not an invented filler: it is the genuine declaration the M8 count oracle pointed to. The name is descriptive (the original name is unknown; renaming is byte-inert).
- Explicit viewer_to_particle statements instead of vector_from_points3d, at this one site.
- The redundant `&& !box_offset` loop condition next to a break (byte-inert, /Od form).
## reopen_criteria
Object completion reopens in either of two cases:
- The owner rules on the _real_local_random NODUP provider. For example: effects.c consumes the header inline, and the real_math.h body is a named result so the SELECT_ANY copy matches January's `add esp,4` body. This needs a full-board blast-radius sweep, because the earlier attempt broke units::_unit_preprocess_node_orientations.
- The lane admits already-present NODUP surplus as non-blocking.
If the owner rejects the dead /Od-attested declaration, the render function reverts to residual. The best compliant fallback is od2.c, with 3 inner-site rows left. In that case, reopen only with first-party evidence of a different January local inventory; one IL-visible local net, modulo 4, closes it.
## task notes
Nothing was lost from the earlier cut-off: the slug directory did not exist, and every prior-lane ledger was read before any probe. No tracked file was edited; `git status` on the production source is clean.
Measured result: _weather_particle_system_render is strict EXACT and the unit's gate is 20/20. I closed it by reading January's local inventory out of the first-party /Od build and using the M8 count law to check it. The object stays blocked by one issue that was already in production before this work: the _real_local_random NODUP provider-link conflict with effects.c.
Keep the credited bytes separate: the meaningful gain is +1665 code bytes (1680 padded). The one_over_char_max storage fix brings January symbol parity to 30/30.
Lab tools written: lab/mod8lab.py and lab/place.py (dead-local instrument, lab only), splice_body.py, nontriv.py, warn.py, cmp_comdat.py.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\weather_particle_systems\LEDGER.md
@@ -0,0 +1,62 @@
# source/cache/xbox_texture_cache
## verdict
OBJECT_COMPLETE_CANDIDATE
## newly_exact_functions
[
"_texture_cache_steal_memory"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\xbox_texture_cache\\cand.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\xbox_texture_cache\\cand.obj",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\xbox_texture_cache\\production.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\xbox_texture_cache\\production_crlf.patch",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\xbox_texture_cache\\cand_gate_all.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\xbox_texture_cache\\cand_object_audit.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\xbox_texture_cache\\cand_provider_link.txt",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\xbox_texture_cache\\LEDGER.md"
]
## production_changes
1) Apply C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\xbox_texture_cache\production.patch to source/cache/xbox_texture_cache.c with `git apply`. The repo has core.autocrlf=true and the file is CRLF; `git apply --check` passes. If you use GNU patch instead, apply production_crlf.patch with `patch --binary -p1`; I checked that it reproduces cand.c byte for byte. The edit is three hunks inside texture_cache_steal_memory only: (a) after `long stolen_size = page_count * XBOX_TEXTURE_CACHE_PAGE_SIZE;` insert
byte *stolen_address =
base_address + XBOX_TEXTURE_CACHE_STEAL_GUARD_SIZE;
(b) the first XPhysicalProtect's first argument becomes `stolen_address,` (it was `base_address + XBOX_TEXTURE_CACHE_STEAL_GUARD_SIZE,`); (c) `return stolen_address;` (it was `return base_address + XBOX_TEXTURE_CACHE_STEAL_GUARD_SIZE;`). end_guard_address stays computed from base_address. It must NOT become stolen_address + stolen_size: that is the old sm1 shape, and the bytes refute it. 2) Retire the config/parked.json entry {unit: source/cache/xbox_texture_cache, function: _texture_cache_steal_memory, class: unclassified}. The function now matches January's hash 7ffbda42..., so the parked body's bytes move. 3) No header, symbols.json, config.json or relocs changes. 4) After integration, run a full ninja and a per-function stable sweep. The edit is local to this .c file. The added local changes the TU name count only for later functions in this TU, and the candidate gate shows all 25 still EXACT.
## evidence
Baseline production gate: 24 exact, 1 residual (steal_memory [sha]). The alndiff showed one difference: January keeps the late writable pointer `lea R,[ebx+0x104000]` in ESI, production uses EDI.
The /Od build has no body for this function (it is the PC build, pc_texture_cache). HCEX has no record for it.
Oracle tests with no effect (all residual): 0..44 dummy names before the function (TU name-count oracle) and 1..12 dead locals.
Allocator decode: I retargeted Lane A's res9 alloc-oracle (dbg32c plus oracle.py/mapweb.py) to this worktree; C2.dll sha1 is identical, and R0..R3 all pass. In production, the CSE writable temp's ESI cost is 1001 = 600 from the stolen_size argument web + 400 from the end-guard web + 1. Both are uncoloured neighbours whose only allowed register is ESI, so the chooser takes EDI.
Lab probes: every computed argument of one call (CSE temp, assignment inside the argument, stolen_size as arg3) interferes with the other argument webs. A variable LOADED at its use does not. Lab LH (a param loaded as arg1) reproduces January's `push esi; mov esi,..; push esi` and its pop order. A census of the whole binary found the push/redefine/push-in-one-call pattern at 17 January sites. All but this function use loads or copies.
Hypothesis: a named writable-address local, initialised early and rematerialised at its use. Result: R1, R2, R3, R4 and the void* variant are all strictly EXACT, so declaration order and type do not matter. Final gate with --all --forbid-emitted-symbol _point_from_line3d: 25 exact, 0 residual, 0 unwritten; steal_memory is 224 padded / 16 relocations / sha 7ffbda42.
Oracle on the final candidate: stolen_size is coloured ESI first, the named local's early web is left uncoloured and rematerialised, and its late piece takes ESI at zero cost. This is January's allocation.
Whole-object audit: object_audit shows every January-owned .text, .rdata and .bss section OK. It reports 3 storage differences, all XDK D3DINLINE wrappers (IDirect3DBaseTexture8_IsBusy@4, IDirect3DDevice8_IsBusy@4, IDirect3DDevice8_KickPushBuffer@4): split 2, ours 3. pdb_storage shows none of the three is a cachebeta public, so the true storage is file-static and ours already agrees. The same class of difference exists in rasterizer_xbox_debug, which is already admitted as complete.
Surplus (already present in production, unchanged by this edit): .drectve, 10 string literals, 3 XDK tables, 4 __real constants, and the _add_vectors3d/_dot_product3d COMDATs. surplus_identity reports both COMDATs IDENTICAL to their providers. provider_link (both orders): SELECTED-PROVIDER LINK PASS.
build/report.json: data 6344/6344 = 100%. Code is 3248/3457 with steal_memory the only non-100% function, so this edit gives 3457/3457.
No admission rejections exist for this unit.
Strip test / house rules: the change is a real named local that carries the value used by the READWRITE protect call and by the single return. Declaration and initialisation are combined (rule 17). It adds no cast, parenthesis, pragma or dummy. The unnamed spelling is the residual; the named-local spelling is exact.
## blockers
None found for the function. Two object-level items for the integrator to confirm under existing precedent: (1) object_audit exits FAIL(3) only because of the XDK D3DINLINE storage differences. cachebeta.pdb publics show these wrappers are file-static, which matches ours, and it is the same class as the admitted rasterizer_xbox_debug. (2) The candidate-only surplus (strings, XDK tables, reals, add_vectors3d/dot_product3d) was already present and all of it passes the provider link in both orders.
## reopen_criteria
Not applicable: the function is exact. Reopen only if the full-build sweep after integration shows a regression elsewhere. None is expected: the change is local to this TU, and all 25 of its functions were verified exact.
## task notes
This run started fresh: scratch/w/xbox_texture_cache/ did not exist before it. What closed the function is a mechanism worth reusing. When January redefines a callee-saved register between two pushes of the SAME call with a computed value (for example `push esi; lea esi,[..]; push esi`), the value is a named local computed early and rematerialised at its use. It is not a CSE temp built at the call: VC7 makes computed argument temps interfere with the call's other argument webs, while rematerialised or loaded webs do not. January's draw_gravy_layer (progress_bar, still residual, size 336 vs 352) shows the related register-copy form `push ebx; mov ebx,edx; push ebx` and may respond to the same idea. The retargeted allocator oracle is at scratch/w/xbox_texture_cache/ao. Run it as `python ao/oracle.py <src> <unit> <fn>` from that directory; it takes about 1 second per compile.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\xbox_texture_cache\LEDGER.md has every probe as hypothesis, evidence, change, result: baseline, O1/O2 name-count oracles, v1/v2/v3/v5/v7, the allocator-oracle decode, labs L1/L2/L3/LA/LC/LD/LG/LH, the pattern census, S1/S3, the exact R1-R4 variants, and the final audit.
@@ -0,0 +1,63 @@
# LAYER1 provider repairs (action_charge, player_control, bored_camera, path_obstacles cand2, decals cand3)
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\decals\\cand3.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\patch\\layer1\\decals.c.diff"
]
## production_changes
Apply scratch/w/provider/patch/layer1/{action_charge,player_control,bored_camera,path_obstacles,decals}.c.diff. The decals diff (cand3) removes the renames and hand copies for plane3d_from_point_and_normal, plane3d_negate, projection_sign_from_vector3d and triple_product3d only; plane2d_from_points and project_point2d must stay hand copies at the current header. Afterwards, on the live tree: run ninja + stable per-function diff, and run provider_link for each of the Matching objects listed in evidence.
## evidence
Each provider keeps identical gate rows, and objcmp shows only NODUP->ANY with identical bytes. decals cand3 is 31+2 pre-existing residuals; the canary _decals_delete_permanent_from_cluster is kept, and the invented surplus _plane3d_negate_inline is dropped. plane3d_negate's genuine use is the bsp3d.h:55 inline (bsp3d_get_plane_from_designator). Board link census over 288 fully code-exact units (sweep/linkcensus.tsv): provider link PASS goes 250 -> 260 with 0 regressions. FAIL->PASS: objects; rasterizer_xbox; items and scenario; and six cameras (camera_scripting, first_person_camera, flying_camera, following_camera, orbiting_camera, static_camera). The cameras, items and scenario are currently labelled Matching despite these latent link failures. Board-wide census: the four released decals helpers are equal to January in every copy except one TU-specific outlier each (breakable_surfaces-type).
## blockers
none for the provider rows. rasterizer_xbox still has 21 symbol-storage attribution diffs (out of scope).
## reopen_criteria
n/a
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,62 @@
# source/ai/path
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\patch\\layer2_ABRPL"
]
## production_changes
No path.c change. It is unlocked by the LAYER 2 packet (distance3d body + action_vehicle.c.diff). After the live verification, set path.c Matching.
## evidence
Production: 32/32 EXACT, object_audit PASS, pdb 0 disagreements; the ONLY blocker is _distance3d (provider link FAIL, and surplus identity DIFFERENT from action_vehicle). Under ABRPL: 32/32, audit PASS, 10 surplus COMDATs all identical, provider link (L2) PASS. No parks and no rejections.
## blockers
Owner ruling on the shared-header packet.
## reopen_criteria
as leaf_map
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,60 @@
# source/ai/path_smoothing
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[]
## production_changes
none
## evidence
6/6 EXACT; provider_link fails only _cross_product2d, whose provider is actor_combat (NODUP hand copy, the sole NODUP definer). actor_combat.c has no use of cross_product2d besides the hand copy. The /Od-attested genuine use is inside _actor_aim_grenade (/Od 0x44ed45), which is a held frame-scope residual (sub esp 0x28 vs 0x24). Its restoration adds 5 view casts + 5 COMDATs that rulings 5/6 admit only in a strict-exact function. Removing the hand copy without the genuine use would lose January's _cross_product2d row.
## blockers
actor_combat::_actor_aim_grenade must first become strict exact with the /Od helper calls (held item; do not retry the invented-goto form).
## reopen_criteria
_actor_aim_grenade strict exact with its genuine cross_product2d call. Then remove actor_combat's cross_product2d rename and hand copy, and re-run provider_link for path_smoothing.
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,63 @@
# source/effects/decals
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\decals\\cand3.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\decals\\cand4.c"
]
## production_changes
As a PROVIDER, apply layer1 cand3 (later layer2 cand4); both keep all 33 rows identical. As an object it stays NonMatching.
## evidence
The _vector_from_points2d conflict is already resolved at base: action_obey emits ANY, and all 13 copies are equal to January. The rejection entry is stale on that symbol. It still fails _cross_product2d (actor_combat NODUP; see path_smoothing) plus 2 pre-existing residuals (_decal_clip_to_surface, _decal_new_from_collision; lane-closed). The invented surplus _plane3d_negate_inline is removed by cand3/cand4. Removing ALL six decals hand copies (cand1) loses 3 rows: _plane2d_from_points and _project_point2d (header bodies differ) and the name-count canary _decals_delete_permanent_from_cluster.
## blockers
Two lane-closed residuals + actor_combat _cross_product2d (held aim_grenade).
## reopen_criteria
New evidence for the two decals residuals + the aim_grenade reopen.
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,60 @@
# source/interface/hud_draw
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[]
## production_changes
Update the hud_draw _fast_ftol rejection text (the provider conflict no longer exists); no source change from this lane.
## evidence
actor_combat now emits _fast_ftol as ANY, and all 25 copies are equal to January. provider_link for production hud_draw passes, and its 2 surplus COMDATs are identical. But the January function _fast_ftol_C (56 B) is UNWRITTEN, so object_audit reports it MISSING.
## blockers
Unwritten _fast_ftol_C (not a provider issue).
## reopen_criteria
Write _fast_ftol_C (or establish its CRT/vendored ownership).
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,60 @@
# source/interface/hud_unit
## verdict
BLOCKED
## newly_exact_functions
[]
## candidate_files
[]
## production_changes
Update the hud_unit _fast_ftol rejection (provider conflict resolved at base). The remaining fix is symbols.json attribution; it is out of this lane's scope and needs csplit regen by the integrator.
## evidence
22/22 EXACT; provider_link PASS; 1 surplus COMDAT identical. object_audit FAIL(2): _get_hud_state and _hud_update_unit_local_player have split storage external, ours static, and cachebeta public=False. symbols.json lists _get_hud_state twice (around line 3429 without "static", around line 3452 with it). Placeholders _code_000c6590/_code_000c65a0 are also non-public.
## blockers
symbols.json storage attribution ("static": true) + csplit regen; not a provider issue.
## reopen_criteria
Fix the symbols.json static flags for the two symbols, regenerate the split, re-run object_audit.
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,65 @@
# source/math/geometry + source/game/players (header-packet function gains)
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[
"source/math/geometry::_convex_hull2d_perimeter",
"source/game/players::_player_set_action_result"
]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\players\\cand1.c"
]
## production_changes
Part of LAYER 2. geometry: no source change; retire the _convex_hull2d_perimeter park. players: apply layer2_ABRPL/players.c.diff (cand1). It drops the unattested current_distance/new_distance locals and uses `set_action = distance3d(unit_position, current_position) > distance3d(unit_position, new_position);` per /Od 0x5d0d50. That diff is neutral under the current header (67/70) and gains under ABRPL (68/70).
## evidence
Strict section_infos_equal under the ABRPL mirror: geometry::_convex_hull2d_perimeter EXACT (attested by the distance2d half alone); players::_player_set_action_result EXACT. Both objects retain other residuals (geometry 27/30, players 68/70).
## blockers
Owner ruling on the shared-header packet; other residuals keep these objects NonMatching.
## reopen_criteria
as leaf_map
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,66 @@
# source/objects/objects
## verdict
OBJECT_COMPLETE_CANDIDATE
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\action_charge\\cand1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\player_control\\cand1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\bored_camera\\cand1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\path_obstacles\\cand2.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\patch\\layer1"
]
## production_changes
Apply the LAYER 1 provider diffs. objects.c itself is unchanged. (1) source/ai/action_charge.c <- scratch/w/provider/patch/layer1/action_charge.c.diff (removes `#define real_random real_random_inline`/#undef and the hand copy; full file scratch/w/provider/action_charge/cand1.c). (2) source/game/player_control.c <- layer1/player_control.c.diff (removes the signed_angular_difference and interpolate_scalar renames and hand copies; limit2d left alone; cand1.c). (3) source/camera/bored_camera.c <- layer1/bored_camera.c.diff (removes the valid_real_vector3d_axes2/valid_real_normal3d renames, their two consumer-local prototypes, and the hand copy; cand1.c). (4) source/ai/path_obstacles.c <- layer1/path_obstacles.c.diff (MINIMAL cand2: removes only the point_in_sphere rename/#undef/hand copy; cand2.c. Layer 2 supersedes it with cand1). Then run ninja and `provider_link.py source/objects/objects` (expect PASS), retire the source/objects/objects entry in config/object_admission_rejections.json, and set objects.c to Matching. Credit no candidate-only helper bytes.
## evidence
Production objects.obj: gate 121/121 EXACT; object_audit PASS (220 sections ok, including .data/.bss); pdb_storage 0 disagreements over 225 symbols; 26 candidate-only COMDATs, 0 not identical (tools/surplus_all.py). Production provider_link fails 4 symbols (_point_in_sphere, _real_random, _signed_angular_difference, _valid_real_vector3d_axes2); _valid_real_vector3d was already ANY at base. Each provider candidate keeps all its rows identical to base: action_charge 21+1 pre-existing residual, player_control 49/49, bored_camera 9/9, path_obstacles 15+1 pre-existing residual. objcmp shows only NODUP->ANY with identical bytes, and every header-emitted helper is section_infos_equal to January's selected section. Genuine uses: real_random inlined in action_charge_setup (January relocs @16b/@171); signed_angular_difference used 3x and interpolate_scalar 4x (January out-of-line call @51f); valid_real_vector3d_axes2 called by bored_camera_update (@1e8); point_in_sphere used in obstacles_get_discs_in_sphere. Pair links (Link.Exe) objects.obj x each candidate provider: PASS in both orders, 0 LNK2005 of any symbol. Combined link objects + all 4 candidates: 0 LNK2005/LNK1169 forward and reverse. provlink2 with substitution: PASS. Ledger P1-P4, L1, C2.
## blockers
none once the Layer 1 provider diffs land (integrator must re-verify on the live tree with ninja + provider_link)
## reopen_criteria
n/a
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,62 @@
# source/render/render_debug
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\patch\\layer2_ABRPL"
]
## production_changes
No render_debug.c change. It is unlocked by the LAYER 2 ABRPL project_point2d body + decals cand4 (layer2_ABRPL/decals.c.diff). NOT unlocked by the ABR alternative. After the live verification, set Matching.
## evidence
Production: 36/36, object_audit PASS, pdb 0 disagreements; the ONLY blocker is _project_point2d (decals NODUP; header copy DIFFERENT from January). Under ABRPL: 36/36, audit PASS, 11 surplus COMDATs all identical, L2 provider link PASS. No parks and no rejections.
## blockers
Owner ruling on the shared-header packet.
## reopen_criteria
as leaf_map
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,69 @@
# source/structures/leaf_map
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\patch\\layer2_ABRPL",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\patch\\layer2_ABR_alternative",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\action_vehicle\\cand1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\object_lights\\cand1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\players\\cand1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\path_obstacles\\cand1.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\decals\\cand4.c",
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\sweep\\m_ABRPL.tsv"
]
## production_changes
LAYER 2 (shared-header packet ABRPL; needs an OWNER RULING). (a) source/math/real_math.h <- patch/layer2_ABRPL/real_math.h.diff (full file at patch/layer2_ABRPL/real_math.h). It sets distance2d to `return square_root(distance_squared2d(a, b));` and distance3d to `return square_root(distance_squared3d(a, b));`, removing the `v` locals. It gives project_point2d `short x/y` locals and a single ternary store `(fabs(...)<_real_epsilon) ? 0.0f : ...`. It adds `void lock_global_random_seed(void); void unlock_global_random_seed(void);` after get_global_local_random_seed_address in the RANDOM_MATH.C section. (b) source/math/random_math.h <- layer2_ABRPL/random_math.h.diff (removes those two prototypes). (c) source/ai/action_vehicle.c <- action_vehicle.c.diff (removes the distance2d/distance3d/distance_squared2d/negate_vector2d renames and hand copies; KEEPS object_get_bounding_sphere). (d) source/objects/object_lights.c <- object_lights.c.diff (/Od-attested `distance3d(&light->position, center)`, dropping the unattested light_position local). (e) source/game/players.c <- players.c.diff. (f) source/ai/path_obstacles.c <- layer2_ABRPL/path_obstacles.c.diff (full cand1, supersedes layer1). (g) source/effects/decals.c <- layer2_ABRPL/decals.c.diff (cand4 = cand3 + project_point2d). Parks: retire _convex_hull2d_perimeter (source/math/geometry). Re-measure _ai_test_line_of_sight base to size 1040, relocs 50, sha c524fe19529bfd4a72e95be52a875925bcc929d347b37471eb16b75bd393da78 (target unchanged). Then retire the leaf_map _distance3d rejection and set Matching after the live ninja + stable_verdicts + provider_link. Smaller alternative packet: patch/layer2_ABR_alternative (distance bodies + get_random_seed moved); it does NOT fix project_point2d, so leaf_map and render_debug stay blocked under it.
## evidence
/Od build: distance3d 0x42dc50 and distance2d 0x443e70 are `square_root(distance_squared*(a,b))` with no local; project_point2d 0x56d680 has short locals and a ternary. January-byte attestations of the new bodies: action_vehicle _distance3d 4cdb6852, and action_vehicle 17/17 with the January emission set (the _distance_squared2d emission arises only via the new distance2d body); geometry _convex_hull2d_perimeter becomes EXACT with the distance2d change alone; object_lights _find_point_lights 43/43 with the /Od caller fix; players _player_set_action_result EXACT with the /Od expression. The rejected trial's losses were decoded as TU name-count shifts: an oracle with 2 dummy names restores both canaries, and the canary windows admit only k=2. A `void f(void)` prototype = 2 units (calibrated). The compensating prototypes are genuine: main.c hits C4013 for lock/unlock while including real_math.h but not random_math.h. Mirror-tree method (junction tree, no preprocessing): control 446/446 units SAME. ABRPL full-board sweep: 0 LOSS, 0 compile failures. The preprocessing (/E) method was shown NOT neutral and discarded. leaf_map under ABRPL: 25/25, object_audit PASS, 18 surplus COMDATs all identical to January (including _distance3d, _project_point2d), provlink2 with candidate providers 33 rows PASS, pair links PASS in both orders.
## blockers
Owner ruling on the shared-header packet. Which zero-parameter random prototype(s) live in real_math.h is underdetermined: C4013 evidence exists for get_random_seed (network_client_manager) and for lock/unlock (main.c), and all these TUs also share game.h/main.h. The canary oracle only fixes the COUNT (two for ABRPL). The integrator must run the live ninja + whole-board stable_verdicts and `python -m tools.parked_functions`.
## reopen_criteria
If the owner rejects the compensating-prototype choice: find a different first-party-evidenced zero-parameter declaration (2 units) or equivalent in the canary-visible span. Verify with the canaries bitmap_drawing::_bitmap_copy, rasterizer_frame_statistics::_rasterizer_frame_statistics_draw and game_engine_race::_race_touch_flag under the mirror method.
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,62 @@
# source/units/biped_limp_noodle
## verdict
ADMISSION_FIX_PROPOSED
## newly_exact_functions
[]
## candidate_files
[
"C:\\halo-worktrees\\claude-fifty-objects-20260925\\scratch\\w\\provider\\patch\\layer2_ABRPL"
]
## production_changes
Same LAYER 2 ABRPL packet as leaf_map. Its specific needs are the distance3d body plus action_vehicle.c.diff, and the decals release of plane3d_from_point_and_normal (already in layer1 decals cand3 / layer2 cand4). Then retire the biped_limp_noodle _distance3d rejection and set Matching after the live verification.
## evidence
Production provider_link fails only _distance3d (action_vehicle NODUP; bytes differ from January) and _plane3d_from_point_and_normal (decals NODUP; bytes identical). Under ABRPL: 6/6 EXACT, object_audit PASS, 21 candidate-only COMDATs 0 not identical (_distance3d and the new _distance_squared3d equal January's selected copies), provlink2 with candidate providers 42 rows PASS, pair links with action_vehicle/decals/path_obstacles candidates PASS in both orders. Also passes under ABR.
## blockers
Owner ruling on the shared-header packet (see leaf_map).
## reopen_criteria
as leaf_map
## task notes
Key files (all under C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\):
- patch\layer1\*.diff (landable now).
- patch\layer2_ABRPL\ (recommended header packet: real_math.h/random_math.h full files + diffs, plus 5 .c diffs).
- patch\layer2_ABR_alternative\ (smaller header packet; does not fix project_point2d).
- sweep\m_base.tsv (control), m_ABRPL.tsv, m_ABR.tsv, m_ABn2.tsv (oracle), linkcensus.tsv.
- tools\ (mkmirror.py, mgate.py, msweep.py, rmmirror.py, pairlink.py, provlink2.py = provider_link + --subst, surplus_all.py, fnexact.py, surplus_check.py, odmap.py, helper_census.py, linkcensus.py).
- od\ (/Od dumps).
Method warnings for the integrator and other workers:
(1) Lab compiles via CL /E preprocessing shift the TU name-count arena and falsely flag canaries such as _decals_delete_permanent_from_cluster and _rasterizer_frame_statistics_draw. Use the mirror tree instead.
(2) mkmirror.py creates Windows junctions into source/. Remove mirrors only with rmmirror.py, never rm -rf; all mirrors are already removed.
Owner-ruling items:
(a) The header packet relies on the name-count law. Only the count of compensating zero-parameter prototypes in canary-visible headers is fixed by the oracle. Which random_math prototypes (get_random_seed vs lock/unlock) belong in real_math.h is underdetermined by consumer C4013 evidence.
(b) random_math.h itself is a 2026-09-04 reconstruction header.
Out-of-scope findings:
- _object_get_bounding_sphere: January's action_vehicle copy asserts "..\\objects\\objects.h" while the header hard-codes "c:\\halo\\source\\objects\\objects.h". The 8 ANY emitters (actor_moving, ai_debug, hud_nav_points, motion_sensor, object_lights, render_objects, bipeds, +1) therefore differ in relocation identity. It stays NODUP in action_vehicle and is kept.
- breakable_surfaces emits TU-specific non-identical copies of _project_point2d/_project_point3d/_plane3d_* (pre-existing).
- The link census also shows symbol-storage attribution gaps in rasterizer_xbox, actor_firing_position, model_animations, units and game_engine_king. These are the only remaining blockers for those now link-clean units.
- The units _point_from_line3d rejection predates the folded-COMDAT rule (3c74fa36); under Layer 2, units.obj is link-clean.
ledger: C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\provider\LEDGER.md (389 lines, every probe recorded). The resume found an empty ledger plus pre-cutoff tools and baseline gate rows, and continued from those. Sequence:
- P1-P4: provider candidates for path_obstacles, action_charge, bored_camera and player_control. Rows identical; NODUP->ANY with identical bytes.
- F2: actor_combat cross_product2d is BLOCKED on the held aim_grenade.
- F3/F4: /Od readout. distance3d and distance2d are square_root(distance_squared*(a,b)) with no local.
- P5: action_vehicle cand1 is 17/17 only under the AB header.
- P6: object_lights /Od caller fix.
- M1/M2: the /E preprocessing lab method is NOT neutral for name-count canaries. M3: a junction mirror-tree method; control 446/446 SAME.
- S2/S3: AB losses are pure name-count, proven by an oracle that is never landed.
- P7/P8: independent January-byte attestations (players, geometry).
- P9: canary windows admit only k=2. A void prototype = 2 units.
- P10: moving the multi-parameter plane/cone prototypes breaks canaries.
- P11/P14: moving random_math zero-parameter prototypes (C4013 evidence) gives ZERO-LOSS full-board sweeps. ABR moves get_random_seed; ABRPL moves lock/unlock and adds the /Od-attested project_point2d body.
- P12/P13: decals is also a NODUP provider; project_point2d attested by /Od.
- L1/L2/C1/C2: link tests and a board link census. Layer 1: 250->260/288 link-clean, 0 regressions. Layer 2: 268/288, 0 regressions.
- D1: patch set written. Mirrors were removed safely afterwards (junctions only); source/ verified intact, 0 tracked changes.
@@ -0,0 +1,68 @@
# review review_admit2: adversarial whole-object admission review of path, render_debug, biped_limp_noodle, leaf_map, actor_firing_position, game_engine, model_animations (tree 931ed8dc)
approve=False
## per_object
[
{
"unit": "source/ai/path",
"approve": false,
"issues": "Every byte and link check passes (32/32, audit PASS, 0 PDB disagreements, 10/10 surplus identical, link PASS). The blocker is an opaque span in path.h: path_debug_storage has `char __unknown140A0[0x5C]`, and path.c:1046 writes into it with a punning cast, `*(struct path_result *)state->debug->__unknown140A0 = *path;`. HCEX.pdb gives the genuine field at that offset: `struct path_result result`. FIX: patches/path_debug_result.patch changes path.h and path.c. The path candidate is 32/32 EXACT with object_audit PASS. Over all 53 transitive path.h users the sweep shows 0 section differences (control also 0; shadowing verified by a sentinel). Approve after this patch. Not a defect: path_state_begin's distance_squared3d + square_root spelling is load-bearing (the /Od distance3d form is residual [sha]). The explicit pad05[3] in path_edge is natural padding; HCEX has the same type."
},
{
"unit": "source/render/render_debug",
"approve": false,
"issues": "Byte and link checks pass (36/36, audit PASS, 0 PDB, surplus identical, link PASS). Source blockers: (a) point_from_line3d is hand-expanded in render_debug_vector, render_debug_tick and render_debug_line_offset, which rule 6 forbids explicitly; /Od 0x84a920, 0x84a510 and 0x848640 call 0x42e0d0 (point_from_line3d). (b) A caller-local view, struct vehicle_runtime_datum / render_debug_vehicle_data, with fields unknown426, unknown428[4] and unknown438[0x40], stands in for the genuine struct vehicle_datum in units/vehicle_datum.h. This is the same class as the dynavobgeom rejection. (c) Three dead TU-private defines: REAL_MATH_EXTERNAL_ARCTANGENT, REAL_MATH_EXTERNAL_DOT_PRODUCT3D and REAL_MATH_EXTERNAL_PLANE3D_DISTANCE_TO_POINT; stripping them changes no bytes. FIX for (a)-(c): patches/render_debug_genuine_helpers_and_vehicle.patch, which changes render_debug.c and adds a vehicle_datum_try_and_get macro to vehicle_datum.h. Result 36/36 EXACT, audit PASS, the new _point_from_line3d copy is identical to action_charge's and links. The 3 vehicle_datum.h users show 0 differences. Two items stay OPEN even after the patch. (d) render_debug_globals_definition pads with opaque_after_game_time[2] and opaque_after_entry_count[2]. A VC7 lab shows that separate short file statics get exactly those 4-byte slots, so January had separate statics and the aggregate is invented (hs_globals_external class). January's names cannot be recovered, and the name-hash layout order depends on them. (e) Six consumer-local `extern boolean debug_*` declarations have no header and no definer in the tree. Needs an owner ruling or new evidence before Matching."
},
{
"unit": "source/units/biped_limp_noodle",
"approve": false,
"issues": "Removing the _distance3d rejection is justified: all surplus COMDATs are identical to January's selected copies and the link passes; checks show 6/6 EXACT, audit PASS and 0 PDB disagreements. Blocker: `struct biped_limp_noodle_globals`, used as a static global, is an invented aggregate. HCEX.pdb shows two separate objects: `features`, a static local of move_relax_and_constrain_positions, and `last_positions`, a static local of relax_nodes_onto_environment. The HCEA decomp also has no aggregate. Tested: static locals with the HCEX names put last_positions first in .bss, which contradicts January, so that form is refuted. File-scope uninitialised statics `features` and `last_positions` reproduce January's layout (features@0, last_positions@44040) in either declaration order. FIX: patches/biped_limp_noodle_file_statics.patch plus patches/biped_limp_noodle_symbols_json.patch, which must be applied together. In symbols.json, line 23277 is renamed to _features (static) and a new entry {file_offset 5031952, _last_positions, static} is inserted; the full edited copy is scratch/w/review_admit2/config/symbols.json. csplit regen changes only biped_limp_noodle.obj, and object_audit passes against the scratch split. Advisory: move_relax pushes a collision user and then returns early twice without popping it. That is an authentic January bug and has no BUG comment. Both gotos are not load-bearing (the structured candidate bln_s13.c is 6/6 EXACT; optional). The duplicated else-tail is load-bearing and authentic."
},
{
"unit": "source/structures/leaf_map",
"approve": false,
"issues": "Removing the _distance3d rejection is justified: surplus identical, link PASS, 25/25, audit PASS, 0 PDB. The _leaf_map_initialize_section data entry still verifies. Its allow_incomplete_unit flag is redundant once the object is Matching (optional cleanup). Blocker: normalize_three_dee hand-expands magnitude3d and scale_vector3d. /Od 0x89fc90 calls both helpers (0x432950 and 0x42e220). FIX: patches/leaf_map_normalize_three_dee.patch. Result 25/25 EXACT, audit PASS; the new _magnitude3d and _scale_vector3d copies are identical to action_charge's and link in both orders. Approve after this patch. Checked and fine: the rest of the helper census matches /Od, and leaf_map_globals is attested by January's own assert strings. portal_count's `= 0` is required by the layout: without it January's unreferenced static _portal_count disappears."
},
{
"unit": "source/ai/actor_firing_position",
"approve": true,
"issues": "No blockers. Checks: 27/27 EXACT, audit PASS (73 symbols), 0 PDB disagreements, 17 surplus COMDATs identical, link PASS. The 2 functions objdiff undercredits are hidden_exact in semantic_report, and the data gap is 0. Batch-1 ownership packet verified: the evaluator tables and sort globals are cachebeta publics, and module 456 owns an 8-byte .bss contribution, so the initialisers are evidence-backed. The 14 functions made static are absent from publics. Source scan: no consumer-local prototypes or externs, no opaque spans. The view casts normalize2d((real_vector2d *)&facing) and point_from_line3d((real_point3d *)&direction,...) are /Od-attested (0x45bd00, 0x456860). The per-function helper census matches /Od."
},
{
"unit": "source/game/game_engine",
"approve": false,
"issues": "The batch-1 packet itself is sound. Storage matches PDB publics. The press_start_to_begin call is /Od-attested: the one empty static, stub 0x5a6510, is called only from nonplayer_post_rasterize, and jump table cases 0 and 1 call it. The three game_engine_get_type sites are /Od- and HCEA-attested. Checks: 180/180, audit PASS, 0 PDB, surplus identical, link PASS. The blockers are in the source and no fix is proposed. (a) Caller-local incomplete views with opaque spans. game_engine.c defines `struct rasterizer_debug_options { byte unused0[0x32]; short postgame_player_list_debug; }` and declares `extern struct rasterizer_debug_options rasterizer_debug_options;`, but the genuine header rasterizer_debug_options.h declares that global as `struct rasterizer_debug_options_definition` and names offset 0x32 pad3. This is the same class as the dynavobgeom rejection. The same pattern appears in game_engine_hud_globals {unused00[0x54]}, game_engine_postgame_hud_definition {unused00[0x3D4]}, scenario_netgame_flag, scenario_netgame_equipment, scenario_starting_equipment and item_collection_permutation. (b) 18 consumer-local prototypes of other files' functions (rule 9), listed in game_engine_consumer_local_protos.txt: hud_get_nav_point_render_type, custom_render_nav_point, global_network_game_server_get, player_ui_game_variant_specified, rasterizer_draw_unicode_string, bitmap_group_try_and_get_bitmap, hud_get_font_index, hud_get_text_color, sound_class_set_gain, network_game_server_switch_to_postgame, player_effect_screen_flash, unit_get_local_player_index, game_team_is_enemy, player_get_starting_location(_count), find_nav_point, and others. (c) `float get_blink_alpha` violates rule 19 (use real). (d) Stale comments claim functions are 'NonMatching' although they are now exact: one names code_0009c460 in get_starting_location_rating, another is a block near line 2803. (e) HCEA names player+0x70 `multiplayer.teleporter_index`; the source still uses unknown70 (players.h debt). Noted, not asserted: update_teleporter's j/i/k hand-expansion is load-bearing; the magnitude_squared3d spelling is residual."
},
{
"unit": "source/models/model_animations",
"approve": false,
"issues": "Checks pass: 32/32, audit PASS, 0 PDB (the 5 batch-1 static flags match publics), surplus identical, link PASS. Blocker: animation_get_root_velocity hand-expands vector_from_points3d. /Od 0x708160, identified by the assert 'animation->frame_count>1', calls it as vector_from_points3d(&previous[0].translation, &node[0].translation, root_velocity). FIX: patches/model_animations_root_velocity.patch. Result 32/32 EXACT, audit PASS, surplus unchanged. Approve after this patch. Checked and fine: the component-wise vector math in inverse_kinematics_adjust_matrices and interpolate_node_orientations is also component-wise in /Od (0x709470, 0x709250). Advisory: animation_graph_node and animation_graph_sound_reference are TU-private copies of shared tag layouts (duplicated in biped_limp_noodle.c and first_person_weapons.c), and no header owns them."
}
]
## checks
Every check ran against the production build at 931ed8dc (build/base, build/split, build/report.json, build/semantic_report.json). Evidence is in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_admit2\ (LEDGER.md plus gate_/audit_/pdb_/surplus_/plink_ files).
(1) gate.py --all: every row EXACT. path 32/32, render_debug 36/36, biped_limp_noodle 6/6, leaf_map 25/25, actor_firing_position 27/27, game_engine 180/180, model_animations 32/32.
(2) object_audit.py: all 7 PASS with 0 symbol differences. Surplus is COMDAT helpers, literals and the three D3D SDK tables. 58 objects already marked Matching carry the same D3D surplus, so it is systemic and not a per-object blocker.
(3) pdb_storage.py: 0 disagreements for all 7.
(4) surplus_identity.py: every candidate-only .text COMDAT is identical to January's selected copy, including _distance3d, which now equals the action_vehicle copy after Layer 2.
(5) provider_link.py: SELECTED-PROVIDER LINK PASS for all 7, both orders.
(6) Data: objdiff 3.3.1 undercredits code in render_debug (2 fns), game_engine (7) and actor_firing_position (2). All of them are hidden_exact in semantic_report, so accepted_ledger covers every function of every unit. The leaf_map .data section (2512 B) is covered by the existing _leaf_map_initialize_section entry. tools/audit_object_admission.py shows a 0/0 function/data gap for all 7 and 0 contradicted.
(7) No parks for any unit. Rejections exist only for leaf_map and biped_limp_noodle (_distance3d); both conditions are now satisfied.
(8) Batch-1 packet bf937aac checked against cachebeta publics and contribs.json:
- actor_firing_position: 4 externals are public. Module 456 owns an 8-byte .bss contribution, so the = 0 / = NULL initialisers are evidence-backed.
- 14 actor_firing_position, 4 game_engine and 5 model_animations functions made static: all are absent from publics.
- game_engine_press_start_to_begin: January has exactly one empty static function (c3). Inside the /Od game_engine.c range, the only internal-only empty stub is 0x5a6510, whose only caller is nonplayer_post_rasterize 0x5a164f. Jump table 0x5a172c = [164f,164f,1659,1659], so cases 0 and 1 call it. HCEA confirms the function is empty.
- game_engine_get_type: /Od 0x59df80 is called at the 3 fixed sites (0x59dbc0, 0x5aefc0, 0x5b25d0), HCEA agrees, and no other hand-expansion remains.
(9) Source scan: consumer-local prototypes (protoscan.py), placeholder names (none in code), opaque spans and views, view casts, and a per-function helper census compared against /Od for afp, path, leaf_map and model_animations. Also HCEX.pdb types and locals, a VC7 .bss layout lab, strip tests, a lab header-shadow harness (sentinel-verified), and sweeps over 53 path.h users and 3 vehicle_datum.h users (control and variant both show 0 section differences). csplit regenerated the split from a scratch config: only biped_limp_noodle.obj changes.
(10) Fix packet: git apply --check is clean for patches/*.patch, individually and together. Full candidate files are in final/. The edited symbols.json copy is config/symbols.json.
## issues
Only actor_firing_position can flip as-is. Every other object has a house-rule defect or other source problem, which the byte checks do not catch. Five of them have ready exact fixes; game_engine does not. All patches are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_admit2\patches\, full files in final\, and every patch passes git apply --check.
- path_debug_result.patch: path.h `char __unknown140A0[0x5C]` becomes HCEX `struct path_result result`, and path.c:1046 becomes `state->debug->result = *path;`. It byte-matches, and the 53 path.h users show 0 section differences.
- render_debug_genuine_helpers_and_vehicle.patch: three /Od-attested point_from_line3d calls; removes the vehicle_runtime_datum view in favour of the genuine struct vehicle_datum (adds a vehicle_datum_try_and_get macro to vehicle_datum.h); drops three dead REAL_MATH_EXTERNAL_* defines. Result 36/36 EXACT; the new _point_from_line3d copy is identical and links.
- leaf_map_normalize_three_dee.patch: magnitude3d + scale_vector3d; 25/25 EXACT.
- model_animations_root_velocity.patch: vector_from_points3d; 32/32 EXACT.
- biped_limp_noodle_file_statics.patch + biped_limp_noodle_symbols_json.patch: must land together. After a split regen, object_audit passes against the scratch split.
Rebuild and run the full sweep after applying.
Top-level approve=false because 6 of the 7 fail as-is.
@@ -0,0 +1,135 @@
# review review_layer2 (commit 931ed8dc, Layer 2 shared-header packet)
approve=False
## per_object
[
{
"unit": "source/math/real_math.h",
"approve": false,
"issues": "Body changes VERIFIED and approvable: distance3d (/Od 0x42dc50, January 4cdb6852), distance2d (/Od 0x443e70; the standalone bytes cannot tell old from new, but the change is attested by January action_vehicle's _distance_squared2d emission and the geometry _convex_hull2d_perimeter gain) and project_point2d (/Od 0x56d680, January 48bc73c5). The added `void lock_global_random_seed(void); void unlock_global_random_seed(void);` lines are canary-load-bearing count compensation. Without them 3 exact functions regress (V1). The pair is count-selected: V5 and V6 are equivalent, and the evidence-equal full move V2 regresses 4. The provider required an owner ruling and none is recorded. Hold for that ruling plus documentation. Never revert the two lines alone."
},
{
"unit": "source/math/random_math.h",
"approve": false,
"issues": "This is the other half of the same move and has the same status as real_math.h. get_random_seed and random_seed_debug_log stay here only because moving them regresses 4 exact functions. get_random_seed still gives C4013 in network_client_manager.c. The supplied genuine random_math.c does not include random_math.h, so this header is a 2026-09-04 reconstruction. It needs an owner ruling and a disclosure note."
},
{
"unit": "source/ai/action_vehicle.c",
"approve": true,
"issues": "None. 17/17 EXACT. _distance2d, _distance3d, _distance_squared2d and _negate_vector2d are now ANY copies equal to January (section_infos_equal). provider_link PASS; surplus 12 with 0 non-identical. The object_get_bounding_sphere hand copy is correctly kept (known assert-string conflict). Depends on the real_math.h body change."
},
{
"unit": "source/ai/path_obstacles.c",
"approve": true,
"issues": "None. 15/16; the _obstacles_test_circle residual is unchanged. _rotate_vector2d, _project_point3d and _point_in_circle are ANY copies equal to January. The call now uses the genuine point_in_circle name. provider_link PASS; surplus 15 with 0 non-identical. Its _distance_squared2d surplus needs the action_vehicle change and the header."
},
{
"unit": "source/effects/decals.c",
"approve": true,
"issues": "None new. 31/33; the 2 residuals are unchanged and the lane is closed. _project_point2d is ANY and equal to January (48bc73c5). provider_link fails only on _cross_product2d (actor_combat NODUP), which pre-dates this commit. The plane2d_from_points rename and REAL_MATH_EXTERNAL_PROJECT_POINT3D remain and were not touched by Layer 2. Depends on the header project_point2d body."
},
{
"unit": "source/objects/object_lights.c",
"approve": true,
"issues": "None. `distance3d(&light->position, center)` is /Od-attested at 0x787870: light+0x30 is pushed directly and the frame has no light_position slot. 43/43 EXACT. provider_link fails only on _object_get_bounding_sphere, which pre-dates this commit. Depends on the header."
},
{
"unit": "source/game/players.c",
"approve": true,
"issues": "None. The single-expression compare is /Od-attested at 0x5d0d50 (xmm0 spill across the second call; no distance locals). _player_set_action_result is EXACT (68/70). The /Od-shaped third arm gives 256 != 240, so the landed `= FALSE` form is correct. Under the old header it is neutral (67/70), so it can stay even under the fallback."
},
{
"unit": "config/parked.json",
"approve": true,
"issues": "The _convex_hull2d_perimeter retirement is correct (now EXACT). The _ai_test_line_of_sight re-measure is valid: parked_functions reports stale [] and invalid []. Both must be reverted with the header if the owner rejects (they are included in the fallback diff)."
}
]
## checks
Evidence and every probe are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review_layer2\LEDGER.md (R1-R10). Tools are in scratch/w/review_layer2/tools/: labgate.py, mkvariants.py, incscan.py, c4013.py, clflags.py.
R1: The landed real_math.h and random_math.h are byte-identical (cmp) to scratch/w/provider/patch/layer2_ABRPL/.
R2, claim (5): `python -m tools.campaign.stable_verdicts snapshot scratch/w/review_layer2/stable_now.json` gives 8,245 functions, 7,603 exact. The file is byte-identical to scratch/campaign/b2_stable.json. The b1->b2 diff is +2 (_player_set_action_result 240, _convex_hull2d_perimeter 96) with 0 regressions. build/base is current: 273 objects are newer than real_math.h (matching the 273 CL lines in b2_ninja1). Each of the 194 older objects was checked with CL /Zs /showIncludes, and none of them includes real_math.h or random_math.h.
R3/R4, claim (1):
- /Od readouts:
- 0x42dc50 distance3d is call distance_squared3d, then square_root, with no local.
- 0x443e70 distance2d has the same shape.
- 0x56d680 project_point2d has short x/y (word stores), two asserts, and one store to n[projection] through a temp, i.e. a single ?: expression.
- helper_census (section_infos_equal) over build/base:
- These copies are all ANY and all equal to January's single selected definer: _distance2d x3, _distance3d x19, _distance_squared2d x6, _distance_squared3d x44, _negate_vector2d, _point_in_circle, _rotate_vector2d x3, _point_in_sphere x6, _magnitude3d x63. No NODUP copy is left.
- _project_point2d is equal in 5 units. Only breakable_surfaces differs, and it already differed before Layer 2 (as did its _project_point3d and _vector_from_points3d).
- Standalone lab (lab_pp.c):
- The new distance3d and project_point2d bodies equal January (4cdb6852 and 48bc73c5). The old bodies do not.
- The OLD distance2d body is also byte-identical to January. The distance2d change is therefore attested only by /Od, by January's action_vehicle _distance_squared2d emission, and by the geometry gain.
- realcmp(n[proj], 0.0f), the literal /Od shape, is byte-identical to the landed fabs() spelling.
R5, lab header method:
- Copies at both <lab>/ and <lab>/math/ opened a second guarded file. That alone flipped _rasterizer_frame_statistics_draw, so it was a method defect and I fixed it.
- Fixed method: one copy at <lab>/math/, with the lab /I placed first.
- Control V0: all 9 canary units are identical to build/base in every code section: bitmap_drawing, rasterizer_frame_statistics, game_engine_race, bitmap_utilities, decals, geometry, rasterizer_xbox_models, error_geometry, collisions.
R6/R10, claim (2), the random prototype move:
- V1 (the landed header without the move) loses _bitmap_copy 2784, _rasterizer_frame_statistics_draw 4176 and _get_edge_vertex 416, and changes the bytes of _collision_move_point (already a residual).
- V2 (all four RANDOM_MATH.C prototypes moved) loses _bitmap_2d_alpha_bleed, _decals_delete_permanent_from_cluster, _rasterizer_frame_statistics_draw and __rasterizer_model_transparent_geometry_submit.
- V3 (get_random_seed only) loses bitmap_copy and frame_statistics_draw.
- V5 {lock, get_random_seed} and V6 {unlock, get_random_seed} are identical to the landed state.
- V4 (realcmp spelling) has no effect on these 9 units.
R7, C4013 (CL /Zs /W3):
- Landed: main.c 0. network_client_manager.c:1674 still gives C4013 for get_random_seed.
- Before the move: main.c:2902/2983 for lock/unlock, plus the get_random_seed warning.
- Full move: 0 everywhere.
Genuine-source evidence: the supplied random_math.c (haloleak2024/halo1) includes cseries.h, real_math.h, geometry.h and game_engine.h, but NOT random_math.h. random_math.h itself was created 2026-09-04 during reconstruction.
R8, claim (3):
- gate --all on production source:
- action_vehicle 17/17.
- path_obstacles 15/16 (_obstacles_test_circle was already a residual).
- decals 31/33 (both residuals pre-existing, lane closed).
- object_lights 43/43.
- players 68/70.
- geometry 27/30.
- provider_link:
- PASS: action_vehicle, path_obstacles, players.
- decals FAILs only on _cross_product2d, and object_lights only on _object_get_bounding_sphere. Both conflicts pre-date Layer 2 and are already listed in the brief.
- surplus_identity: 0 non-identical copies in action_vehicle (12), path_obstacles (15), decals (21) and players (13). object_lights (_object_get_bounding_sphere) and geometry (_plane2d_from_points, a header body Layer 2 did not touch) each have one DIFFERENT copy that was already there before this commit.
R9, claim (4):
- /Od 0x5d0d50: the first distance3d result goes fstp to a temp, then into xmm0, and is spilled across the second call before comiss. That is one expression, with no current_distance/new_distance slots.
- A lab with the /Od-shaped third arm gives size 256 != 240, so the landed `= FALSE` form is the one January's bytes support.
- /Od 0x787870: push center, then light+0x30, then call distance3d. There is no light_position slot.
Parks: `python -B -m tools.parked_functions` reports stale [] and invalid [].
## issues
Layer 2 is technically correct:
- The three helper bodies match the /Od build and January's selected copies.
- Every emitted helper copy is byte-identical to January's selected section.
- The provider objects keep every January row.
- The object_lights and players respellings are /Od-attested.
- The board sweep claim is reproduced exactly.
**The lock/unlock_global_random_seed move is an owner-gated item that was landed without a recorded owner ruling.** The move goes to a genuine owner header: real_math.h already has a RANDOM_MATH.C section, the supplied genuine random_math.c includes real_math.h and not random_math.h, and main.c showed C4013 for both functions. But it is also canary-load-bearing name-count compensation:
- The strip test fails. With the landed header minus the move, _bitmap_copy, _rasterizer_frame_statistics_draw and _get_edge_vertex (7,376 B) are lost, and the bytes of _collision_move_point (already a residual) change.
- The bytes decide only the count, not which prototypes. Moving {lock, get_random_seed} or {unlock, get_random_seed} gives results identical to the landed move.
- The evidence-equal full move regresses 4 other exact functions: _bitmap_2d_alpha_bleed, _decals_delete_permanent_from_cluster, _rasterizer_frame_statistics_draw and __rasterizer_model_transparent_geometry_submit. The full move covers lock, unlock, get_random_seed and random_seed_debug_log.
- get_random_seed still gives C4013 in network_client_manager.c:1674 after the landed move. That is the same class of evidence the commit cites for main.c.
- The provider (P13, and the wave1 result.json blocker) said explicitly that the choice is underdetermined and needs an OWNER RULING.
- The commit message presents the move only as evidence-driven placement ("main.c calls them without random_math.h"). It does not say the move is load-bearing or that the subset was picked to balance the count.
Required amendment, no source change needed if the owner accepts:
1. Put the V1/V2/V5/V6 evidence (my ledger R6/R10) to the owner, including that the full genuine move is blocked by 4 regressions.
2. Record the ruling, with a Layer 2 section in docs/object_matching_logs/claude_fifty_objects_20260925.md. That section should say the move compensates the -4 declared-name shift from the body corrections, name the 3 functions it protects, and give the reopen criterion: move get_random_seed and random_seed_debug_log and retire random_math.h once the other count gaps are recovered.
If the owner rejects:
- Do NOT revert only the two prototype lines. Per V1 that regresses 3 exact functions.
- Apply scratch/w/review_layer2/fallback_revert_except_players.diff. It is `git diff 931ed8dc 931ed8dc~1` for real_math.h, random_math.h, action_vehicle.c, path_obstacles.c, decals.c, object_lights.c and config/parked.json, and `git apply --check` is clean.
- That fallback loses _convex_hull2d_perimeter (its park is restored) and _player_set_action_result.
- players.c may stay: it is /Od-attested and neutral under the old header (67/70).
Optional, not required: the literal /Od spelling of the project_point2d condition is realcmp(plane->n.n[projection], 0.0f). It is byte-identical to January and has no effect on 9 canary units, but it would need a full-board sweep before landing.
Pre-existing items, not caused by Layer 2: breakable_surfaces' _project_point2d/_project_point3d/_vector_from_points3d still differ from January (content changed, still non-identical). Also the /Od RTC name for find_point_lights' cursor is `state`, not `reference_index`.
@@ -0,0 +1,39 @@
# review source/cache/cache_files_windows
approve=True
## per_object
[
{
"unit": "source/cache/cache_files_windows",
"approve": true,
"issues": "APPROVE as OBJECT_COMPLETE. Apply C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/cache_files_windows/production.patch; it touches only source/cache/cache_files_windows.c, and `git apply --check` is clean against 931ed8dc. The CRLF full file scratch/w/cache_files_windows/cache_files_windows.c is an equivalent alternative. Then retire the parked.json entry for _cache_files_open_cache_files. Verified: 50/50 exact, object_audit PASS (0 differ), pdb_storage 0, surplus_identity 0 and provider_link PASS 6/6, data 100%. objdiff 3.3.1 gives open_cache_files 100% and matched_code 5968/6144; the remainder is the accepted semantic-coff precache_map_status. Strip and equivalent-spelling labs are clean. Reviewer notes: scratch/w/review2_cache_files_windows/REVIEW.md."
}
]
## checks
Read all of WORKER_BRIEF.md (including section 7), the house rules, the campaign log, the worker LEDGER, the prior closeout ledgers for this unit and the parked.json entry. I did not run ninja or any git-mutating command, and I edited no tracked file. All reviewer work is in C:/halo-worktrees/claude-fifty-objects-20260925/scratch/w/review2_cache_files_windows/ (REVIEW.md, lab/, od/).
(1) Patch. `git apply --check scratch/w/cache_files_windows/production.patch` is clean against the current tree. HEAD is 931ed8dc, the blob is 4c8eadd2 and the worktree file is clean. HEAD's file with the patch applied is identical to cand.c. The CRLF full file differs from the worktree file only in the intended lines, and both files are all CRLF with no LF-only lines.
(2) gate.py --source (CRLF full file) --all --out review2/cand.obj: `== exact 50 residual 0 unwritten 0 (of 50)`. My obj differs from the worker's only in the gate temp-file names and the timestamp. tinfo shows the candidate's _cache_files_open_cache_files at 752 B / 47 relocs with sha f1c88310c585856c, which equals January and the parked target sha. In alndiff the counts are T 234 / O 234. The only row shown is the pre-existing literal attribution for 01.01.14.2342, which normalizes equal.
(3) object_audit on cand.obj: PASS, 87 January symbols, 0 differ. The surplus (.drectve, the buffer/header/01.01.14.2342 literals and the three D3D selectany tables) is identical to the surplus in production build/base. Production's audit fails only on open_cache_files. The D3D surplus is systemic: prior reviews counted 57-58 Matching objects that carry it.
(4) pdb_storage: 0 disagreements. surplus_identity: 0 code COMDATs. provider_link with cand.obj: PASS 6/6.
(5) Data: build/report.json shows .rdata 1192 and .bss 12412, both 100%. I re-ran objdiff 3.3.1 (build/tools sha1 3130e428) in review2/od. The candidate gets matched_code 5968/6144 and data 13604/13604, and open_cache_files is 100%. The only function below 100% is _cache_files_precache_map_status at 91.52542, which is strict-exact and already in the semantic_report accepted_ledger with semantic-coff proof. Production's matched_code is 5222.
(6) fake_match_scan on the candidate: 0 leads. The unit has no admission or semantic rejection entries. No file textually includes the .c. The function is static and absent from cachebeta publics, which is consistent.
(7) Strip and robustness labs:
- s1, the nested equivalent `if(!valid){if(file!=INVALID){CloseHandle;file=INVALID;}}`: EXACT.
- s2, the swapped `file!=INVALID && !valid`: EXACT.
- So the match does not depend on one particular spelling.
- s4, the PC /Od literal form: residual. This confirms h7a.
- s5, with no file guard: residual, and it would call CloseHandle(INVALID_HANDLE_VALUE). The guard is needed for correct behaviour, not added for the match.
- s3, which drops the pre-existing failure-arm `valid = FALSE`: residual at 237 insns, so that store is load-bearing.
(8) Evidence spot-checks:
- January bytes: 0x6b valid=0 at the loop top; 0x1b6 valid=0 in the failure arm; 0x1e2/0x1e8 CloseHandle and file=-1; J1 at 0x1ef; the 823 arm at 0x2b1 does `jmp 0x1f5` with no -1 store.
- /Od 0x857520: a trailing `if(!success){if(file!=-1)CloseHandle(file); file=-1;}` after the whole if/else.
- October DEBUG 0x1bd5f0: J1 at 0x1bd7df and the 773 arm after `ret`, the same layout as January.
- October release: `xor bl,bl` in the failure arm at 0x158b8e.
## issues
No blocking issues. Notes for the integrator and owner:
(a) The source-shape evidence for the trailing cleanup is an analog. It comes from a later first-party function in a different file (/Od 0x857520, saved_games/game_state_pc.c), built from the same CreateFile/resize idiom, not from this function itself. The October XBEs support the layout but cannot tell the source forms apart, because both base and candidate reproduce the October release. The acceptance therefore rests on four things: the strict-exact result, the plain, behaviour-preserving spelling that needs no decoration, the fact that equivalent spellings also match, and that analog. This meets the recorded 20260919 reopen criterion: the cold CreateFileA-failure placement is explained and the success store at 0x1a4 does not move.
(b) The failure-arm `valid = FALSE;` is redundant in meaning, because valid is already FALSE from the loop top. It was already in production and is not part of this patch. January's bytes attest it independently (0x1b6), and so does the October release build (xor bl,bl). It is load-bearing (lab s3).
(c) Assert-line bookkeeping: the 811 to 823 gap is 12 lines. Moving the two lines out of the failure arm makes our text span shorter still. This is text-only and weak, since comments and blank lines count, and it does not change the verdict.
(d) Bookkeeping: the integrator must retire the config/parked.json entry for source/cache/cache_files_windows / _cache_files_open_cache_files (target sha f1c88310...). Otherwise ninja PROGRESS fails on parked bytes moving. No semantic_matches or semantic_data_matches entry is needed, and no header, config or symbols.json change is involved.
@@ -0,0 +1,81 @@
# review source/camera/editor_flying_camera
approve=True
## per_object
[
{
"unit": "source/camera/editor_flying_camera (packet A: production.patch)",
"approve": true,
"issues": "None blocking. 21/21 strict EXACT, object_audit PASS, surplus identical to production, provider link PASS, data unchanged and still credited by the existing grouped entry. The integrator must record in the ledger the rulings on the attested dead `left` pair and the byte-inert euler view cast. Config ops: retire the park, config.json:741 to Matching after the full chain, reword the stale grouped-entry reason sentence."
},
{
"unit": "source/camera/editor_flying_camera (packet B: storage_optional.patch + symbols.json lines 14089/22443)",
"approve": true,
"issues": "Verified with a real scratch csplit run: only this object changes, 40/40 sections exact, audit PASS, objdiff 3.3.1 credit unchanged. Required in the same commit: the camera-static-sections _editor_camera_speed owner.storage re-pin from 2 to 3 for target and base. Fixes the 2 storage-class disagreements with cachebeta publics that already exist in production."
}
]
## checks
All results are in C:\halo-worktrees\claude-fifty-objects-20260925\scratch\w\review2_editor_flying_camera\REVIEW.md. The tracked tree was never modified.
1. Patches. HEAD is 931ed8dc. `git apply --check --whitespace=error-all` is clean for production.patch, for storage_optional.patch, and for both together. HEAD blob plus production.patch gives cand.c, which is byte-identical to the worker's F2.c after LF normalisation. cand.c plus the storage patch gives candB.c, which is identical to F2_static.c.
2. gate.py --all on cand.c against the real split: 21/21 EXACT. _editor_camera_set_position_and_roll is 432 bytes with 30 relocations, raw sha 71253260755d0770 (same as the worker's F2.obj).
3. object_audit on cand.obj: PASS (47 January symbols, 0 differ). Surplus list is identical to production. Comparing all 81 sections of cand.obj and production with section_infos_equal, only the function and .debug$S differ; .debug$S is not January-owned. surplus_identity: 17 code COMDATs, 0 not identical. provider_link: PASS for cand.obj and for candB.obj.
4. /Od build re-read.
- od_spar.txt plus rtc_at.py confirm the RTC names at 0x527c9c: matrix, forward, left, up, diff, hack_angles.
- The statement order matches the worker's account: cross_product3d(&up, &forward, &left) at 0x527bb5, then normalize3d(&left), then the diff cross product.
- The slot at -0x68 (`left`) is never read afterwards, so `left` is dead in /Od too.
- The angles pointer is pushed unchanged to 0x527700, which is editor_camera_set_focus (asserts "position"/"angles" at lines 133/134).
5. NEW independent evidence: assert line anchors.
- January to /Od line offsets are +4 for get_focus, set_focus, move_to_point, set_position, set_position_and_roll (169/170 -> 173/174) and set_mode (302/308 -> 306/312).
- They are +5 from update (340) onward.
- There is zero drift across the whole set_position_and_roll body. That span in the later build contains the `left` declaration and the cross/normalize pair, so January's same span has the same line count. This supports the pair being in January's source, not a later addition.
6. My strip and control tests (gate --all):
- R1, `left` plus the pair removed: residual, sha c334539c (the 3 x87 blocks). The pair is load-bearing.
- R2, normalize3d(&left) dropped: identical bytes.
- R3, cast dropped: identical bytes plus a C4133 warning. The cast is byte-inert.
- R4, declaration only: residual. It is not a count effect.
- R5, cross_product3d(&forward, &matrix.up, &left): residual. The effect depends on which vectors are read.
- R6, pair moved before normalize3d(&forward): 21/21. The /Od position is inside the working window.
7. Warnings and fake scan. /W3: cand and candB produce the same warnings as HEAD. fake_match_scan: 0 leads.
8. Data.
- objdiff 3.3.1 (sha1 3130e428) was run on a scratch project. The production row reproduces build/report.json exactly (.bss 50, .data 22.24, .rdata 100).
- cand: code 5065/5065 and 21/21 functions, data unchanged.
- Using tools.semantic_progress, the camera-static-sections member snapshots are unchanged for packet A.
9. Packet B, verified with a real csplit run (not the emulation).
- I ran build/tools/csplit.exe into scratch with a config copy. The control run with the unmodified config was byte-identical to build/split.
- With the worker's symbols.json, only editor_flying_camera.obj changes among the 833 objects. It is byte-identical to the worker's emulated split.
- candB against that split: 40/40 January sections exact. object_audit pointed at it: PASS (47/0).
- objdiff 3.3.1 data percentages are unchanged; .rdata stays 100%.
- The grouped-entry snapshot differs only in the _editor_camera_speed owner storage, 2 -> 3, for both target and base.
- cachebeta_publics.txt does not list _editor_camera_speed or _editor_camera_speed_steps, so they are file-static; _editor_custom_render is public.
- No other split or base object names either symbol. The symbols.json copy differs only at lines 14089 and 22443, edited in place.
10. Config targets checked: the parked.json entry is at lines 1392-1402 and config.json line 741 is the editor_flying_camera NonMatching row. The unit has no admission-rejection entry, and allow_incomplete_unit only relaxes a check (semantic_progress.py:678).
## issues
No technical defect found; every measured claim in the proposal reproduced. Policy rulings and conditions for the integrator:
(1) Dead `left` pair. It is dead in January (VC7 removes it) and it fails a literal strip test. I rule it ADMISSIBLE, not a section-7 filler:
- the first-party /Od build has the same RTC name and the same statement pair, with the same arguments, position and declaration order;
- January's own assert line anchors show zero drift across the function body;
- the effect depends on which vectors are read (R5 and the worker's C1 are not exact);
- it is not a declaration or count effect (R4 is not exact).
This fits the "independent evidence authenticates that exact construct" standard. The in-source comment discloses that `left` is never read. The integrator must record this ruling and the anchor-drift evidence in the object ledger. Escalate to the owner only if section 7 is read as covering attested dead statements.
(2) euler3d->euler2d cast. It meets the brief's section-2 criteria: per-site /Od pass-through, compatible yaw/pitch prefix, byte-inert strip test, and a strictly exact caller. It is not owner-gated. It is the first cast of this kind in the tree, so disclose it in the docs ledger. The parked.json evidence sentence that favours "copying instead of punning" is superseded: the copy form is not January's code (464 bytes).
(3) Landing conditions.
- Packet A: retire the parked.json entry _editor_camera_set_position_and_roll (source/camera/editor_flying_camera). Change config.json line 741 from NonMatching to Matching only after the full chain passes. Reword the stale "remaining camera code function stays residual" sentence in the camera-static-sections reason.
- Packet B: apply it in the same commit as the symbols.json lines 14089 and 22443. Re-pin the grouped entry's _editor_camera_speed owner.storage from 2 to 3 for BOTH target and base, or progress will fail with "snapshot changed". Its objdiff 3.3.1 credit is already measured unchanged.
- Recommend landing A and B together. With A alone, the object still has 2 storage-class disagreements with cachebeta publics; they already exist in production and A does not introduce them.
(4) Optional follow-up, not blocking: HCEX names the .data+0 file static `speed`, while the project keeps the name editor_camera_speed.
Loaded 100 of 1170 files, more files were not shown because too many files have changed in this diff. Show more