Skip to content

Reimpl/aiv state - #233

Draft
gynt wants to merge 85 commits into
mainfrom
reimpl/AIVState
Draft

gynt wants to merge 85 commits into
mainfrom
reimpl/AIVState

Conversation

@gynt

@gynt gynt commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Reimplements the 37 functions of OpenSHC::AI::AIVState.

Measured with reccmp-reccmp --target STRONGHOLDCRUSADER --resolve-wrapped-calls, with the global data resolvers enabled locally (tools/reimplementation-control/Enable-Reimplemented-Data.ps1, not committed). 26 of the 37 functions are at 100%.

cmake/compiler-flags-gl.txt gains executeDefaultCastleAIV.cpp and findAppropriateGridLocationForBuilding.cpp: the original keeps this in ecx across that call. For the same reason the resolver of findAppropriateGridLocationForBuilding is set to implemented, and clearTheHeatmaps is set to implemented for the tail call in wipeAIVsAndHeatMaps.

Functions below 100%

Function Address Match
computeLargestSeparateArea 0x4edd00 98.78%
aiDecideOnNewBuildings 0x4f15c0 98.51%
aiPlaceFlag 0x4ed240 98.25%
rotateAIV 0x4ed0b0 97.20%
aiPlaceWoodcuttershut 0x4efab0 91.38%
updateBuildingsStateAndUpdateAIBuildingDecisions 0x4f1860 90.83%
recomputeHeatmaps 0x4f0e80 88.89%
findAppropriateGridLocationForBuilding 0x4eee10 88.21%
hostChecksLobbyAIVAvailability 0x4ecbc0 68.63%
applyAIV 0x4ef0d0 67.19%
executeDefaultCastleAIV 0x4f0060 53.56%

In the diffs, - is the original and + is this branch; @@ marks skipped identical instructions.

computeLargestSeparateArea - 98.78%

Code is identical. The only difference is the name reccmp gives the stack-probe helper: _chkstk in our build, an unnamed offset in the original. Not reachable from source.

reccmp diff (2 differing lines)
 mov eax, 0x9c40
-call <OFFSET1>
+call _chkstk
 lea eax, [esp]
 push eax

aiDecideOnNewBuildings - 98.51%

The original stores the initial values of isPausing and stepIncrement before the branch of the aivID <= 0 check; ours emits them after it, next to the resourceRebuildDelay load. Declaring the two locals before the check, or nesting the body in if (aivID > 0), moves the stores but disturbs the rest of the function (70-82%).

reccmp diff (6 differing lines)
 test ebx, ebx
 mov ebp, ecx
+jle 0x26e
+mov eax, dword ptr [esi + GameStateStructures+210976]
+test eax, eax
 mov dword ptr [esp + 0x10], 0
 mov dword ptr [esp + 0x18], 1
-jle 0x25e
-mov eax, dword ptr [esi + GameStateStructures+210976]
-test eax, eax
 je 0x2a
 add eax, 1

aiPlaceFlag - 98.25%

Same instruction and same address. The access is aics[aiType - 1].flagType, and its constant base lies just before the start of the AICState global, so reccmp names it after the nearest preceding symbol. That symbol differs between the original (EntityState+705348) and our DLL (cached+4). Not reachable from source.

reccmp diff (2 differing lines)
 imul ecx, ecx, 0x2a4
 cmp dword ptr [eax + GameStateStructures+212512], 0
-mov ebx, dword ptr [ecx + EntityState+705348]
+mov ebx, dword ptr [ecx + cached+24]
 jle 0x8d
 push ebp

rotateAIV - 97.20%

Only the order in which four array base addresses are materialised (lea) before the copy loops differs. Swapping the two statements in the loop body (62.6%), reading the sources into locals first (27.8%) and a flat index for steps (tie) were measured.

reccmp diff (6 differing lines)
 jne 0x6d
 lea edx, [ebp + 0x4dbe6]
+lea eax, [ebp + 0x3f0c0]
+lea ecx, [ebp + 0x43ee0]
 mov dword ptr [esp + 0x1c], edx
 lea ebx, [ebp + 0x52acc]
-lea eax, [ebp + 0x3f0c0]
-lea ecx, [ebp + 0x43ee0]
 mov dword ptr [esp + 0x10], 0x64
 mov esi, ebx
