mirror of
https://github.com/pret/pokeyellow.git
synced 2026-08-25 11:54:47 -05:00
pokemon: data/stats Stage 3-4 — CalcStats core + verified gate
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GcHpAsg8pwsVATD6LWPsoR
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
235
dos_port/src/home/move_mon.asm
Normal file
235
dos_port/src/home/move_mon.asm
Normal file
@@ -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
|
||||
51
dos_port/src/home/pokemon.asm
Normal file
51
dos_port/src/home/pokemon.asm
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user