Repository navigation
reimplement: AICState methods that are 100% - #261
Merged
Merged
Conversation
gynt
force-pushed
the
reimpl/AICState-100
branch
from
October 14, 2026 08:15
959b9a9 to
a8f7dae
Compare
gynt
commented
Oct 17, 2026
gynt
commented
Oct 17, 2026
gynt
commented
Oct 17, 2026
gynt
commented
Oct 17, 2026
gynt
commented
Oct 17, 2026
gynt
commented
Oct 17, 2026
TheRedDaemon
requested changes
Oct 17, 2026
TheRedDaemon
left a comment
Contributor
There was a problem hiding this comment.
First batch. Will continue review later.
TheRedDaemon
requested changes
Oct 17, 2026
TheRedDaemon
requested changes
Oct 18, 2026
…ents Two generated-header changes requested in the PR review, kept separate from the .cpp work as AGENTS.md asks: - AICState::sendUnitsToAttackBreachedCastle returns BOOLEnum instead of undefined4; the body returns TRUE/FALSE accordingly. - The 0x00EB0B18 global is named TIME_LastThanksBikPlaybackTime instead of DWORD_00eb0b18, which is what playThanksBikFromPlayerToPlayer stores into. quick_diff holds at its baseline for both functions (1.0000 and 0.9762). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Include hygiene (the reviewer's "wrong import", checked across all 137 files): every AICState .cpp now opens with the relative "../AICState.func.hpp" instead of the absolute "OpenSHC/AI/AICState.hpp", matching the files that already did. 63 files changed; the func header is a superset, so no other include moved. Magic values and style: - assignRequiredIdleEngineersToNewTribe: drop the stale (int) cast on maxUnitCount (already int in the header), and test `dying` for truthiness. - checksAndGenerateAITribesForPlayerIfNotExisting: `if (checkOnly)` instead of `if (checkOnly != FALSE)`. - playGoodsSentBikFromPlayerToPlayer: `if (!getAliveLordForPlayer(...))`. - getUnitTypeIndexForUnitID: the Caliph check now reads `aiType - 1 != AITA_CALIPH`, and the loop bound 0x14 is written as 20. - wipeAICMemory: sizeof(this->aics[i]) instead of the literal 676. - shareGoldAmongTeamMembers: drop the obsolete FIXME about the int return (the header already declares void), and use s__d__s___005a7674 from string-literals.hpp for the "%d %s - " format. Measured, not assumed: - aiRequiresExtraOxtethers: the (short) on quarryStockpileID is load-bearing (removing it drops quick_diff 1.0000 -> 0.9901), so it stays, with a comment. - someChatMessageSelection: rolling the unrolled player scan into a plain 1..8 loop drops quick_diff 0.9198 -> 0.6745, so the unroll is in the original source. Kept, with the measurement recorded in the comment. - computeNervousness: isNotNervousByEnemyTroopValue is an int field storing 0/1, which per AGENTS.md is an int and not a bool, so it is left as is. status/addresses: reverted the 56 changed lines that belong to functions outside this PR; the remaining 121 all map to a `// FUNCTION:` address in src/OpenSHC/AI/AICState. No regressions: quick_diff is unchanged from baseline on every touched function. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ested loop The 4 hand-written copies of the candidate test are replaced by an inner `for (offset = 0; offset < 4; offset++)` inside the existing step-by-4 outer loop. The generated instruction stream is identical to the unrolled source -- 132 instructions, same opcodes and operands, differing only in MSVC's internal label numbering -- so quick_diff stays at 0.9198 (118 vs the original's 119). This corrects the reasoning in the previous commit. The compiler does unroll here, contrary to the "MSVC1400 at /O2 never unrolls a loop" note in AGENTS.md: writing all 8 iterations out by hand compiles to byte-identical code to a plain 1..8 loop (136 instructions, 0.6745 either way). What the original's shape actually pins down is the unroll *factor* of 4, not the absence of a loop in its source, so the outer loop must step by 4 while the inner 4 can be left to the compiler. Also measured and rejected: a file-local inlined helper called 4 times in the outer loop (identical code, but emits a dead out-of-line copy of the helper), the same helper called 8 times (0.4000), unroll by 2 (0.7339), and a half/offset index split (0.8220). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ction Tried `for (int i = 1; i < 9; i++)` and three other spellings of the same loop (reusing the function's declared `i`, a fresh name declared in the `for`, and the pre-declared `playerID`). All four produce byte-identical assembly at 0.6745 and 154 instructions, against 132 for the nested form and 119 in the original, so the loop variable's name, declaration site and reuse are all irrelevant here. The reason is visible in the diff: fully unrolling 8 iterations with a known trip count lets MSVC constant-fold every index, so each body gets its own absolute addresses and an immediate player number. The original instead keeps the index in a register and computes the stored value with `lea`, which means its source had an induction variable that survived the unroll. Stepping the outer loop by 4 is what reproduces that. Comment updated with the mechanism; no code change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verified against the original binary with Ghidra rather than inferred. The field at PlayerData + 0x3820 has 7 writes and 5 reads in the whole image, and every write stores 0 or 1: 004d3802 computeNervousness xor eax, eax -> 0 004d383f computeNervousness ebx -> 1 004ad1c5 ProcessAllyAcceptsRequest imm -> 0 004ad295 ProcessAllyDeniesRequest2 imm -> 0 004ad315 ProcessAllyRequestAttackDefense eax after xor -> 0 004ad38e ProcessAllyRequestAttackDefense imm -> 1 004ad433 ProcessAllyRequestAttackDefense imm -> 0 All five reads are == 0 / != 0 truthiness tests, so the field is boolean. BOOLEnum is already the convention for such fields in PlayerData.hpp. This also corrects the note in the earlier commit, which argued the field had to stay an int because the original stores it with `mov dword ptr [..], 0/1`. That AGENTS.md rule distinguishes int from C++ `bool` (a one-byte store); BOOLEnum is a typedef of BOOL, hence int, so the dword store is exactly what a BOOLEnum field looks like and was never evidence against it. Being the same underlying type, the change is purely semantic: quick_diff stays at 1.0000 for computeNervousness, determineAIPlayerHelp and determineAIPlayerAttackRequestResponse, the field's only users. The two readers also lose their `!= 0` per the review's preferred if() style. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
165 locals across the AICState sources gain `const` after the type, per the project's preferred `int const stackvariable` spelling. Applied only where the local is initialised once at its declaration and never afterwards assigned, incremented, decremented or address-taken; arrays, multi-declarators and uninitialised declarations are left alone. Wrong calls would not compile, and all 137 files were compiled. This also restores the `const` on getAIBuildInterval's `aiType`, which was not dropped deliberately: it was lost in a8f7dae, where the AICState sources were re-added from the field-refactor script's output. It was the only one of the four files predating that commit to lose it. const is NOT codegen-neutral here, so it is applied by measurement rather than by rule. Seven locals of the form `int tribeIndex = <literal>` had to keep their non-const spelling: with const, MSVC1400 folds the literal into the array index at its use site and drops 4 instructions that the original still has, costing 0.18 to 0.50 of match in aiCommandTribe11StandGroundToGate, aiCommandTribe13DefensiveWave, sendAITribe_0xf_toAttackPatrol, sendTribeToAttack, setTribe0x11ToAggressiveAndAttackIfConditionMet, setTribe0xCtoAggressiveAndAttack and setTribe0xbToAggressiveAndAttack. const on a local initialised from a memory read or a call is neutral, which is every other case here. Verified by a full before/after quick_diff sweep of all 137 functions: every one is identical to its baseline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restore status/addresses-SHC-3BB0A8C1.txt from origin/main and re-apply only the entries for addresses carrying a FUNCTION marker in the .cpp files added or changed on this branch (120 entries). Drops the unrelated 0x004CC000 percentage change for buyGoods.cpp, which this branch does not touch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gynt
force-pushed
the
reimpl/AICState-100
branch
from
October 18, 2026 21:23
08895be to
21ede33
Compare
TheRedDaemon
approved these changes
Oct 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.