@@
 jne 0x49
 lea eax, [ebp + 0x3f0c0]
+lea ecx, [ebp + 0x43ee0]
 lea edx, [ebp + 0x5293e]
-lea ecx, [ebp + 0x43ee0]
 lea esi, [ebp + 0x5c57c]
 mov dword ptr [esp + 0x1c], 0x64

aiPlaceWoodcuttershut - 91.38%

After the call to findAppropriateWoodCutterGridLocation the original compares buildingApproriateGridXPosition with -1 in memory and then loads it; ours loads it and compares the register. Casts, a named local, nested positive conditions and /GL on caller plus both callees all compile to identical code. The trailing + lines are a compare-window artefact.

reccmp diff (10 differing lines)
 call <OFFSET2>
 test eax, eax
-je 0xb7
+je 0xb3
 cmp dword ptr [BuildingsState+12], 0x14
-jl 0xaa
+jl 0xa6
 push edi
 mov ecx, esi
 call AIVState::findAppropriateWoodCutterGridLocation
-cmp dword ptr [esi + 0xb4550], -1
-je 0x95
 mov eax, dword ptr [esi + 0xb4550]
+cmp eax, -1
+je 0x8f
 lea eax, [eax + eax*4]
 shl eax, 4
@@
 call <OFFSET8>
 pop edi
+pop esi
+ret 4

updateBuildingsStateAndUpdateAIBuildingDecisions - 90.83%

In the start-of-day loop the original loads currentPlayerFullIDArray[player] into eax and, on the path where it is -1 and the AI slot is 0, executes a redundant cmp eax, eax; je. That looks like a third == -1 test with the constant replaced by the register known to hold it. Six ways of writing a redundant third condition (re-read, local, named constant, negated form) are all folded away by our build. The trailing + lines follow from the resulting size difference.

reccmp diff (22 differing lines)
 push ebx
 mov ebx, ecx
-je 0x1e5
+je 0x1d5
 cmp dword ptr [GameStateStructures+334392], 0
 push esi
 push edi
-je 0xbe
+je 0xb8
 mov edi, 1
 add dword ptr [ebx + 0x5ca1c], edi
@@
 mov esi, GameStateStructures+229588
 lea esp, [esp]
-mov eax, dword ptr [edi*4 + GameSynchronyState+1704]
-cmp eax, -1
-jne 0xe
+cmp dword ptr [edi*4 + GameSynchronyState+1704], -1
+jne 0xa
 cmp dword ptr [edi*4 + GameSynchronyState+1812], 0
-jne 0x4
-cmp eax, eax
 je 0x5e
 push edi
@@
 add edi, 1
 cmp esi, GameStateStructures+348276
-jl -0x8d
+jl -0x87
 cmp dword ptr [GameStateStructures+334348], 0
 je 0xa
@@
 call <OFFSET18>
 cmp dword ptr [GameStateStructures+334356], 0
-je 0x76
+je 0x6f
 push 2
 mov ecx, BuildingsState
@@
 mov esi, 1
 mov edi, GameStateStructures+223556
-lea esp, [esp]
 cmp dword ptr [esi*4 + GameSynchronyState+1704], -1
 jne 0x35
@@
 call <OFFSET28>
 cmp dword ptr [GameStateStructures+595408], 0xa
+jne 0x9
+push 0
+mov ecx, ebx
+call <AIVState::recomputeHeatmaps>
+pop ebx
+ret

recomputeHeatmaps - 88.89%

Three things. (1) Constant pooling: the original keeps 0x100000 in ebx, ours keeps 0x90 in bl. (2) In both tile loops the original computes y, then x, then divides; ours interleaves the load and the division. Naming y/x locals in that order compiles to identical code. (3) test eax, 0x10001580 is shown as <OFFSET> on our side because the flag mask looks like an address inside the DLL image - a reccmp artefact.

reccmp diff (79 differing lines)
 je 0x239
 xor edi, edi
-mov ebx, 0x100000
-mov edi, edi
-movsx ecx, word ptr [edi*2 + ViewportRenderState+160192]
-lea eax, [ecx + ecx*2]
-mov esi, edi
-sub esi, dword ptr [eax*4 + ViewportRenderState+1607464]
+mov bl, 0x90
+jmp 0x3
+lea ecx, [ecx]
+movsx esi, word ptr [edi*2 + ViewportRenderState+160192]
 mov eax, 0x66666667
