From 63f4187ac811d3de72efc05dc4a735afdaaeeeba Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Jun 2026 19:09:24 +0000 Subject: [PATCH] =?UTF-8?q?pokemon:=20data/stats=20Stage=203-4=20=E2=80=94?= =?UTF-8?q?=20CalcStats=20core=20+=20verified=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 3 (core formula): - src/home/pokemon.asm: GetMonHeader — BaseStats lookup via IndexToPokedex, rep movsb into wMonHeader (data tables are flat program-image labels, not EBP-relative GB memory, so we read them directly). - src/home/move_mon.asm: CalcStat / CalcStats — faithful translation of the Gen-1 stat formula (base+IV, ceil(sqrt(statExp))/4, *level/100, +Level+10 for HP / +5 otherwise, cap 999). Big-endian result in H_MULTIPLICAND+1/+2. - Wired pokemon.asm, move_mon.asm, math.asm, multiply_divide.asm into the build. Math audit (the existing math was flagged as possibly inaccurate swarm output): - multiply_divide.asm _Divide was BROKEN: used SM83 `sbc` (invalid x86 — the file never assembled) plus an unverified byte-level emulation with "let's just guess" comments. Rewrote with a single hardware div, same HRAM contract (quotient -> H_QUOTIENT big-endian, remainder -> H_REMAINDER). - src/home/math.asm Multiply wrapper didn't preserve edx; our _Multiply clobbers it via `mul ecx` while GB _Multiply preserves de (CalcStat keeps the base stat in e across the stat-exp loop). Added push/pop edx. Stage 4 (verification gate): - DEBUG_CALCSTATS harness: entry.asm hook -> RunCalcStatsTest (debug_dump.asm) computes Bulbasaur L5/L100 stats, dumps DUMP.BIN. Makefile flag added. - Running the EXE needs a DPMI host the sandbox lacks, so the assembled x86 was ALSO executed natively via an ELF32 harness. Output EXACTLY matches canonical Gen-1 values: L5 21/11/11/11/13, L100 230/133/133/125/165, and the stat-exp sqrt path L100-EVmax 293/196/196/188/228 (faithful to the b=255 cap quirk). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GcHpAsg8pwsVATD6LWPsoR --- docs/current_plan_pokemon_engine.md | 29 ++- dos_port/Makefile | 13 +- dos_port/boot/entry.asm | 7 + dos_port/src/debug/debug_dump.asm | 47 ++++ dos_port/src/engine/math/multiply_divide.asm | 205 ++++------------ dos_port/src/home/math.asm | 6 +- dos_port/src/home/move_mon.asm | 235 +++++++++++++++++++ dos_port/src/home/pokemon.asm | 51 ++++ 8 files changed, 426 insertions(+), 167 deletions(-) create mode 100644 dos_port/src/home/move_mon.asm create mode 100644 dos_port/src/home/pokemon.asm diff --git a/docs/current_plan_pokemon_engine.md b/docs/current_plan_pokemon_engine.md index 437c1e8c..a736536d 100644 --- a/docs/current_plan_pokemon_engine.md +++ b/docs/current_plan_pokemon_engine.md @@ -42,18 +42,25 @@ in UPPERCASE (`W_PARTY_COUNT`). pokemon_data.asm` exposes the globals; wired into Makefile (`POKEMON_SRCS` + `assets` target); full project builds + links. (TM/HM bitfield zeroed → Stage 6.) -- [ ] **Stage 3 — Core formula.** Write `GetMonHeader` (new - `dos_port/src/home/pokemon.asm`, from `home/pokemon.asm:403`) and `CalcStat` + - `CalcStats` (new `dos_port/src/home/move_mon.asm`, from `home/move_mon.asm:34`). - Depends only on already-ported `Multiply`/`Divide`/`AddNTimes` + HRAM scratch. - Wire `src/home/pokemon.asm`, `src/home/move_mon.asm`, `src/home/math.asm`, - `src/engine/math/multiply_divide.asm` + new asset includes into the Makefile - `ALL_SRCS`. +- [x] **Stage 3 — Core formula.** DONE. Wrote `GetMonHeader` + (`src/home/pokemon.asm`) and `CalcStat`/`CalcStats` (`src/home/move_mon.asm`). + Wired `src/home/pokemon.asm`, `src/home/move_mon.asm`, `src/home/math.asm`, + `src/engine/math/multiply_divide.asm` into the Makefile. Builds + links. + **Math audit (per the "swarm code may be inaccurate" warning):** + - `_Divide` (`multiply_divide.asm`) was broken — used the SM83 mnemonic `sbc` + (invalid x86; the file never assembled) and an unverified byte-level loop. + Rewrote it with a single hardware `div`, same HRAM memory contract. + - `Multiply` wrapper (`src/home/math.asm`) didn't preserve `edx`, but our + `_Multiply` clobbers it via `mul ecx` (GB `_Multiply` preserves `de`, which + `CalcStat` keeps the base stat in). Added `push/pop edx`. -- [ ] **Stage 4 — Verify CalcStats (milestone gate).** `DEBUG_CALCSTATS` harness - seeds a known case (Bulbasaur L5, DVs=15, 0 stat-exp; + a stat-exp case), runs - `CalcStats`, dumps the 5 stats to `DUMP.BIN`; compare against canonical Gen-1 - values on the host. No renderer in the loop. +- [x] **Stage 4 — Verify CalcStats (milestone gate).** DONE. `DEBUG_CALCSTATS` + harness (entry.asm hook → `RunCalcStatsTest` in debug_dump.asm) dumps Bulbasaur + L5/L100 stats to `DUMP.BIN`. Because running the EXE needs a DPMI host the + sandbox lacks, also **validated the actual assembled x86 natively** via an + ELF32 harness (nasm -f elf32 + ld -m elf_i386, scratch). Results EXACT vs. + canonical Gen-1: L5 `21/11/11/11/13`, L100 `230/133/133/125/165`, and the + stat-exp/√ path L100-EVmax `293/196/196/188/228` (faithful to the `b`=255 cap). - [ ] **Stage 5 — Creation / loading (unblocks Oak gift).** Write `_AddPartyMon` (`src/engine/pokemon/add_mon.asm`, from `engine/pokemon/add_mon.asm:1`; stub diff --git a/dos_port/Makefile b/dos_port/Makefile index 7f519165..7848b6aa 100644 --- a/dos_port/Makefile +++ b/dos_port/Makefile @@ -107,7 +107,11 @@ GAME_SRCS := \ # Pokémon data/stats engine (current_plan_pokemon_engine.md). Grows per stage. POKEMON_SRCS := \ - src/data/pokemon_data.asm + src/data/pokemon_data.asm \ + src/home/math.asm \ + src/engine/math/multiply_divide.asm \ + src/home/pokemon.asm \ + src/home/move_mon.asm # Debug-only sources (linked only when the corresponding flag is set). # Any debug flag implies SKIP_TITLE — debug harnesses boot straight to the overworld. @@ -136,6 +140,13 @@ NEED_DEBUG_DUMP := 1 NASMFLAGS += -D DEBUG_NPC_WALK NASMFLAGS += -D SKIP_TITLE endif +# CalcStats gate: compute known Bulbasaur stats at boot, dump DUMP.BIN, exit. +# make DEBUG_CALCSTATS=1 +ifdef DEBUG_CALCSTATS +NEED_DEBUG_DUMP := 1 +NASMFLAGS += -D DEBUG_CALCSTATS +NASMFLAGS += -D SKIP_TITLE +endif ifdef NEED_DEBUG_DUMP GAME_SRCS += src/debug/debug_dump.asm endif diff --git a/dos_port/boot/entry.asm b/dos_port/boot/entry.asm index e527d970..7c9073e7 100644 --- a/dos_port/boot/entry.asm +++ b/dos_port/boot/entry.asm @@ -30,6 +30,9 @@ extern pit_restore ; boot/timing.asm extern joypad_init ; src/input/joypad.asm extern joypad_restore ; src/input/joypad.asm extern Init ; src/init/init.asm — power-on init +%ifdef DEBUG_CALCSTATS +extern RunCalcStatsTest ; src/debug/debug_dump.asm — Pokémon CalcStats gate +%endif ; --------------------------------------------------------------------------- ; Exported symbols @@ -82,6 +85,10 @@ start: call pit_init ; reprogram PIT to ~60 Hz, install tick ISR call joypad_init ; hook IRQ 1 (keyboard) → GB joypad state +%ifdef DEBUG_CALCSTATS + call RunCalcStatsTest ; compute known stats, dump DUMP.BIN, exit (never returns) +%endif + call Init ; power-on init → title screen (runs game loop) ; Execution reaches here only if Init returns without exiting via pad_quit. call cleanup diff --git a/dos_port/src/debug/debug_dump.asm b/dos_port/src/debug/debug_dump.asm index 9d60d165..a18548c1 100644 --- a/dos_port/src/debug/debug_dump.asm +++ b/dos_port/src/debug/debug_dump.asm @@ -22,6 +22,11 @@ bits 32 %include "gb_macros.inc" extern ds_base +%ifdef DEBUG_CALCSTATS +extern GetMonHeader +extern CalcStats +global RunCalcStatsTest +%endif global DebugDumpMemory global DumpBackbuffer @@ -67,6 +72,20 @@ fnlog: db "NPCLOG.BIN", 0 ; 0x180 0xC3A0 wTileMap (final 20x18 view) — H1: tilemap ; 0x1C0 0xD358 map header vars (curmap/dims/dataptr) — header setup ; 0x200 0xD520 tileset pointers (bank/blocks/gfx) — pointer setup +%ifdef DEBUG_CALCSTATS +; CalcStats gate: one 64-byte window over the test scratch at $D1E0 covers the +; scratch mon (DVs at +$1B) and both stat results (L5 at +$20, L100 at +$30). +windows: + dd 0xD1E0 + dd 0xD1E0 + dd 0xD1E0 + dd 0xD1E0 + dd 0xD1E0 + dd 0xD1E0 + dd 0xD1E0 + dd 0xD1E0 + dd 0xD1E0 +%else windows: dd 0x4600 dd 0x4B20 @@ -77,6 +96,7 @@ windows: dd 0xC3A0 dd 0xD358 dd 0xD520 +%endif ; --------------------------------------------------------------------------- section .bss @@ -97,6 +117,33 @@ dbg_destTile: resb 1 ; tile CL at CanWalkOntoTile entry (saved before ; --------------------------------------------------------------------------- section .text +%ifdef DEBUG_CALCSTATS +; --------------------------------------------------------------------------- +; RunCalcStatsTest — compute Bulbasaur (internal $99) stats at L5 and L100 with +; DVs=15 / stat-exp=0 into the $D1E0 scratch, then dump to DUMP.BIN. Validates +; GetMonHeader + CalcStat + _Multiply/_Divide end-to-end against canonical values. +; Never returns. Expected (big-endian words, host hexdump): +; dump +$20 (L5): HP=0015 Atk=000B Def=000B Spd=000B Spc=000D (21/11/11/11/13) +; dump +$30 (L100): HP=00E6 Atk=0085 Def=0085 Spd=007D Spc=00A5 (230/133/133/125/165) +; In: EBP = GB memory base. +; --------------------------------------------------------------------------- +RunCalcStatsTest: + mov byte [ebp + wCurSpecies], 0x99 ; Bulbasaur internal index + call GetMonHeader + mov word [ebp + 0xD1FB], 0xFFFF ; scratch DVs (all 15) at monbase+MON_DVS + mov byte [ebp + wCurEnemyLevel], 5 ; --- L5 --- + xor bh, bh ; b=0: ignore stat exp + mov esi, 0xD1F0 ; stat-exp base ptr (= monbase + $10) + mov edx, 0xD200 ; result dest + call CalcStats + mov byte [ebp + wCurEnemyLevel], 100 ; --- L100 --- + xor bh, bh + mov esi, 0xD1F0 + mov edx, 0xD210 + call CalcStats + jmp DebugDumpMemory ; writes DUMP.BIN, exits +%endif + ; --------------------------------------------------------------------------- ; DebugDumpMemory — gather windows, write DUMP.BIN, exit. Never returns. ; In: EBP = GB memory base. diff --git a/dos_port/src/engine/math/multiply_divide.asm b/dos_port/src/engine/math/multiply_divide.asm index e3d4dd62..00dd3a78 100644 --- a/dos_port/src/engine/math/multiply_divide.asm +++ b/dos_port/src/engine/math/multiply_divide.asm @@ -1,4 +1,14 @@ -; dos_port/src/util/multiply_divide.asm +; dos_port/engine/math/multiply_divide.asm +; +; _Multiply / _Divide — emulate the Game Boy 24-bit×8-bit multiply and the +; multi-byte ÷ 8-bit divide, preserving the exact HRAM scratch side-effects so +; callers (CalcStat, CalcExperience, damage calc, …) see identical results. +; +; Source: engine/math/multiply_divide.asm (pret/pokeyellow). +; +; HRAM map (gb_memmap.inc): H_PRODUCT=FF95(4) ; H_MULTIPLICAND=FF96(3) ; +; H_MULTIPLIER=FF99 ; H_DIVIDEND=FF95(4) ; H_DIVISOR=FF99 ; H_QUOTIENT=FF95(4) ; +; H_REMAINDER=FF99. (Quotient overlaps dividend; remainder overlaps divisor.) %include "gb_macros.inc" %include "gb_memmap.inc" @@ -9,28 +19,21 @@ global _Multiply global _Divide ; ----------------------------------------------------------------------------- -; _Multiply -; -; Emulates the Game Boy 24-bit by 8-bit multiplication, storing the 32-bit result -; at H_PRODUCT. Uses hardware multiplication for speed while maintaining exact -; memory side effects. +; _Multiply — 24-bit multiplicand (H_MULTIPLICAND, big-endian) × 8-bit multiplier +; (H_MULTIPLIER) -> 32-bit product (H_PRODUCT, big-endian). Zeros H_MULTIPLIER, +; matching the GB loop's end state. Caller (wrapper) preserves esi/edx/bx. ; ----------------------------------------------------------------------------- _Multiply: - ; Load 8-bit multiplier movzx ecx, byte [ebp + H_MULTIPLIER] - ; Load 24-bit multiplicand (big endian) movzx eax, byte [ebp + H_MULTIPLICAND + 0] shl eax, 8 mov al, byte [ebp + H_MULTIPLICAND + 1] shl eax, 8 mov al, byte [ebp + H_MULTIPLICAND + 2] - ; Hardware multiply (EAX * ECX -> EDX:EAX) - ; Since max is 24-bit * 8-bit, the result is exactly 32-bit and fits in EAX. - mul ecx + mul ecx ; EDX:EAX = eax * ecx (fits in EAX) - ; Store the 32-bit product to H_PRODUCT (big endian) mov byte [ebp + H_PRODUCT + 3], al shr eax, 8 mov byte [ebp + H_PRODUCT + 2], al @@ -39,160 +42,56 @@ _Multiply: shr eax, 8 mov byte [ebp + H_PRODUCT + 0], al - ; Match exact Game Boy loop side-effects mov byte [ebp + H_MULTIPLIER], 0 - - ; (Optional but faithful) - gb code left the product in hMultiplyBuffer too - ; But we don't strictly have a constant for H_MULTIPLY_BUFFER yet. It's at H_DIVIDE_BUFFER. - ; We'll just omit it unless needed, as nothing reads it. ret - ; ----------------------------------------------------------------------------- -; _Divide +; _Divide — divide the BH-byte big-endian dividend at H_DIVIDEND by the 8-bit +; divisor at H_DIVISOR. Writes the 32-bit quotient big-endian to H_QUOTIENT and +; the remainder to H_REMAINDER. BH = dividend length in bytes (1..4). ; -; Emulates the Game Boy long division. -; Divides a multi-byte dividend at H_DIVIDEND by an 8-bit divisor at H_DIVISOR. -; BH (b) contains the length of the dividend in bytes. -; Uses 32-bit hardware division for maximum efficiency. +; Rewritten from the original (broken) draft, which used the SM83 mnemonic `sbc` +; (invalid x86; the file never assembled) and an unverified byte-level emulation. +; This uses a single hardware divide with the same memory contract. ; ----------------------------------------------------------------------------- _Divide: - ; Read divisor - movzx ecx, byte [ebp + H_DIVISOR] - - ; Sanity check: prevent divide by zero + push ebx + push edx + push edi + + movzx ecx, byte [ebp + H_DIVISOR] ; divisor test ecx, ecx - jz .div_by_zero + jz .done ; guard divide-by-zero (GB would loop forever) + + movzx ebx, bh ; ebx = dividend length in bytes + test ebx, ebx + jz .done + + ; Assemble the big-endian dividend (first BH bytes of H_DIVIDEND) into EAX + ; before any quotient store, since H_QUOTIENT overlaps H_DIVIDEND. + xor eax, eax + xor edi, edi +.assemble: + shl eax, 8 + mov al, byte [ebp + H_DIVIDEND + edi] + inc edi + cmp edi, ebx + jb .assemble - ; Determine the size of the dividend from BH (b) - ; GB loop shifts 'b' bytes. - ; hDividend is a 4-byte buffer (FF95-FF98). - ; If b=1, dividend is at H_DIVIDEND+3 (or H_DIVIDEND+0? GB code reads from H_DIVIDEND+1 and shifts left). - ; Wait, the GB code assumes the dividend is in the LAST 'b' bytes of hDividend! - ; Actually, let's just do a faithful implementation using registers to avoid subtle bugs - ; with how the bytes are aligned in H_DIVIDEND. - - ; We'll use the faithful division by subtraction, but highly optimized in registers. - ; EAX = H_DIVIDEND (32-bit big endian) - mov eax, dword [ebp + H_DIVIDEND] - bswap eax - - ; EDI = {H_DIVISOR, hDivideBuffer[0]} - ; wait, hDivideBuffer[0] is initially 0. - movzx edi, byte [ebp + H_DIVISOR] - shl edi, 8 - - ; EDX = quotient (hDivideBuffer+1..+4), initially 0 xor edx, edx + div ecx ; EAX = quotient, EDX = remainder -.loop_byte: - ; process 8 bits - mov ch, 8 -.loop_bit: - ; subtract 16-bit divisor (EDI) from top 16 bits of EAX? - ; In GB: hDivideBuffer[0] and hDividend[1] - hDivisor - ; Effectively, EAX is shifted left into a 40-bit buffer. - ; Let's just emulate the byte-level exact logic instead of guessing the 32-bit math equivalent. - - ; GB: - ; ldh a, [hDivideBuffer] - ; sub hDividend+1 - ; ... - ; This is getting complicated to map to registers exactly. Let's just use the memory exactly like GB! - - ; Zero out hDivideBuffer (5 bytes) - ; We'll assume H_DIVIDE_BUFFER is at FF9A (it is). - mov byte [ebp + H_DIVIDE_BUFFER + 0], 0 - mov byte [ebp + H_DIVIDE_BUFFER + 1], 0 - mov byte [ebp + H_DIVIDE_BUFFER + 2], 0 - mov byte [ebp + H_DIVIDE_BUFFER + 3], 0 - mov byte [ebp + H_DIVIDE_BUFFER + 4], 0 - - mov ch, 9 ; e = 9 - -.div_loop: - mov al, byte [ebp + H_DIVIDE_BUFFER + 0] - mov cl, al - mov al, byte [ebp + H_DIVIDEND + 1] - sub al, cl - mov dl, al - - mov al, byte [ebp + H_DIVISOR] - mov cl, al - mov al, byte [ebp + H_DIVIDEND] - sbc al, cl - jc .div_next - - mov byte [ebp + H_DIVIDEND], al - mov al, dl - mov byte [ebp + H_DIVIDEND + 1], al - - mov al, byte [ebp + H_DIVIDE_BUFFER + 4] - inc al - mov byte [ebp + H_DIVIDE_BUFFER + 4], al - jmp .div_loop - -.div_next: - cmp bh, 1 - jz .div_done - - mov al, byte [ebp + H_DIVIDE_BUFFER + 4] - shl al, 1 - mov byte [ebp + H_DIVIDE_BUFFER + 4], al - - mov al, byte [ebp + H_DIVIDE_BUFFER + 3] - rcl al, 1 - mov byte [ebp + H_DIVIDE_BUFFER + 3], al - - mov al, byte [ebp + H_DIVIDE_BUFFER + 2] - rcl al, 1 - mov byte [ebp + H_DIVIDE_BUFFER + 2], al - - mov al, byte [ebp + H_DIVIDE_BUFFER + 1] - rcl al, 1 - mov byte [ebp + H_DIVIDE_BUFFER + 1], al - - dec ch - jnz .div_next2 - - mov ch, 8 - mov al, byte [ebp + H_DIVIDE_BUFFER + 0] - mov byte [ebp + H_DIVISOR], al - mov byte [ebp + H_DIVIDE_BUFFER + 0], 0 - - mov al, byte [ebp + H_DIVIDEND + 1] - mov byte [ebp + H_DIVIDEND], al - mov al, byte [ebp + H_DIVIDEND + 2] - mov byte [ebp + H_DIVIDEND + 1], al - mov al, byte [ebp + H_DIVIDEND + 3] - mov byte [ebp + H_DIVIDEND + 2], al - -.div_next2: - cmp ch, 1 - jnz .div_okay - dec bh -.div_okay: - mov al, byte [ebp + H_DIVISOR] - shr al, 1 - mov byte [ebp + H_DIVISOR], al - - mov al, byte [ebp + H_DIVIDE_BUFFER + 0] - rcr al, 1 - mov byte [ebp + H_DIVIDE_BUFFER + 0], al - jmp .div_loop - -.div_done: - mov al, byte [ebp + H_DIVIDEND + 1] - mov byte [ebp + H_REMAINDER], al - - mov al, byte [ebp + H_DIVIDE_BUFFER + 4] mov byte [ebp + H_QUOTIENT + 3], al - mov al, byte [ebp + H_DIVIDE_BUFFER + 3] + shr eax, 8 mov byte [ebp + H_QUOTIENT + 2], al - mov al, byte [ebp + H_DIVIDE_BUFFER + 2] + shr eax, 8 mov byte [ebp + H_QUOTIENT + 1], al - mov al, byte [ebp + H_DIVIDE_BUFFER + 1] + shr eax, 8 mov byte [ebp + H_QUOTIENT + 0], al - -.div_by_zero: + + mov byte [ebp + H_REMAINDER], dl +.done: + pop edi + pop edx + pop ebx ret diff --git a/dos_port/src/home/math.asm b/dos_port/src/home/math.asm index 8202b113..f0f863a4 100644 --- a/dos_port/src/home/math.asm +++ b/dos_port/src/home/math.asm @@ -12,9 +12,11 @@ section .text ; FF95-FF98 = product Multiply: push esi - push bx - call _Multiply + push edx ; GB _Multiply preserves de; our _Multiply clobbers + push bx ; edx via `mul ecx`, so save it (CalcStat keeps the + call _Multiply ; base stat in e/dl across the stat-exp multiply loop). pop bx + pop edx pop esi ret diff --git a/dos_port/src/home/move_mon.asm b/dos_port/src/home/move_mon.asm new file mode 100644 index 00000000..c9d6a013 --- /dev/null +++ b/dos_port/src/home/move_mon.asm @@ -0,0 +1,235 @@ +; move_mon.asm — CalcStats / CalcStat (Pokémon data/stats plan). +; +; Source: home/move_mon.asm:CalcStats, CalcStat (pret/pokeyellow). +; +; Stat formula (per stat): (((Base + IV) * 2 + ceil(sqrt(statExp))/4) * Level)/100 +; then + Level + 10 for HP, or + 5 for the others; capped at MAX_STAT_VALUE (999). +; +; Register map: a=AL, b=BH, c=BL, d=DH, e=DL, hl=ESI, de=EDX, bc=EBX. +; GB memory at [EBP+addr]; HRAM math scratch via H_MULTIPLICAND/H_PRODUCT/etc. +; The big-endian 2-byte result is left in H_MULTIPLICAND+1 (high) / +2 (low), +; exactly as the original, so CalcStats can copy it straight to [de]. +; +; Build: nasm -f coff -I include/ -I . -o move_mon.o move_mon.asm + +bits 32 + +%include "gb_memmap.inc" +%include "gb_constants.inc" + +extern Multiply +extern Divide + +global CalcStats +global CalcStat + +MAX_STAT_HIGH equ (MAX_STAT_VALUE >> 8) & 0xFF ; HIGH(999) = 0x03 +MAX_STAT_LOW equ MAX_STAT_VALUE & 0xFF ; LOW(999) = 0xE7 + +section .text + +; calculates all 5 stats of the current mon and writes them to [de] (2 bytes each) +; In: BH (b) = consider stat exp?; ESI (hl) = base ptr to stat exp values; +; EDX (de) = destination pointer (GB address). +CalcStats: + mov bl, 0 ; c = 0 +.statsLoop: + inc bl ; inc c + call CalcStat + mov al, [ebp + H_MULTIPLICAND + 1] ; stat high byte + mov [ebp + edx], al + inc edx + mov al, [ebp + H_MULTIPLICAND + 2] ; stat low byte + mov [ebp + edx], al + inc edx + cmp bl, NUM_STATS + jne .statsLoop + ret + +; calculates stat c of the current mon +; In: BL (c) = stat (HP=1,Atk=2,Def=3,Spd=4,Spc=5); BH (b) = consider stat exp?; +; ESI (hl) = base ptr to stat exp ([hl+2c-1] and [hl+2c]). +; Out: result in H_MULTIPLICAND+1/+2 (big-endian). ESI/EDX/EBX preserved. +CalcStat: + push esi + push edx + push ebx + mov al, bh ; ld a, b + mov dh, al ; ld d, a (consider-stat-exp flag) + push esi ; push hl + mov esi, wMonHeader ; ld hl, wMonHeader + mov bh, 0 ; ld b, 0 + movzx ecx, bx ; add hl, bc + add esi, ecx + mov al, [ebp + esi] ; ld a, [hl] base stat value + mov dl, al ; ld e, a + pop esi ; pop hl (stat exp base ptr) + push esi ; push hl + shl bl, 1 ; sla c (c *= 2) + mov al, dh ; ld a, d + test al, al ; and a (consider stat exp?) + jz .statExpDone + movzx ecx, bx ; add hl, bc (skip to stat exp value) + add esi, ecx +.statExpLoop: ; ceil(sqrt(statExp)) in b + xor al, al + mov [ebp + H_MULTIPLICAND], al + mov [ebp + H_MULTIPLICAND + 1], al + inc bh ; inc b + mov al, bh + cmp al, 0xFF + je .statExpDone + mov [ebp + H_MULTIPLICAND + 2], al ; b + mov [ebp + H_MULTIPLIER], al ; b + call Multiply ; b*b -> product + mov al, [ebp + esi] ; ld a, [hld] (stat exp low byte) + dec esi + mov dh, al ; ld d, a + mov al, [ebp + H_PRODUCT + 3] + sub al, dh ; sub d (sets borrow) + mov al, [ebp + esi] ; ld a, [hli] (stat exp high byte; mov/inc keep CF) + inc esi + mov dh, al ; ld d, a + mov al, [ebp + H_PRODUCT + 2] + sbb al, dh ; sbc d (test b^2 < statExp) + jc .statExpLoop +.statExpDone: + shr bl, 1 ; srl c (back to stat number) + pop esi ; pop hl (stat exp base ptr) + push ebx ; push bc + mov ebx, MON_DVS - (MON_HP_EXP - 1) ; ld bc, $0B + movzx ecx, bx ; add hl, bc -> hl = MON_DVS + add esi, ecx + pop ebx ; pop bc (stat number back in c/BL) + mov al, bl ; ld a, c + cmp al, 2 + je .getAttackIV + cmp al, 3 + je .getDefenseIV + cmp al, 4 + je .getSpeedIV + cmp al, 5 + je .getSpecialIV +; HP IV = LSB of the other four IVs + push ebx + mov al, [ebp + esi] ; Atk IV byte (DV byte 0) + rol al, 4 ; swap a + and al, 1 + shl al, 1 + shl al, 1 + shl al, 1 + mov bh, al + mov al, [ebp + esi] ; Def IV (byte 0 low nibble); hl++ + inc esi + and al, 1 + shl al, 1 + shl al, 1 + add al, bh + mov bh, al + mov al, [ebp + esi] ; Spd IV (byte 1 high nibble) + rol al, 4 ; swap a + and al, 1 + shl al, 1 + add al, bh + mov bh, al + mov al, [ebp + esi] ; Spc IV (byte 1 low nibble) + and al, 1 + add al, bh ; HP IV + pop ebx + jmp .calcStatFromIV +.getAttackIV: + mov al, [ebp + esi] + rol al, 4 ; swap a + and al, 0x0F + jmp .calcStatFromIV +.getDefenseIV: + mov al, [ebp + esi] + and al, 0x0F + jmp .calcStatFromIV +.getSpeedIV: + inc esi + mov al, [ebp + esi] + rol al, 4 ; swap a + and al, 0x0F + jmp .calcStatFromIV +.getSpecialIV: + inc esi + mov al, [ebp + esi] + and al, 0x0F +.calcStatFromIV: + mov dh, 0 ; ld d, 0 + add al, dl ; add e (IV + Base) + mov dl, al ; ld e, a + jnc .noCarry + inc dh ; de = Base + IV +.noCarry: + shl dl, 1 + rcl dh, 1 ; de = (Base + IV) * 2 + shr bh, 1 + shr bh, 1 ; b = ceil(sqrt(statExp)) / 4 + mov al, bh + add al, dl ; add e + jnc .noCarry2 + inc dh +.noCarry2: + mov [ebp + H_MULTIPLICAND + 2], al + mov al, dh + mov [ebp + H_MULTIPLICAND + 1], al + xor al, al + mov [ebp + H_MULTIPLICAND], al + mov al, [ebp + wCurEnemyLevel] + mov [ebp + H_MULTIPLIER], al + call Multiply ; * Level + mov al, [ebp + H_MULTIPLICAND] + mov [ebp + H_DIVIDEND], al + mov al, [ebp + H_MULTIPLICAND + 1] + mov [ebp + H_DIVIDEND + 1], al + mov al, [ebp + H_MULTIPLICAND + 2] + mov [ebp + H_DIVIDEND + 2], al + mov al, 0x64 + mov [ebp + H_DIVISOR], al + mov bh, 3 ; b = 3-byte dividend + call Divide ; / 100 + mov al, bl ; ld a, c + cmp al, 1 + mov al, 5 ; +5 for non-HP + jne .notHPStat + mov al, [ebp + wCurEnemyLevel] ; HP: + Level first + mov bh, al + mov al, [ebp + H_MULTIPLICAND + 2] + add al, bh + mov [ebp + H_MULTIPLICAND + 2], al + jnc .noCarry3 + mov al, [ebp + H_MULTIPLICAND + 1] + inc al + mov [ebp + H_MULTIPLICAND + 1], al +.noCarry3: + mov al, 10 ; +10 for HP +.notHPStat: + mov bh, al + mov al, [ebp + H_MULTIPLICAND + 2] + add al, bh + mov [ebp + H_MULTIPLICAND + 2], al + jnc .noCarry4 + mov al, [ebp + H_MULTIPLICAND + 1] + inc al + mov [ebp + H_MULTIPLICAND + 1], al +.noCarry4: + mov al, [ebp + H_MULTIPLICAND + 1] ; overflow check (>999) + cmp al, MAX_STAT_HIGH + 1 + jnc .overflow + cmp al, MAX_STAT_HIGH + jc .noOverflow + mov al, [ebp + H_MULTIPLICAND + 2] + cmp al, MAX_STAT_LOW + 1 + jc .noOverflow +.overflow: + mov al, MAX_STAT_HIGH + mov [ebp + H_MULTIPLICAND + 1], al + mov al, MAX_STAT_LOW + mov [ebp + H_MULTIPLICAND + 2], al +.noOverflow: + pop ebx + pop edx + pop esi + ret diff --git a/dos_port/src/home/pokemon.asm b/dos_port/src/home/pokemon.asm new file mode 100644 index 00000000..20d0c9cd --- /dev/null +++ b/dos_port/src/home/pokemon.asm @@ -0,0 +1,51 @@ +; pokemon.asm — GetMonHeader (Pokémon data/stats plan). +; +; Source: home/pokemon.asm:GetMonHeader + engine/menus/pokedex.asm:IndexToPokedex. +; +; Copies the 28-byte base-stats record for the internal species index in +; [wCurSpecies] into wMonHeader, then overwrites byte 0 (the dex id) with the +; internal index — matching the original. +; +; DIVERGENCE FROM GB: the data tables (BaseStats, IndexToPokedex) live in the +; program image as flat labels, not in EBP-relative GB memory, so we index them +; directly and `rep movsb` into [ebp+wMonHeader] instead of going through the +; GB CopyData/AddNTimes (which assume EBP-relative source). The fossil/ghost +; special sprite IDs are not handled (no battle sprites in this port yet). +; +; Build: nasm -f coff -I include/ -I . -o pokemon.o pokemon.asm + +bits 32 + +%include "gb_memmap.inc" +%include "gb_constants.inc" + +extern BaseStats +extern IndexToPokedex + +global GetMonHeader + +section .text + +GetMonHeader: + pushad + + ; dex = IndexToPokedex[wCurSpecies - 1] (internal index -> national dex) + movzx eax, byte [ebp + wCurSpecies] + dec eax + movzx eax, byte [IndexToPokedex + eax] + + ; src = BaseStats + (dex - 1) * BASE_DATA_SIZE + dec eax + imul eax, eax, BASE_DATA_SIZE + lea esi, [BaseStats + eax] ; flat (program-image) source + + lea edi, [ebp + wMonHeader] ; flat dest in GB memory + mov ecx, BASE_DATA_SIZE + rep movsb + + ; wMonHIndex = wCurSpecies (write internal index back over the dex byte) + mov al, [ebp + wCurSpecies] + mov [ebp + wMonHIndex], al + + popad + ret