diff --git a/config/symbols.json b/config/symbols.json index 8cd1f043..6d05bb43 100644 --- a/config/symbols.json +++ b/config/symbols.json @@ -572,10 +572,10 @@ { "file_offset": 204416, "flags": 32, "name": "_code_00031e80" }, { "file_offset": 204848, "flags": 32, "name": "_code_00032030" }, { "file_offset": 205504, "flags": 32, "name": "_code_000322c0" }, -{ "file_offset": 205552, "flags": 32, "name": "_code_000322f0" }, +{ "file_offset": 205552, "flags": 32, "name": "_actor_reset_idle_vocalization_timer" }, { "file_offset": 205760, "flags": 32, "name": "_actor_communication_update" }, { "file_offset": 206000, "flags": 32, "name": "_code_000324b0" }, -{ "file_offset": 206128, "flags": 32, "name": "_code_00032530" }, +{ "file_offset": 206128, "flags": 32, "name": "_ai_communication_update_speech_timers" }, { "file_offset": 206800, "flags": 32, "name": "_ai_communication_get_player_rating" }, { "file_offset": 207632, "flags": 32, "name": "_ai_conversation_stop" }, { "file_offset": 207776, "flags": 32, "name": "_ai_conversation_actor_deleted" }, diff --git a/docs/object_matching_logs/opus_wave_canonical_reconciliation_20260902.md b/docs/object_matching_logs/opus_wave_canonical_reconciliation_20260902.md new file mode 100644 index 00000000..deb004a9 --- /dev/null +++ b/docs/object_matching_logs/opus_wave_canonical_reconciliation_20260902.md @@ -0,0 +1,158 @@ +# Opus 44,928-byte wave canonical reconciliation (2026-09-02) + +## Purpose + +This ledger reconciles the reported `+44,928` padded-byte Claude/Opus session on +`claude/breakable-surfaces-20260830` at `617703d2e` against canonical +`jonas/ai-debug-integration-20260829` at `1cc83929d`. It answers a narrower and +more useful question than comparing branch totals: which January code sections +were strict exact in Opus but were not strict exact in canonical? + +The comparison used rename-stable keys of `unit + target code-section ordinal`. +That is important because canonical has replaced many `code_...` names with +evidence-backed semantic names. The frozen inputs are: + +- `scratch/opus-617703d2e-stable.json`: 8,246 sections, 4,982 strict exact; +- `scratch/canonical-1cc83929d-stable.json`: 8,245 sections, 5,309 strict exact. + +Canonical has 375 strict-exact sections absent from the older Opus snapshot. +Opus has only 48 strict-exact sections, totaling 15,696 padded bytes, which are +not strict exact in that canonical snapshot. Therefore the reported 44,928-byte +session gain is not a 44,928-byte canonical deficit. + +## Reconciliation result + +| Disposition at the frozen canonical baseline | Functions | Padded bytes | +| --- | ---: | ---: | +| Truthful ordinary-C implementation retained as a measured residual | 25 | 11,456 | +| Credible strict-exact recovery admitted in this wave | 1 | 704 | +| Deliberately not emitted pending a real owner/caller or credible source | 22 | 3,536 | +| **Total Opus-only strict set audited** | **48** | **15,696** | + +The 25 residual functions are not lost source progress. Canonical deliberately +keeps their semantic implementation and refuses only the Opus spelling that +made the bytes exact through assembly, forced inlining, `noinline`, volatile or +barrier steering, false BSS/type ownership, duplicate inline-helper bodies, or +other unsupported source shapes. Their detailed evidence remains in the object +ledgers cited below. + +## Recovered exception: `ai_communication_started` + +`_ai_communication_started` is the credible exception. The Opus body is +ordinary typed C, contains no compiler-steering construct, and compiles to all +704 January bytes with all 48 relocation identities. Its normalized SHA-256 is +`eed6aa664227288ffe15a7f7c3c51791dbe88c42ef947d279e9a66c5422a0cd8` +for both target and candidate. + +The genuine call graph also emits the 672-byte speech-timer helper. Independent +HCEA source names that helper `ai_communication_update_speech_timers`, and names +the already-exact 208-byte reset helper +`actor_reset_idle_vocalization_timer`. Those semantic names replace +`code_00032530` and `code_000322f0` in source and `config/symbols.json`; neither +address-based name is retained. + +The speech-timer helper itself is an honest residual: target and candidate are +both 672 padded bytes with 43 relocations, with target normalized SHA-256 +`91999fda75815c6638478824f3efa53ee6717572f365530362cbec00e78a50ac` +and candidate SHA-256 +`885fdb81966284acf5ba7138a4bbbd2221e7c61c89114fdd6f53b96ba89b81a0`. +Objdiff reports 97.68 percent similarity. The remaining differences are the +previously documented natural register/lifetime schedule; no register hint, +volatile access, barrier, pragma, assembly, false alias, or inert dependency is +introduced. The typed implementation is retained and fuzzy-parked. + +The public `ai_communication_started` declaration is placed in its owner header, +`source/ai/ai_communication.h`. The two private helper declarations remain in +their owner translation unit. Typed object access uses `unit_get` and +`actor_get`; tag-block access uses `TAG_BLOCK_GET_ELEMENT`. + +## Already retained as credible residuals + +These 25 Opus-exact sections, totaling 11,456 padded bytes, already exist in +canonical as ordinary-C residual implementations: + +- AI: `action_converse_perform`, `actor_combat_find_nearby_target`, + `actor_combat_check_collateral_damage`, `infection_swarm_aim_jump`, and + `find_turning_point`; +- platform/camera/cutscene: `get_mutex_from_pool`, `dead_camera_update`, and + `recorded_animation_play_internal`; +- game engine: `game_engine_verify_current_map`; +- players: `unit_should_autopick_weapon`, `player_set_action_result`, + `player_handle_powerup`, `player_handle_powerup_minor`, + `player_update_powerups`, `player_teleport_on_bsp_switch`, + `players_reconnect_to_structure_bsp`, `player_examine_nearby_vehicle`, + `player_examine_nearby_device`, `player_examine_nearby_item`, and + `player_examine_nearby_objects`; +- memory: `lrar_allocate`; +- object lights: `light_unmarked`, `light_mark`, + `find_point_lights_for_object_in_cluster`, and `light_reconnect_to_map`. + +The exact Opus forms are intentionally not restored. In particular: + +- `action_converse_perform` used gameplay inline assembly to force a register; +- `actor_combat_find_nearby_target` disturbed the protected + `point_from_line3d` inline schedule; +- `actor_combat_check_collateral_damage` requires an owner-header shape that + regresses `units.obj` from 189/189; +- `game_engine_verify_current_map` and `lrar_allocate` depended on unsupported + `noinline`/manual helper expansion; +- the Players group used combinations of copied helpers, dead emitters, + volatile/barrier steering, duplicate fetches, and representation tricks; +- `recorded_animation_play_internal` reached exact only with false BSS + ownership; and +- the Object Lights forms relied on duplicate bodies, false linkage, or helpers + without a genuine emitting caller. + +## Deliberately unwritten boundaries + +The remaining 22 Opus-exact sections total 3,536 padded bytes. They stay +unwritten until their authentic source owner naturally emits them: + +- `midpoint3d` (64): a fake standalone duplicate of the shared real-math inline; +- Decals helpers (704 total): `projection_from_vector3d`, `project_point2d`, + `triple_product3d`, `plane2d_from_points`, + `plane3d_from_point_and_normal`, and + `bsp3d_get_plane_from_designator`; these were unowned standalone copies of + shared inline/macro behavior; +- `player_examine_nearby_unit` (16): no truthful source form currently emits + the target-only leaf; +- Weapons (1,632 total): `animation_update` has an unresolved authentic COMDAT + grouping; `weapon_trigger_get_charged_fraction` and + `weapon_export_function_values` atomically introduce a candidate-owned float + constant that January treats as external; and + `weapon_handle_potential_inventory_item` depended on a layout-only surrogate + contradicting authenticated fields; +- Object Lights (1,056 total): the two anonymous 32-byte leaves, + `render_debug_light`, `light_get_bounding_sphere`, `sample_lightmap`, and + `sample_diffuse_texture` still lack a credible complete owner/caller graph; +- four Active Camouflage XDK wrappers (64 total): the targets are correctly + named `IDirect3DDevice8` wrappers, but must be instantiated by the real typed + cache/draw functions rather than copied, assembled, or emitted by a dummy + anchor. + +## Validation and provenance + +The isolated gate moved `ai_communication.obj` from 36 exact / 0 residual / 12 +unwritten to 37 exact / 1 residual / 10 unwritten. Every inherited exact section +remained exact. The exact recovery and helper were compiled from the complete +translation unit; no proof-only caller or dormant emission anchor is retained. + +Primary local evidence: + +- `docs/object_matching_logs/claude_wave_small_exact_batch_20260831.md`; +- `docs/object_matching_logs/players_obj_january_safe_harvest_20260902.md`; +- `docs/object_matching_logs/object_lights_obj_small_family_recovery_20260902.md`; +- `docs/object_matching_logs/game_engine_obj_jonas_no_fake_inline_cleanup_20260831.md`; +- `docs/object_matching_logs/thread_win32_obj_house_rule_correction_20260831.md`; +- `docs/object_matching_logs/action_converse_obj_jonas_ordinary_c_fuzzy_park_20260830.md`; +- `docs/object_matching_logs/actor_combat_obj_collateral_damage_rejected_20260831.md`; +- `docs/object_matching_logs/recorded_animations_obj_natural_recovery_20260830.md`; +- `docs/object_matching_logs/weapons_obj_jonas_safe_trio_and_charged_export_blocker_20260901.md`; +- `docs/object_matching_logs/lrar_cache_obj_jonas_no_fake_inline_cleanup_20260831.md`; +- `docs/object_matching_logs/rasterizer_xbox_active_camouflage_obj_jonas_credibility_recovery_20260830.md`; +- HCEA `src/blam/ai/ai_communication_started.c`, + `ai_communication_update_speech_timers.c`, and + `actor_reset_idle_vocalization_timer.c`. + +January's Halo CE Xbox COFF remains the sole byte-admission authority. Donor +source supplies names, types, and control-flow evidence only. diff --git a/source/ai/ai_communication.c b/source/ai/ai_communication.c index 49ec53d0..3689ed8c 100644 --- a/source/ai/ai_communication.c +++ b/source/ai/ai_communication.c @@ -59,13 +59,13 @@ symbols in this file: 000322C0 0030: _code_000322c0 (0000) 000322F0 00d0: - _code_000322f0 (0000) + _actor_reset_idle_vocalization_timer (0000) 000323C0 00f0: _actor_communication_update (0000) 000324B0 0080: _code_000324b0 (0000) 00032530 02a0: - _code_00032530 (0000) + _ai_communication_update_speech_timers (0000) 000327D0 0340: _ai_communication_get_player_rating (0000) 00032B10 0090: @@ -549,6 +549,7 @@ symbols in this file: #include "actor_definitions.h" #include "actors.h" #include "actor_types.h" +#include "ai_scenario_definitions.h" #include "game/game.h" #include "main/console.h" #include "memory/data.h" @@ -557,6 +558,7 @@ symbols in this file: #include "scenario/scenario.h" #include "scenario/scenario_definitions.h" #include "tag_files/tag_files.h" +#include "units/dialogue_definitions.h" #include "units/units.h" /* ---------- constants */ @@ -597,6 +599,7 @@ enum _ai_sound_volume_shout = 3, _ai_communication_team_human = 0, _ai_communication_team_covenant = 1, + NUMBER_OF_AI_COMMUNICATION_TEAMS = 2, }; #define COMMUNICATION_CLOSE_DISTANCE 5.0f @@ -911,12 +914,18 @@ static struct scenario_conversation_definition_view *code_000322c0( long conversation_index); boolean actor_is_fighting( long actor_index); -static void code_000322f0( +static void actor_reset_idle_vocalization_timer( long actor_index); static void code_000324b0( long actor_index, long prop_index, struct ai_information_packet *information); +static void ai_communication_update_speech_timers( + long unit_index, + short priority, + short vocalization_type, + short dialogue_type_index, + short reply_table_index); short unit_test_speech( long unit_index, short priority, @@ -2088,7 +2097,7 @@ short ai_conversation_status( return status; } -static void code_000322f0( +static void actor_reset_idle_vocalization_timer( long actor_index) { struct actor_datum *actor; @@ -2331,7 +2340,7 @@ void actor_communication_update( if (actor->control.idle_vocalization_timer == 0 || actor->control.idle_vocalization_combat != in_combat) { - code_000322f0(actor_index); + actor_reset_idle_vocalization_timer(actor_index); } if (actor->control.idle_vocalization_timer > 0 && @@ -2406,6 +2415,273 @@ static void code_000324b0( return; } +static void ai_communication_update_speech_timers( + long unit_index, + short priority, + short vocalization_type, + short dialogue_type_index, + short reply_table_index) +{ + struct unit_datum *unit = unit_get(unit_index); + struct actor_datum *actor; + long time; + long notification_time; + + if (unit->unit.actor_index == NONE) + { + actor = NULL; + } + else + { + actor = actor_get(unit->unit.actor_index); + } + + time = game_time_get(); + notification_time = time + + MAX(0, unit->unit.speech.sound_timer - communication_overlap_time_modifier); + unit->unit.speech.last_speech_finished_time = notification_time; + + if (actor) + { + short communication_team; + + actor_reset_idle_vocalization_timer(unit->unit.actor_index); + communication_team = actor_communication_team(unit->unit.actor_index); + if (communication_team != NONE) + { + if (priority <= _unit_speech_shout) + { + ai_globals->last_chatter_time[communication_team] = MAX( + ai_globals->last_chatter_time[communication_team], + notification_time); + if (priority >= _unit_speech_talk) + { + ai_globals->last_talk_time[communication_team] = MAX( + ai_globals->last_talk_time[communication_team], + notification_time); + } + if (priority >= _unit_speech_shout) + { + ai_globals->last_shout_time[communication_team] = MAX( + ai_globals->last_shout_time[communication_team], + notification_time); + } + + if (ai_debug.print_speech_timers) + { + char const *team_name = + global_communication_team_names[communication_team * 2]; + + error( + 2, + "%s %s %d/%s: %s %d", + team_name, + unit_get_speech_priority_name(priority), + dialogue_type_index, + dialogue_get_vocalization_name(vocalization_type, TRUE), + priority >= _unit_speech_talk ? "talk" : "chatter", + notification_time - time); + } + } + + if (dialogue_type_index != NONE) + { + struct dialogue_event_status *event; + struct dialogue_usage const *usage; + + match_assert( + "c:\\halo\\SOURCE\\ai\\ai_communication.c", + 0xC9C, + (dialogue_type_index >= 0) && (dialogue_type_index < global_dialogue_event_count)); + usage = &global_dialogue_table[dialogue_type_index]; + event = &global_dialogue_events[ + dialogue_type_index * NUMBER_OF_AI_COMMUNICATION_TEAMS + communication_team]; + event->last_time_spoken = time; + if (game_connection() || + !ai_debug.communication_timeout_disabled) + { + if (usage->repeat_delay > 0.0f) + { + event->disable_until_time = (long)( + usage->repeat_delay * TICKS_PER_SECOND + + notification_time); + } + } + } + + if (reply_table_index != NONE) + { + struct dialogue_event_status *event; + struct reply_usage const *usage; + + match_assert( + "c:\\halo\\SOURCE\\ai\\ai_communication.c", + 0xCAF, + (reply_table_index >= 0) && (reply_table_index < global_reply_event_count)); + usage = &global_reply_table[reply_table_index]; + event = &global_reply_events[ + reply_table_index * NUMBER_OF_AI_COMMUNICATION_TEAMS + communication_team]; + event->last_time_spoken = time; + if (game_connection() || + !ai_debug.communication_timeout_disabled) + { + if (usage->repeat_delay > 0.0f) + { + event->disable_until_time = (long)( + usage->repeat_delay * TICKS_PER_SECOND + + notification_time); + } + } + } + } + } + + return; +} + +void ai_communication_started( + long unit_index, + short priority, + short vocalization_type, + struct ai_information_packet *information) +{ + switch (priority) + { + case _unit_speech_none: + case _unit_speech_idle: + case _unit_speech_pain: + case _unit_speech_involuntary: + case _unit_speech_death: + break; + + default: + if (ai_debug.print_vocalizations) + { + char string[1024]; + char name[256]; + struct unit_datum *unit = unit_get(unit_index); + + if (unit->unit.actor_index != NONE) + { + struct actor_datum *actor = actor_get(unit->unit.actor_index); + + if (actor->meta.encounter_index == NONE) + { + csstrcpy(name, ""); + } + else + { + struct encounter_definition *encounter = TAG_BLOCK_GET_ELEMENT( + &global_scenario_get()->ai_encounters, + DATUM_INDEX_TO_ABSOLUTE_INDEX(actor->meta.encounter_index), + struct encounter_definition); + + sprintf( + name, + "%s/%s", + encounter->name, + TAG_BLOCK_GET_ELEMENT( + &encounter->squads, + actor->meta.squad_index, + struct squad_definition)->name); + } + + sprintf( + string, + "%s/%s: ", + name, + actor_type_get_name(actor->meta.type)); + } + else if (unit->object.name_index != NONE) + { + sprintf( + string, + "%s: ", + TAG_BLOCK_GET_ELEMENT( + &global_scenario_get()->object_names, + unit->object.name_index, + struct scenario_object_name)->name); + } + else if (unit->unit.player_index != NONE) + { + sprintf( + string, + "player %d: ", + DATUM_INDEX_TO_ABSOLUTE_INDEX(unit->unit.player_index)); + } + else + { + sprintf( + string, + "unit %04X: ", + DATUM_INDEX_TO_ABSOLUTE_INDEX(unit_index)); + } + + sprintf( + temporary, + "%s %s", + unit_get_speech_priority_name(priority), + vocalization_type == NONE ? + "non-voc" : + dialogue_get_vocalization_name(vocalization_type, FALSE)); + csstrcat(string, temporary); + + if (information && + information->dialogue_type_index != NONE) + { + sprintf( + temporary, + " [%d/%s]", + information->dialogue_type_index, + ai_communication_get_type_name( + global_dialogue_table[information->dialogue_type_index].communication_type)); + csstrcat(string, temporary); + } + + console_printf(FALSE, string); + } + + if (ai_debug.print_speech) + { + struct unit_datum *unit = unit_get(unit_index); + + if (unit->unit.speech.current.priority > _unit_speech_none) + { + char actor_string[512]; + char speech_string[512]; + + error( + 2, + "%s: %s", + ai_debug_describe_actor( + unit->unit.actor_index, + unit_index, + FALSE, + actor_string, + sizeof(actor_string)), + unit_describe_speech( + unit_index, + TRUE, + sizeof(speech_string), + speech_string)); + } + } + + if (!information->updated_dialogue_timers) + { + ai_communication_update_speech_timers( + unit_index, + priority, + vocalization_type, + information->dialogue_type_index, + NONE); + } + break; + } + + return; +} + void ai_communication_notify( long unit_index, short priority, diff --git a/source/ai/ai_communication.h b/source/ai/ai_communication.h index b92e5999..9cf574e5 100644 --- a/source/ai/ai_communication.h +++ b/source/ai/ai_communication.h @@ -119,6 +119,11 @@ void ai_communication_dispose_from_old_map( void); void ai_communication_packet_new( struct ai_information_packet *information); +void ai_communication_started( + long unit_index, + short priority, + short vocalization_type, + struct ai_information_packet *information); void actor_communication_update( long actor_index); void actor_handle_communication(