-imul ecx
+imul esi
 sar edx, 1
 mov ecx, edx
 shr ecx, 0x1f
 add ecx, edx
+lea eax, [esi + esi*2]
+mov edx, edi
+sub edx, dword ptr [eax*4 + ViewportRenderState+1607464]
 mov eax, 0x66666667
-imul esi
+imul edx
 sar edx, 1
 mov eax, edx
@@
 lea edx, [edx + ebp + 0x5cd48]
 test dword ptr [edi*4 + TileMapState+1462624], 0x300031
-jne 0x17d
+jne 0x180
 movzx edx, byte ptr [edi + TileMapState+2831424]
 lea eax, [eax + eax*4]
@@
 movsx edx, word ptr [edi*2 + TileMapState+1947024]
 test edx, edx
-je 0x104
+je 0x107
 mov eax, edx
 imul eax, eax, 0x9c
@@
 je 0xa
 cmp cx, 4
-jne 0xd9
+jne 0xdc
 cmp dword ptr [eax + LandscapeState+156], 4
-jge 0xcc
+jge 0xcf
 cmp word ptr [eax + LandscapeState+96], 2
-jne 0xbe
+jne 0xc1
 mov eax, dword ptr [eax + LandscapeState+104]
 push eax
@@
 call <OFFSET15>
 test eax, eax
-je 0xa4
+je 0xa7
 add byte ptr [esi + 0x5cd4b], 3
 add dword ptr [ebp + 0x5ca14], 3
-jmp 0x91
+jmp 0x94
 test eax, 0x20000
 je 0x9
@@
 jns 0x7
 add byte ptr [esi + 0x5cd5a], 1
-test dword ptr [edi*4 + TileMapState+1462624], ebx
-jne 0x20
+test dword ptr [edi*4 + TileMapState+1462624], 0x100000
+jne 0x1f
 test byte ptr [edi + TileMapState+1785824], 0x91
 je 0x7
 add byte ptr [esi + 0x5cd55], 1
-test byte ptr [edi + TileMapState+1785824], 0x90
+test byte ptr [edi + TileMapState+1785824], bl
 je 0x7
 add byte ptr [esi + 0x5cd56], 1
 add edi, 1
 cmp edi, 0x13a10
-jl -0x224
+jl -0x227
 pop edi
 pop esi
@@
 pop ecx
 ret 4
-lea esp, [esp]
+nop
 mov eax, dword ptr [esi*4 + TileMapState+1462624]
-test eax, 0x10001580
+test eax, <OFFSET17>
 je 0x219
 test al, al
 jns 0x51
-movsx edi, word ptr [esi*2 + ViewportRenderState+160192]
-lea edx, [edi + edi*2]
-mov ecx, esi
-sub ecx, dword ptr [edx*4 + ViewportRenderState+1607464]
+movsx ecx, word ptr [esi*2 + ViewportRenderState+160192]
+lea eax, [ecx + ecx*2]
+mov edx, esi
+sub edx, dword ptr [eax*4 + ViewportRenderState+1607464]
+mov eax, 0x66666667
+imul edx
+sar edx, 1
+mov eax, edx
+shr eax, 0x1f
+add eax, edx
+lea edi, [eax + eax*4]
 mov eax, 0x66666667
 imul ecx
 sar edx, 1
-mov ecx, edx
-shr ecx, 0x1f
-add ecx, edx
-mov eax, 0x66666667
-imul edi
-sar edx, 1
-mov edi, edx
-shr edi, 0x1f
-lea eax, [ecx + ecx*4]
-shl eax, 4
+shl edi, 4
+mov eax, edx
+shr eax, 0x1f
 add edi, edx
 add eax, edi
-lea eax, [eax + eax*2]
-shl eax, 4
-add byte ptr [eax + ebp + 0x5cd5a], 1
-lea eax, [eax + ebp + 0x5cd5a]
+lea ecx, [eax + eax*2]
+shl ecx, 4
+add byte ptr [ecx + ebp + 0x5cd5a], 1
+lea eax, [ecx + ebp + 0x5cd5a]
 mov eax, dword ptr [esi*4 + TileMapState+1462624]
 test eax, 0x1000

findAppropriateGridLocationForBuilding - 88.21%

Loop rotation. The original reloads algAIndex at the loop head, jumps back with jne and rematerialises the constant 1 in edi every iteration; ours reuses the register from the bottom test and sets edi in a separate block before jumping back. while, do-while with the result inside or after the loop, and a for(;;) with the test at the top score 83.2-88.2%; split entry conditions and hoisted declarations tie.

reccmp diff (31 differing lines)
 mov dword ptr [ecx + 0xb4550], esi
 mov dword ptr [ecx + 0xb4554], esi
-ja 0x220
+ja 0x1f8
 cmp edx, 0x4f
-ja 0x217
+ja 0x1ef
 lea eax, [eax + eax*4]
 shl eax, 4
@@
 add eax, ecx
 cmp byte ptr [eax + 0x5cd54], 0
-jne 0x1fa
+jne 0x1d2
 add dword ptr [ecx + 0x5ca20], edi
 mov edx, dword ptr [ecx + 0x5ca20]
@@
 mov eax, dword ptr [ecx + 0xa7d48]
 cmp eax, dword ptr [ecx + 0xa7d4c]
-je 0x1ca
+je 0x1a2
 push ebx
 push ebp
@@
 lea esp, [esp]
 lea ecx, [ecx]
-mov eax, dword ptr [ecx + 0xa7d48]
 mov edx, dword ptr [ecx + eax*4 + 0xa7d50]
 mov ebp, dword ptr [ecx + eax*4 + 0xae150]
@@
 mov dword ptr [ecx + 0xa7d44], esi
 mov ebx, TerrainDefinedData+248
-jmp 0xf
-lea esp, [esp]
+jmp 0xc
+mov edx, dword ptr [esp + 0x20]
+mov ebp, dword ptr [esp + 0x10]
 mov eax, dword ptr [esp + 0x14]
-mov ebp, dword ptr [esp + 0x10]
 mov esi, dword ptr [ebx - 4]
 mov edi, dword ptr [ebx]
@@
 je 0x12
 cmp esi, 0x4f
-ja 0xc5
+ja 0xc1
 cmp edi, 0x4f
-ja 0xbc
+ja 0xb8
 lea eax, [esi + esi*4]
 shl eax, 4
@@
 jl 0xa
 mov dword ptr [ecx + 0xa7d4c], 0
-mov edx, dword ptr [esp + 0x20]
 add ebx, 8
 cmp ebx, TerrainDefinedData+312
 jl -0xf7
 cmp dword ptr [esp + 0x1c], 9
-je 0x3d
-mov edi, 1
-add dword ptr [ecx + 0xa7d48], edi
+je 0x35
+add dword ptr [ecx + 0xa7d48], 1
 cmp dword ptr [ecx + 0xa7d48], 0x1900
 jl 0xa
@@
 mov eax, dword ptr [ecx + 0xa7d48]
 cmp eax, dword ptr [ecx + 0xa7d4c]
-jne -0x191
+je 0x16
+mov edi, 1
+jmp -0x186
+mov dword ptr [ecx + 0xb4554], edi
+mov dword ptr [ecx + 0xb4550], esi
 pop ebp
 pop ebx

hostChecksLobbyAIVAvailability - 68.63%

The per-player availability test is instruction-identical. What differs is the outer loop: the original runs three induction variables (AI index in ebx, a pointer into the received table in esi, a pointer into aivFileAvailabilityPerAIArray in edi) and ends the loop on the pointer; ours derives the received-table pointer from the other one (sub ebx, ecx / lea edx, [ebx + esi]) and ends on the index. That permutes the register roles from the first push on. Fourteen source variants (!= bound, uint index, flat table index, hoisted or removed locals, post-increment store, if/else instead of early return) compile to identical code.

reccmp diff (85 differing lines, first 110 diff lines shown)
 mov eax, dword ptr [GameSynchronyState+1560]
+push esi
+push edi
+xor edi, edi
+cmp eax, edi
+je 0x193
+cmp eax, 0x63
+je 0x18a
+cmp dword ptr [GameSynchronyState+1936], edi
+je 0x1b1
 push ebx
-xor ebx, ebx
-cmp eax, ebx
-push esi
-je 0x1a2
-cmp eax, 0x63
-je 0x199
-cmp dword ptr [GameSynchronyState+1936], ebx
-je 0x1c1
+mov ebx, GameSynchronyState+828136
 push ebp
 mov ebp, dword ptr [GameSynchronyState+1089140]
-push edi
-mov dword ptr [GameCore+7604], ebx
-mov esi, GameSynchronyState+1086276
-lea edi, [ecx + 0x3f05c]
-nop
+mov dword ptr [GameCore+7604], edi
+lea esi, [ecx + 0x3f05c]
+sub ebx, ecx
 mov eax, 1
 mov ecx, GameSynchronyState+1086276
-mov edx, esi
-lea esp, [esp]
+lea edx, [ebx + esi]
+mov edi, edi
 cmp dword ptr [eax*4 + GameSynchronyState+1704], -1
 je 0x1c
@@
 cmp eax, ebp
 jne 0x13
-cmp dword ptr [edi], 0
+cmp dword ptr [esi], 0
 jmp 0xc
 cmp eax, ebp
 jne 0x5
-cmp dword ptr [edi], 0
+cmp dword ptr [esi], 0
 jmp 0x3
 cmp dword ptr [edx], 0
@@
 jl -0x34
 mov ecx, dword ptr [GameCore+7604]
-lea eax, [ebx + 1]
+lea eax, [edi + 1]
 mov dword ptr [ecx*4 + GameCore+7608], eax
 add dword ptr [GameCore+7604], 1
+add edi, 1
 add esi, 4
-add ebx, 1
-add edi, 4
-cmp esi, GameSynchronyState+1086340
-jl -0x6c
+cmp edi, 0x10
+jl -0x65
 xor ebx, ebx
 xor edi, edi
 mov esi, 1
+nop
 cmp dword ptr [esi*4 + GameSynchronyState+1704], -1
-jne 0x58
-cmp dword ptr [esi*4 + GameSynchronyState+1812], ebx
-je 0x4f
+jne 0x4e
+mov ecx, dword ptr [esi*4 + GameSynchronyState+1812]
+cmp ecx, ebx
+je 0x43
 mov edx, dword ptr [GameCore+7604]
-mov ecx, dword ptr [esi*4 + GameSynchronyState+1812]
 xor eax, eax
 cmp edx, ebx
-jle 0x15
-jmp 0x3
-lea ecx, [ecx]
+jle 0x10
 cmp ecx, dword ptr [eax*4 + GameCore+7608]
 je 0x2e
@@
 add esi, 1
 cmp esi, 9
-jl -0x6a
+jl -0x60
+pop ebp
 cmp edi, ebx
-pop edi
-pop ebp
+pop ebx
 je 0x14
 mov ecx, GameSynchronyState
@@
 jne 0x37
 cmp dword ptr [GameCore+7604], 8
-jle 0x97
+jle 0x96
 call <OFFSET16>
 mov edx, dword ptr [WindowAndDirectDraw+36]
@@
 mov ecx, MenuModalComposition
 call <OFFSET20>
+pop edi

applyAIV - 67.19%

Register assignment in the first grid loop. The original keeps y on the stack and spends ebx on aivID * 0x922; ours keeps y in ebx and spills the product, which changes every stack slot and operand after it. Giving the loops their own x/y, or sharing them differently, scores 40-55%; /GL scores 57%.

reccmp diff (378 differing lines, first 150 diff lines shown)
 add eax, AIVDefinedData+564
 push eax
-xor esi, esi
+xor ebx, ebx
 push AIVDefinedData+4
 mov ecx, FilePackager
-mov dword ptr [esp + 0x34], esi
+mov dword ptr [esp + 0x2c], ebx
 call <OFFSET4>
 mov ecx, dword ptr [edi + 0xc]
@@
 mov ecx, ebp
 call AIVState::rotateAIV
-xor ecx, ecx
-mov dword ptr [esp + 0x1c], esi
-mov dword ptr [esp + 0x10], ecx
-jmp 0x9
+mov dword ptr [esp + 0x10], ebx
+mov ecx, ebx
+jmp 0xd
 lea esp, [esp]
-mov edi, edi
+lea ebx, [ebx]
 lea edx, [ebp + ecx*4 + 0x43ee0]
 lea eax, [ebp + ecx*2 + 0x3f0c0]
@@
 lea ebx, [ebx]
 cmp byte ptr [ecx + ebp + 0xb4558], 0
-jne 0x141
+jne 0x145
 mov edx, dword ptr [esp + 0x18]
 movsx eax, word ptr [edx]
@@
 jne 0xd
 mov byte ptr [ecx + ebp + 0xb4558], 1
-jmp 0x129
+jmp 0x12d
 cmp eax, 2
 jne 0xd
 mov byte ptr [ecx + ebp + 0xb4558], 1
-jmp 0x117
+jmp 0x11b
 cmp eax, 0x26
-jne 0x34
-cmp dword ptr [esp + 0x2c], 0
-jne 0x20
+jne 0x32
+cmp dword ptr [esp + 0x24], 0
+jne 0x1e
 mov eax, dword ptr [edi + 0x28]
 add eax, esi
 mov dword ptr [ebp + 0x3f0b0], eax
 mov edx, dword ptr [edi + 0x2c]
-add edx, dword ptr [esp + 0x1c]
-mov dword ptr [esp + 0x2c], 1
+add edx, ebx
 mov dword ptr [ebp + 0x3f0b4], edx
+mov dword ptr [esp + 0x24], 1
 mov byte ptr [ecx + ebp + 0xb4558], 1
-jmp 0xde
+jmp 0xe4
 push eax
 mov ecx, ebp
@@
 mov ecx, dword ptr [esp + 0x20]
 mov edx, dword ptr [ecx]
+mov dword ptr [esp + 0x14], eax
+mov dword ptr [esp + 0x1c], edx
+je 0xc5
+cmp eax, 0x2e
+je 0xbc
+cmp eax, 0x1a
+je 0xb3
+cmp eax, 0x23
+je 0xaa
+cmp eax, 0x6a
+je 0xa1
+cmp eax, 0x63
+je 0x98
+mov eax, dword ptr [esp + 0x10]
+mov byte ptr [eax + ebp + 0xb4558], 1
+mov eax, dword ptr [esp + 0x38]
+imul eax, eax, 0x922
+mov ecx, edx
 mov dword ptr [esp + 0x28], eax
-mov dword ptr [esp + 0x14], edx
-je 0xbf
-cmp eax, 0x2e
-je 0xb6
-cmp eax, 0x1a
-je 0xad
-cmp eax, 0x23
-je 0xa4
-cmp eax, 0x6a
-je 0x9b
-cmp eax, 0x63
-je 0x92
-mov ebx, dword ptr [esp + 0x38]
-mov eax, dword ptr [esp + 0x10]
-imul ebx, ebx, 0x922
-mov byte ptr [eax + ebp + 0xb4558], 1
-mov ecx, edx
-lea eax, [ebx + ecx + 5]
+lea eax, [eax + ecx + 5]
 lea edx, [eax + eax*2]
 cmp word ptr [ebp + edx*4], 0
 lea eax, [ebp + edx*4]
-mov dword ptr [esp + 0x30], eax
-jg 0x63
-mov ecx, dword ptr [esp + 0x1c]
+mov dword ptr [esp + 0x2c], eax
+jg 0x65
 mov eax, dword ptr [edi + 0x2c]
-add eax, ecx
-mov ecx, dword ptr [edi + 0x28]
-add ecx, esi
+add eax, ebx
 push eax
-push ecx
+mov dword ptr [esp + 0x34], eax
+mov eax, dword ptr [edi + 0x28]
+add eax, esi
+push eax
 mov ecx, ViewportRenderState
-mov dword ptr [esp + 0x2c], eax
 call <OFFSET8>
 test eax, eax
-je 0x41
-mov eax, dword ptr [esp + 0x24]
-lea edx, [eax + eax*2]
-mov eax, dword ptr [edx*4 + ViewportRenderState+1607464]
-mov edx, dword ptr [esp + 0x14]
-add eax, dword ptr [edi + 0x28]
-add ebx, edx
-lea ecx, [ebx + ebx*2]
-mov bx, word ptr [esp + 0x28]
-lea ecx, [ebp + ecx*4]
-mov word ptr [ecx + 0x3a], bx
-mov ebx, dword ptr [esp + 0x30]
-add eax, esi
-mov byte ptr [ecx + 0x38], 1
-mov word ptr [ebx], 1
-mov dword ptr [ecx + 0x40], eax
+je 0x47
+mov ecx, dword ptr [esp + 0x28]
+mov edx, dword ptr [esp + 0x1c]
+lea eax, [ecx + edx]
+mov cx, word ptr [esp + 0x14]
+lea eax, [eax + eax*2]
+mov word ptr [ebp + eax*4 + 0x3a], cx

executeDefaultCastleAIV - 53.56%

Register and stack-slot assignment across the whole body. The original keeps the stockpile counter in ebx with a home slot at [esp+0x58], the constant 1 in ebp and the playerID offset only on the stack; ours keeps the stockpile counter in ebp without a home slot, 1 in ecx and the player offset in ebx. The sequence of compares, calls and branches otherwise equals the original, except for the woodsman check (jg + jmp in the original, one jle in ours). Declaration order and placement of the leading locals move the score by less than half a point.

reccmp diff (888 differing lines, first 170 diff lines shown)
 sub esp, 0x74
-mov eax, dword ptr [esp + 0x78]
-imul eax, eax, 0x39f4
 push ebx
+mov ebx, dword ptr [esp + 0x7c]
+imul ebx, ebx, 0x39f4
+mov eax, 1
 push ebp
-mov ebp, 1
+mov dword ptr [esp + 0x60], eax
+mov dword ptr [esp + 0x64], eax
+mov eax, dword ptr [ebx + GameStateStructures+208960]
 push esi
-mov esi, dword ptr [eax + GameStateStructures+208956]
-mov dword ptr [esp + 0x60], eax
-mov eax, dword ptr [eax + GameStateStructures+208960]
+mov esi, dword ptr [ebx + GameStateStructures+208956]
 push edi
 xor edi, edi
-sub eax, ebp
-xor ebx, ebx
-cmp eax, ebp
-mov dword ptr [esp + 0x6c], ecx
-mov dword ptr [esp + 0x80], esi
-mov dword ptr [esp + 0x58], ebx
+sub eax, 1
+mov edx, ecx
+xor ebp, ebp
+cmp eax, 1
+mov dword ptr [esp + 0x64], edx
+mov dword ptr [esp + 0x80], ebx
+mov dword ptr [esp + 0x7c], esi
 mov dword ptr [esp + 0x10], edi
-mov dword ptr [esp + 0x70], ebp
-mov dword ptr [esp + 0x74], ebp
 mov dword ptr [esp + 0x1c], edi
 mov dword ptr [esp + 0x14], edi
@@
 mov dword ptr [esp + 0x4c], edi
 mov dword ptr [esp + 0x50], edi
-lea edx, [ebp + 7]
-jne 0x46
-mov dword ptr [esp + 0x58], ebx
+jne 0x42
 mov dword ptr [esp + 0x10], 3
-mov dword ptr [esp + 0x1c], ebp
-mov dword ptr [esp + 0x14], ebp
+mov dword ptr [esp + 0x1c], eax
+mov dword ptr [esp + 0x14], eax
 mov dword ptr [esp + 0x18], edi
 mov dword ptr [esp + 0x54], edi
@@
 mov dword ptr [esp + 0x24], edi
 mov dword ptr [esp + 0x28], edi
-mov dword ptr [esp + 0x2c], ebp
-mov dword ptr [esp + 0x30], ebp
+mov dword ptr [esp + 0x2c], eax
+mov dword ptr [esp + 0x30], eax
 mov dword ptr [esp + 0x34], edi
 mov dword ptr [esp + 0x38], edi
 mov dword ptr [esp + 0x48], edi
-jmp 0x4b
-cmp eax, 2
-jne 0x70
-mov ebx, eax
-mov dword ptr [esp + 0x58], ebx
+jmp 0x50
+mov ecx, 2
+cmp eax, ecx
+jne 0x71
+mov eax, 1
+mov ebp, ecx
 mov dword ptr [esp + 0x10], 4
-mov dword ptr [esp + 0x1c], ebp
-mov dword ptr [esp + 0x14], ebp
-mov dword ptr [esp + 0x18], ebp
+mov dword ptr [esp + 0x1c], eax
+mov dword ptr [esp + 0x14], eax
+mov dword ptr [esp + 0x24], edi
+mov dword ptr [esp + 0x28], edi
+mov dword ptr [esp + 0x2c], ecx
+mov dword ptr [esp + 0x30], ecx
+mov dword ptr [esp + 0x20], edi
+mov dword ptr [esp + 0x44], edi
+mov dword ptr [esp + 0x18], eax
 mov dword ptr [esp + 0x54], edi
 mov dword ptr [esp + 0x40], edi
-mov dword ptr [esp + 0x44], edi
-mov dword ptr [esp + 0x20], edi
-mov dword ptr [esp + 0x24], edi
-mov dword ptr [esp + 0x28], edi
-mov dword ptr [esp + 0x2c], ebx
-mov dword ptr [esp + 0x30], ebx
 mov dword ptr [esp + 0x34], edi
 mov dword ptr [esp + 0x38], edi
-mov dword ptr [esp + 0x48], ebp
+mov dword ptr [esp + 0x48], eax
 mov dword ptr [esp + 0x3c], edi
 mov dword ptr [esp + 0x50], edi
 mov dword ptr [esp + 0x4c], edi
-xor edx, edx
+xor eax, eax
 mov eax, esi
 imul eax, eax, 0x6d98
-add eax, ecx
-mov dword ptr [esp + 0x7c], edx
-mov dword ptr [esp + 0x68], eax
-mov edi, 1
-jmp 0x4ee
+xor ecx, ecx
+add eax, edx
+mov dword ptr [esp + 0x78], ecx
+mov dword ptr [esp + 0x60], eax
+lea edi, [ecx + 1]
+jmp 0x4e0
 cmp eax, 3
-jne 0x47
-mov ebx, 2
-mov dword ptr [esp + 0x58], ebx
+jne 0x45
+mov eax, 1
+mov ebp, ecx
 mov dword ptr [esp + 0x10], 4
-mov dword ptr [esp + 0x1c], ebp
-mov dword ptr [esp + 0x14], ebp
+mov dword ptr [esp + 0x1c], eax
+mov dword ptr [esp + 0x14], eax
 mov dword ptr [esp + 0x18], edi
 mov dword ptr [esp + 0x54], edi
@@
 mov dword ptr [esp + 0x28], edi
 mov dword ptr [esp + 0x2c], edi
-mov dword ptr [esp + 0x30], ebx
-mov dword ptr [esp + 0x34], ebx
-mov dword ptr [esp + 0x38], ebx
-jmp -0x7a
+mov dword ptr [esp + 0x30], ecx
+mov dword ptr [esp + 0x34], ecx
+mov dword ptr [esp + 0x38], ecx
+jmp -0x78
 cmp eax, 4
-jne 0x57
-mov eax, 2
-mov ebx, 3
-mov dword ptr [esp + 0x58], ebx
+jne 0x51
+lea ebp, [eax - 1]
+mov eax, 1
 mov dword ptr [esp + 0x10], 5
-mov dword ptr [esp + 0x1c], ebp
-mov dword ptr [esp + 0x14], ebp
-mov dword ptr [esp + 0x18], ebp
+mov dword ptr [esp + 0x1c], eax
+mov dword ptr [esp + 0x14], eax
+mov dword ptr [esp + 0x18], eax
 mov dword ptr [esp + 0x54], edi
-mov dword ptr [esp + 0x40], eax
-mov dword ptr [esp + 0x44], eax
-mov dword ptr [esp + 0x20], ebx
-mov dword ptr [esp + 0x24], edi
-mov dword ptr [esp + 0x28], edi
-mov dword ptr [esp + 0x2c], eax
-mov dword ptr [esp + 0x30], eax
-mov dword ptr [esp + 0x34], ebp
-mov dword ptr [esp + 0x38], eax
-mov dword ptr [esp + 0x3c], ebp
-mov dword ptr [esp + 0x48], ebp
-jmp -0xce
-mov ebp, 5
-cmp eax, ebp

🤖 Generated with Claude Code

@gynt
gynt force-pushed the reimpl/AIVState branch 2 times, most recently from d5e0ae2 to 4edefa0 Compare September 26, 2026 23:44
gynt added 28 commits September 27, 2026 01:51
gynt and others added 16 commits October 5, 2026 19:45
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… aivID branch

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eax,eax

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ers differ

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rs differ

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… return

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ches

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d-calls

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…differ

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing with /GL

The original keeps this in ecx across the call, which needs /GL on caller and callee and a direct call (resolver enabled).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant