summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/assembly_programming.md15
-rw-r--r--docs/bugs_and_glitches.md1455
-rw-r--r--docs/effect_commands.md358
-rw-r--r--docs/event_commands.md348
-rw-r--r--docs/images/hp_exp_bar_border.pngbin0 -> 125 bytes
-rw-r--r--docs/images/hp_exp_bar_border_fix.pngbin0 -> 124 bytes
-rw-r--r--docs/images/port.pngbin0 -> 1055 bytes
-rw-r--r--docs/images/port_fix.pngbin0 -> 1056 bytes
-rw-r--r--docs/map_scripts.md139
-rw-r--r--docs/move_anim_commands.md102
-rw-r--r--docs/movement_commands.md84
-rw-r--r--docs/music_commands.md96
-rw-r--r--docs/pic_animations.md31
-rw-r--r--docs/text_commands.md164
14 files changed, 2792 insertions, 0 deletions
diff --git a/docs/assembly_programming.md b/docs/assembly_programming.md
new file mode 100644
index 000000000..c04710b05
--- /dev/null
+++ b/docs/assembly_programming.md
@@ -0,0 +1,15 @@
+# Assembly Programming
+
+- [**RGBDS documentation**](rgbds-doc): Includes information on the RGBDS tools and the assembly language syntax.
+- [**Pan Docs**](pan-docs): Everything You Always Wanted To Know About GAMEBOY (but were afraid to ask).
+- [**GameBoy Programming Manual](gb-manual): The official GameBoy programming and hardware manual by Nintendo.
+- [**GameBoy Opcode Summary**](gb-opcodes): Describes the opcodes of GameBoy assembly language.
+- [**GameBoy Memory Map**](gb-memory-map): Describes the GameBoy Color address space.
+- [**awesome-gbdev**](awesome-gbdev): A curated list of Game Boy development resources such as tools, docs, emulators, related projects and open-source ROMs.
+
+[rgbds-doc]: https://rednex.github.io/
+[pan-docs]: http://bgb.bircd.org/pandocs.htm
+[gb-manual]: https://ia801906.us.archive.org/19/items/GameBoyProgManVer1.1/GameBoyProgManVer1.1.pdf
+[gb-opcodes]: http://www.devrs.com/gb/files/opcodes.html
+[gb-memory-map]: http://gameboy.mongenel.com/dmg/asmmemmap.html
+[awesome-gbdev]: https://github.com/avivace/awesome-gbdev
diff --git a/docs/bugs_and_glitches.md b/docs/bugs_and_glitches.md
new file mode 100644
index 000000000..b38ec12ee
--- /dev/null
+++ b/docs/bugs_and_glitches.md
@@ -0,0 +1,1455 @@
+# Bugs and Glitches
+
+
+## Contents
+
+- [Thick Club and Light Ball can decrease damage done with boosted (Special) Attack](#thick-club-and-light-ball-can-decrease-damage-done-with-boosted-special-attack)
+- [Metal Powder can increase damage taken with boosted (Special) Defense](#metal-powder-can-increase-damage-taken-with-boosted-special-defense)
+- [Belly Drum sharply boosts Attack even with under 50% HP](#belly-drum-sharply-boosts-attack-even-with-under-50-hp)
+- [Moves that lower Defense can do so after breaking a Substitute](#moves-that-lower-defense-can-do-so-after-breaking-a-substitute)
+- [Counter and Mirror Coat still work if the opponent uses an item](#counter-and-mirror-coat-still-work-if-the-opponent-uses-an-item)
+- [A Disabled but PP Up–enhanced move may not trigger Struggle](#a-disabled-but-pp-upenhanced-move-may-not-trigger-struggle)
+- [A Pokémon that fainted from Pursuit will have its old status condition when revived](#a-pokémon-that-fainted-from-pursuit-will-have-its-old-status-condition-when-revived)
+- [Lock-On and Mind Reader don't always bypass Fly and Dig](#lock-on-and-mind-reader-dont-always-bypass-fly-and-dig)
+- [Beat Up can desynchronize link battles](#beat-up-can-desynchronize-link-battles)
+- [Present damage is incorrect in link battles](#present-damage-is-incorrect-in-link-battles)
+- ["Smart" AI encourages Mean Look if its own Pokémon is badly poisoned](#smart-ai-encourages-mean-look-if-its-own-pokémon-is-badly-poisoned)
+- [AI makes a false assumption about `CheckTypeMatchup`](#ai-makes-a-false-assumption-about-checktypematchup)
+- [NPC use of Full Heal or Full Restore does not cure Nightmare status](#npc-use-of-full-heal-or-full-restore-does-not-cure-nightmare-status)
+- [HP bar animation is slow for high HP](#hp-bar-animation-is-slow-for-high-hp)
+- [HP bar animation off-by-one error for low HP](#hp-bar-animation-off-by-one-error-for-low-hp)
+- [Experience underflow for level 1 Pokémon with Medium-Slow growth rate](#experience-underflow-for-level-1-pokémon-with-medium-slow-growth-rate)
+- [Five-digit experience gain is printed incorrectly](#five-digit-experience-gain-is-printed-incorrectly)
+- [BRN/PSN/PAR do not affect catch rate](#brnpsnpar-do-not-affect-catch-rate)
+- [Moon Ball does not boost catch rate](#moon-ball-does-not-boost-catch-rate)
+- [Love Ball boosts catch rate for the wrong gender](#love-ball-boosts-catch-rate-for-the-wrong-gender)
+- [Fast Ball only boosts catch rate for three Pokémon](#fast-ball-only-boosts-catch-rate-for-three-pokémon)
+- [Dragon Scale, not Dragon Fang, boosts Dragon-type moves](#dragon-scale-not-dragon-fang-boosts-dragon-type-moves)
+- [Daisy's grooming doesn't always increase happiness](#daisys-grooming-doesnt-always-increase-happiness)
+- [Magikarp in Lake of Rage are shorter, not longer](#magikarp-in-lake-of-rage-are-shorter-not-longer)
+- [Magikarp lengths can be miscalculated](#magikarp-lengths-can-be-miscalculated)
+- [Battle transitions fail to account for the enemy's level](#battle-transitions-fail-to-account-for-the-enemys-level)
+- [Slot machine payout sound effects cut each other off](#slot-machine-payout-sound-effects-cut-each-other-off)
+- [Team Rocket battle music is not used for Executives or Scientists](#team-rocket-battle-music-is-not-used-for-executives-or-scientists)
+- [No bump noise if standing on tile `$3E`](#no-bump-noise-if-standing-on-tile-3e)
+- [Playing Entei's Pokédex cry can distort Raikou's and Suicune's](#playing-enteis-pokédex-cry-can-distort-raikous-and-suicunes)
+- [In-battle “`…`” ellipsis is too high](#in-battle--ellipsis-is-too-high)
+- [Two tiles in the `port` tileset are drawn incorrectly](#two-tiles-in-the-port-tileset-are-drawn-incorrectly)
+- [`LoadMetatiles` wraps around past 128 blocks](#loadmetatiles-wraps-around-past-128-blocks)
+- [Surfing directly across a map connection does not load the new map](#surfing-directly-across-a-map-connection-does-not-load-the-new-map)
+- [`Function6ec1` does not correctly limit object movement](#function6ec1-does-not-correctly-limit-object-movement)
+- [`CheckOwnMon` only checks the first five letters of OT names](#checkownmon-only-checks-the-first-five-letters-of-ot-names)
+- [Catching a Transformed Pokémon always catches a Ditto](#catching-a-transformed-pokémon-always-catches-a-ditto)
+- [Using a Park Ball in normal battles has a corrupt animation](#using-a-park-ball-in-normal-battles-has-a-corrupt-animation)
+- [`HELD_CATCH_CHANCE` has no effect](#held_catch_chance-has-no-effect)
+- [Only the first three `EvosAttacks` evolution entries can have Stone compatibility reported correctly](#only-the-first-three-evosattacks-evolution-entries-can-have-stone-compatibility-reported-correctly)
+- [`ScriptCall` can overflow `wScriptStack` and crash](#scriptcall-can-overflow-wscriptstack-and-crash)
+- [`LoadSpriteGFX` does not limit the capacity of `UsedSprites`](#loadspritegfx-does-not-limit-the-capacity-of-usedsprites)
+- [`ChooseWildEncounter` doesn't really validate the wild Pokémon species](#choosewildencounter-doesnt-really-validate-the-wild-pokémon-species)
+- [`TryObjectEvent` arbitrary code execution](#tryobjectevent-arbitrary-code-execution)
+- [`Special_CheckBugContestContestantFlag` can read beyond its data table](#special_checkbugcontestcontestantflag-can-read-beyond-its-data-table)
+- [`ClearWRAM` only clears WRAM bank 1](#clearwram-only-clears-wram-bank-1)
+- [`GetForestTreeFrame` works, but it's still bad](#getforesttreeframe-works-but-its-still-bad)
+
+
+## Thick Club and Light Ball can decrease damage done with boosted (Special) Attack
+
+([Video](https://www.youtube.com/watch?v=rGqu3d3pdok&t=450))
+
+This is a bug with `SpeciesItemBoost` in [battle/effect_commands.asm](/battle/effect_commands.asm):
+
+```asm
+; Double the stat
+ sla l
+ rl h
+ ret
+```
+
+**Fix:**
+
+```asm
+; Double the stat
+ sla l
+ rl h
+
+ ld a, 999 / $100
+ cp h
+ jr c, .cap
+ ld a, 999 % $100
+ cp l
+ ret nc
+
+.cap
+ ld h, 999 / $100
+ ld l, 999 % $100
+ ret
+```
+
+
+## Metal Powder can increase damage taken with boosted (Special) Defense
+
+([Video](https://www.youtube.com/watch?v=rGqu3d3pdok&t=450))
+
+This is a bug with `DittoMetalPowder` in [battle/effect_commands.asm](/battle/effect_commands.asm):
+
+```asm
+ ld a, c
+ srl a
+ add c
+ ld c, a
+ ret nc
+
+ srl b
+ ld a, b
+ and a
+ jr nz, .done
+ inc b
+.done
+ scf
+ rr c
+ ret
+```
+
+**Fix:**
+
+```asm
+ ld a, c
+ srl a
+ add c
+ ld c, a
+ ret nc
+
+ srl b
+ ld a, b
+ and a
+ jr nz, .done
+ inc b
+.done
+ scf
+ rr c
+
+ ld a, 999 / $100
+ cp b
+ jr c, .cap
+ ld a, 999 % $100
+ cp c
+ ret nc
+
+.cap
+ ld b, 999 / $100
+ ld c, 999 % $100
+ ret
+```
+
+
+## Belly Drum sharply boosts Attack even with under 50% HP
+
+([Video](https://www.youtube.com/watch?v=zuCLMikWo4Y))
+
+This is a bug with `BattleCommand_BellyDrum` in [battle/effect_commands.asm](/battle/effect_commands.asm):
+
+```asm
+BattleCommand_BellyDrum: ; 37c1a
+; bellydrum
+; This command is buggy because it raises the user's attack
+; before checking that it has enough HP to use the move.
+; Swap the order of these two blocks to fix.
+ call BattleCommand_AttackUp2
+ ld a, [AttackMissed]
+ and a
+ jr nz, .failed
+
+ callab GetHalfMaxHP
+ callab CheckUserHasEnoughHP
+ jr nc, .failed
+```
+
+**Fix:**
+
+```asm
+BattleCommand_BellyDrum: ; 37c1a
+; bellydrum
+ callab GetHalfMaxHP
+ callab CheckUserHasEnoughHP
+ jr nc, .failed
+
+ call BattleCommand_AttackUp2
+ ld a, [AttackMissed]
+ and a
+ jr nz, .failed
+```
+
+
+## Moves that lower Defense can do so after breaking a Substitute
+
+([Video](https://www.youtube.com/watch?v=OGwKPRJLaaI))
+
+This bug affects Acid, Iron Tail, and Rock Smash.
+
+This is a bug with `DefenseDownHit` in [battle/moves/move_effects.asm](/battle/moves/move_effects.asm):
+
+```asm
+DefenseDownHit:
+ checkobedience
+ usedmovetext
+ doturn
+ critical
+ damagestats
+ damagecalc
+ stab
+ damagevariation
+ checkhit
+ effectchance
+ hittarget
+ failuretext
+ checkfaint
+ criticaltext
+ supereffectivetext
+ checkdestinybond
+ buildopponentrage
+ effectchance ; bug: duplicate effectchance shouldn't be here
+ defensedown
+ statdownmessage
+ endmove
+```
+
+**Fix:** Delete the second `effectchance`.
+
+
+## Counter and Mirror Coat still work if the opponent uses an item
+
+([Video](https://www.youtube.com/watch?v=uRYyzKRatFk))
+
+*To do:* Identify specific code causing this bug and fix it.
+
+
+## A Disabled but PP Up–enhanced move may not trigger Struggle
+
+([Video](https://www.youtube.com/watch?v=1v9x4SgMggs))
+
+This is a bug with `CheckPlayerHasUsableMoves` in [battle/core.asm](/battle/core.asm):
+
+```asm
+.done
+ ; Bug: this will result in a move with PP Up confusing the game.
+ ; Replace with "and $3f" to fix.
+ and a
+ ret nz
+
+.force_struggle
+ ld hl, BattleText_PkmnHasNoMovesLeft
+ call StdBattleTextBox
+ ld c, 60
+ call DelayFrames
+ xor a
+ ret
+```
+
+**Fix:** Change `and a` to `and $3f`.
+
+
+## A Pokémon that fainted from Pursuit will have its old status condition when revived
+
+([Video](https://www.youtube.com/watch?v=tiRvw-Nb2ME))
+
+*To do:* Identify specific code causing this bug and fix it.
+
+
+## Lock-On and Mind Reader don't always bypass Fly and Dig
+
+This bug affects Attract, Curse, Foresight, Mean Look, Mimic, Nightmare, Spider Web, Transform, and stat-lowering effects of moves like String Shot or Bubble during the semi-invulnerable turn of Fly or Dig.
+
+This is a bug with `CheckHiddenOpponent` in [battle/effect_commands.asm](/battle/effect_commands.asm):
+
+```asm
+CheckHiddenOpponent: ; 37daa
+; BUG: This routine should account for Lock-On and Mind Reader.
+ ld a, BATTLE_VARS_SUBSTATUS3_OPP
+ call GetBattleVar
+ and 1 << SUBSTATUS_FLYING | 1 << SUBSTATUS_UNDERGROUND
+ ret
+```
+
+*To do:* Fix this bug.
+
+
+## Beat Up can desynchronize link battles
+
+([Video](https://www.youtube.com/watch?v=202-iAsrIa8))
+
+This is a bug with `BattleCommand_BeatUp` in [battle/effect_commands.asm](/battle/effect_commands.asm):
+
+```asm
+.got_mon
+ ld a, [wd002]
+ ld hl, PartyMonNicknames
+ call GetNick
+ ld a, MON_HP
+ call GetBeatupMonLocation
+ ld a, [hli]
+ or [hl]
+ jp z, .beatup_fail ; fainted
+ ld a, [wd002]
+ ld c, a
+ ld a, [CurBattleMon]
+ ; BUG: this can desynchronize link battles
+ ; Change "cp [hl]" to "cp c" to fix
+ cp [hl]
+ ld hl, BattleMonStatus
+ jr z, .active_mon
+ ld a, MON_STATUS
+ call GetBeatupMonLocation
+.active_mon
+ ld a, [hl]
+ and a
+ jp nz, .beatup_fail
+```
+
+**Fix:** Change `cp [hl]` to `cp c`.
+
+
+## Present damage is incorrect in link battles
+
+([Video](https://www.youtube.com/watch?v=XJaQoKtrEuw))
+
+This bug existed for all battles in Gold and Silver, and was only fixed for single-player battles in Crystal to preserve link compatibility.
+
+This is a bug with `BattleCommand_Present` in [battle/effects/present.asm](/battle/effects/present.asm):
+
+```asm
+BattleCommand_Present: ; 37874
+; present
+
+ ld a, [wLinkMode]
+ cp LINK_COLOSSEUM
+ jr z, .colosseum_skippush
+ push bc
+ push de
+.colosseum_skippush
+
+ call BattleCommand_Stab
+
+ ld a, [wLinkMode]
+ cp LINK_COLOSSEUM
+ jr z, .colosseum_skippop
+ pop de
+ pop bc
+.colosseum_skippop
+```
+
+**Fix:**
+
+```asm
+BattleCommand_Present: ; 37874
+; present
+
+ push bc
+ push de
+ call BattleCommand_Stab
+ pop de
+ pop bc
+```
+
+
+## "Smart" AI encourages Mean Look if its own Pokémon is badly poisoned
+
+([Video](https://www.youtube.com/watch?v=cygMO-zHTls))
+
+This is a bug with `AI_Smart_MeanLook` in [battle/ai/scoring.asm](/battle/ai/scoring.asm):
+
+```asm
+; 80% chance to greatly encourage this move if the enemy is badly poisoned (buggy).
+; Should check PlayerSubStatus5 instead.
+ ld a, [EnemySubStatus5]
+ bit SUBSTATUS_TOXIC, a
+ jr nz, .asm_38e26
+```
+
+**Fix:** Change `EnemySubStatus5` to `PlayerSubStatus5`.
+
+
+## AI makes a false assumption about `CheckTypeMatchup`
+
+In [battle/effect_commands.asm](/battle/effect_commands.asm):
+
+```asm
+BattleCheckTypeMatchup: ; 347c8
+ ld hl, EnemyMonType1
+ ld a, [hBattleTurn]
+ and a
+ jr z, CheckTypeMatchup
+ ld hl, BattleMonType1
+CheckTypeMatchup: ; 347d3
+; There is an incorrect assumption about this function made in the AI related code: when
+; the AI calls CheckTypeMatchup (not BattleCheckTypeMatchup), it assumes that placing the
+; offensive type in a will make this function do the right thing. Since a is overwritten,
+; this assumption is incorrect. A simple fix would be to load the move type for the
+; current move into a in BattleCheckTypeMatchup, before falling through, which is
+; consistent with how the rest of the code assumes this code works like.
+ push hl
+ push de
+ push bc
+ ld a, BATTLE_VARS_MOVE_TYPE
+ call GetBattleVar
+ ld d, a
+```
+
+*To do:* Fix this bug.
+
+
+## NPC use of Full Heal or Full Restore does not cure Nightmare status
+
+([Video](https://www.youtube.com/watch?v=rGqu3d3pdok&t=322))
+
+This is a bug with `AI_HealStatus` in [battle/ai/items.asm](/battle/ai/items.asm):
+
+```asm
+AI_HealStatus: ; 384e0
+ ld a, [CurOTMon]
+ ld hl, OTPartyMon1Status
+ ld bc, PARTYMON_STRUCT_LENGTH
+ call AddNTimes
+ xor a
+ ld [hl], a
+ ld [EnemyMonStatus], a
+ ; Bug: this should reset SUBSTATUS_NIGHTMARE too
+ ; Uncomment the lines below to fix
+ ; ld hl, EnemySubStatus1
+ ; res SUBSTATUS_NIGHTMARE, [hl]
+ ld hl, EnemySubStatus5
+ res SUBSTATUS_TOXIC, [hl]
+ ret
+; 384f7
+```
+
+**Fix:** Uncomment `ld hl, EnemySubStatus1` and `res SUBSTATUS_NIGHTMARE, [hl]`.
+
+
+## HP bar animation is slow for high HP
+
+([Video](https://www.youtube.com/watch?v=SE-BfsFgZVM))
+
+This is a bug with `LongAnim_UpdateVariables` in [engine/anim_hp_bar.asm](/engine/anim_hp_bar.asm):
+
+```asm
+ ; This routine is buggy. The result from ComputeHPBarPixels is stored
+ ; in e. However, the pop de opcode deletes this result before it is even
+ ; used. The game then proceeds as though it never deleted that output.
+ ; To fix, uncomment the line below.
+ call ComputeHPBarPixels
+ ; ld a, e
+ pop bc
+ pop de
+ pop hl
+ ld a, e ; Comment or delete this line to fix the above bug.
+ ld hl, wCurHPBarPixels
+ cp [hl]
+ jr z, .loop
+ ld [hl], a
+ and a
+ ret
+```
+
+**Fix:** Move `ld a, e` to right after `call ComputeHPBarPixels`.
+
+
+## HP bar animation off-by-one error for low HP
+
+([Video](https://www.youtube.com/watch?v=9KyNVIZxJvI))
+
+This is a bug with `ShortHPBar_CalcPixelFrame` in [engine/anim_hp_bar.asm](/engine/anim_hp_bar.asm):
+
+```asm
+ ld b, 0
+; This routine is buggy. If [wCurHPAnimMaxHP] * [wCurHPBarPixels] is divisible
+; by 48, the loop runs one extra time. To fix, uncomment the line below.
+.loop
+ ld a, l
+ sub 6 * 8
+ ld l, a
+ ld a, h
+ sbc $0
+ ld h, a
+ ; jr z, .done
+ jr c, .done
+ inc b
+ jr .loop
+```
+
+**Fix:** Uncomment `jr z, .done`.
+
+
+## Experience underflow for level 1 Pokémon with Medium-Slow growth rate
+
+([Video](https://www.youtube.com/watch?v=SXH8u0plHrE))
+
+This can bring Pokémon straight from level 1 to 100 by gaining just a few experience points.
+
+This is a bug with `CalcExpAtLevel` in [main.asm](/main.asm):
+
+```asm
+CalcExpAtLevel: ; 50e47
+; (a/b)*n**3 + c*n**2 + d*n - e
+ ld a, [BaseGrowthRate]
+ add a
+ add a
+ ld c, a
+ ld b, 0
+ ld hl, GrowthRates
+ add hl, bc
+```
+
+**Fix:**
+
+```asm
+CalcExpAtLevel: ; 50e47
+; (a/b)*n**3 + c*n**2 + d*n - e
+ ld a, d
+ cp 1
+ jr nz, .UseExpFormula
+; Pokémon have 0 experience at level 1
+ xor a
+ ld hl, hProduct
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ ld [hl], a
+ ret
+
+.UseExpFormula
+ ld a, [BaseGrowthRate]
+ add a
+ add a
+ ld c, a
+ ld b, 0
+ ld hl, GrowthRates
+ add hl, bc
+```
+
+
+## Five-digit experience gain is printed incorrectly
+
+([Video](https://www.youtube.com/watch?v=o54VjpAEoO8))
+
+This is a bug with `Text_ABoostedStringBuffer2ExpPoints` and `Text_StringBuffer2ExpPoints` in [text/common_2.asm](/text/common_2.asm):
+
+```asm
+Text_ABoostedStringBuffer2ExpPoints::
+ text ""
+ line "a boosted"
+ cont "@"
+ deciram StringBuffer2, 2, 4
+ text " EXP. Points!"
+ prompt
+
+Text_StringBuffer2ExpPoints::
+ text ""
+ line "@"
+ deciram StringBuffer2, 2, 4
+ text " EXP. Points!"
+ prompt
+```
+
+**Fix:** Change both `deciram StringBuffer2, 2, 4` to `deciram StringBuffer2, 2, 5`.
+
+
+## BRN/PSN/PAR do not affect catch rate
+
+This is a bug with `PokeBall` in [items/item_effects.asm](/items/item_effects.asm):
+
+```asm
+.statuscheck
+; This routine is buggy. It was intended that SLP and FRZ provide a higher
+; catch rate than BRN/PSN/PAR, which in turn provide a higher catch rate than
+; no status effect at all. But instead, it makes BRN/PSN/PAR provide no
+; benefit.
+; Uncomment the line below to fix this.
+ ld b, a
+ ld a, [EnemyMonStatus]
+ and 1 << FRZ | SLP
+ ld c, 10
+ jr nz, .addstatus
+ ; ld a, [EnemyMonStatus]
+ and a
+ ld c, 5
+ jr nz, .addstatus
+ ld c, 0
+.addstatus
+ ld a, b
+ add c
+ jr nc, .max_1
+ ld a, $ff
+.max_1
+```
+
+**Fix:** Uncomment `ld a, [EnemyMonStatus]`.
+
+
+## Moon Ball does not boost catch rate
+
+This is a bug with `MoonBallMultiplier` in [items/item_effects.asm](/items/item_effects.asm):
+
+```asm
+MoonBallMultiplier:
+; This function is buggy.
+; Intent: multiply catch rate by 4 if mon evolves with moon stone
+; Reality: no boost
+
+...
+
+; Moon Stone's constant from Pokémon Red is used.
+; No Pokémon evolve with Burn Heal,
+; so Moon Balls always have a catch rate of 1×.
+ push bc
+ ld a, BANK(EvosAttacks)
+ call GetFarByte
+ cp MOON_STONE_RED ; BURN_HEAL
+ pop bc
+ ret nz
+```
+
+**Fix:** Change `MOON_STONE_RED` to `MOON_STONE`.
+
+
+## Love Ball boosts catch rate for the wrong gender
+
+This is a bug with `LoveBallMultiplier` in [items/item_effects.asm](/items/item_effects.asm):
+
+```asm
+LoveBallMultiplier:
+; This function is buggy.
+; Intent: multiply catch rate by 8 if mons are of same species, different sex
+; Reality: multiply catch rate by 8 if mons are of same species, same sex
+
+...
+
+ ld a, d
+ pop de
+ cp d
+ pop bc
+ ret nz ; for the intended effect, this should be "ret z"
+```
+
+**Fix:** Change `ret nz` to `ret z`.
+
+
+## Fast Ball only boosts catch rate for three Pokémon
+
+This is a bug with `FastBallMultiplier` in [items/item_effects.asm](/items/item_effects.asm):
+
+```asm
+FastBallMultiplier:
+; This function is buggy.
+; Intent: multiply catch rate by 4 if enemy mon is in one of the three
+; FleeMons tables.
+; Reality: multiply catch rate by 4 if enemy mon is one of the first three in
+; the first FleeMons table.
+
+...
+
+ inc hl
+ cp -1
+ jr z, .next
+ cp c
+ jr nz, .next ; for the intended effect, this should be "jr nz, .loop"
+ sla b
+ jr c, .max
+```
+
+**Fix:** Change `jr nz, .next` to `jr nz, .loop`.
+
+
+## Dragon Scale, not Dragon Fang, boosts Dragon-type moves
+
+This is a bug with `ItemAttributes` in [items/item_attributes.asm](/items/item_attributes.asm):
+
+```asm
+; DRAGON FANG
+ item_attribute 100, 0, 0, CANT_SELECT, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
+
+...
+
+; DRAGON SCALE
+ item_attribute 2100, HELD_DRAGON_BOOST, 10, CANT_SELECT, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
+```
+
+**Fix:** Move `HELD_DRAGON_BOOST` to the `DRAGON FANG` attributes and `0` to `DRAGON SCALE`.
+
+
+## Daisy's grooming doesn't always increase happiness
+
+This is a bug with `MassageOrHaircut` in [event/special.asm](/event/special.asm):
+
+```asm
+; Bug: Subtracting $ff from $ff fails to set c.
+; This can result in overflow into the next data array.
+; In the case of getting a massage from Daisy, we bleed
+; into CopyPokemonName_Buffer1_Buffer3, which passes
+; $d0 to ChangeHappiness and returns $73 to the script.
+; The end result is that there is a 0.4% chance your
+; Pokemon's happiness will not change at all.
+.loop
+ sub [hl]
+ jr c, .ok
+ inc hl
+ inc hl
+ inc hl
+ jr .loop
+
+.ok
+ inc hl
+ ld a, [hli]
+ ld [ScriptVar], a
+ ld c, [hl]
+ call ChangeHappiness
+ ret
+
+...
+
+Data_DaisyMassage: ; 746b
+ db $ff, 2, HAPPINESS_MASSAGE ; 99.6% chance
+
+CopyPokemonName_Buffer1_Buffer3: ; 746e
+ ld hl, StringBuffer1
+ ld de, StringBuffer3
+ ld bc, PKMN_NAME_LENGTH
+ jp CopyBytes
+```
+
+**Fix:**
+
+```asm
+Data_DaisyMassage: ; 746b
+ db $80, 2, HAPPINESS_MASSAGE ; 50% chance
+ db $ff, 2, HAPPINESS_MASSAGE ; 50% chance
+```
+
+
+## Magikarp in Lake of Rage are shorter, not longer
+
+This is a bug with `LoadEnemyMon.CheckMagikarpArea` in [battle/core.asm](/battle/core.asm):
+
+```asm
+.CheckMagikarpArea:
+; The z checks are supposed to be nz
+; Instead, all maps in GROUP_LAKE_OF_RAGE (mahogany area)
+; and routes 20 and 44 are treated as Lake of Rage
+
+; This also means Lake of Rage Magikarp can be smaller than ones
+; caught elsewhere rather than the other way around
+
+; Intended behavior enforces a minimum size at Lake of Rage
+; The real behavior prevents size flooring in the Lake of Rage area
+ ld a, [MapGroup]
+ cp GROUP_LAKE_OF_RAGE
+ jr z, .Happiness
+ ld a, [MapNumber]
+ cp MAP_LAKE_OF_RAGE
+ jr z, .Happiness
+```
+
+**Fix:** Change both `jr z, .Happiness` to `jr nz, .Happiness`.
+
+
+## Magikarp lengths can be miscalculated
+
+This is a bug with `CalcMagikarpLength.BCLessThanDE` in [event/magikarp.asm](/event/magikarp.asm):
+
+```asm
+.BCLessThanDE: ; fbc9a
+; Intention: Return bc < de.
+; Reality: Return b < d.
+ ld a, b
+ cp d
+ ret c
+ ret nc ; whoops
+ ld a, c
+ cp e
+ ret
+; fbca1
+```
+
+**Fix:** Delete `ret nc`.
+
+
+## Battle transitions fail to account for the enemy's level
+
+([Video](https://www.youtube.com/watch?v=eij_1060SMc))
+
+This is a bug with `StartTrainerBattle_DetermineWhichAnimation` in [engine/battle_start.asm](/engine/battle_start.asm):
+
+```asm
+StartTrainerBattle_DetermineWhichAnimation: ; 8c365 (23:4365)
+; The screen flashes a different number of times depending on the level of
+; your lead Pokemon relative to the opponent's.
+; BUG: BattleMonLevel and EnemyMonLevel are not set at this point, so whatever
+; values happen to be there will determine the animation.
+ ld de, 0
+ ld a, [BattleMonLevel]
+ add 3
+ ld hl, EnemyMonLevel
+ cp [hl]
+ jr nc, .okay
+ set 0, e
+.okay
+ ld a, [wPermission]
+ cp CAVE
+ jr z, .okay2
+ cp PERM_5
+ jr z, .okay2
+ cp DUNGEON
+ jr z, .okay2
+ set 1, e
+.okay2
+ ld hl, .StartingPoints
+ add hl, de
+ ld a, [hl]
+ ld [wJumptableIndex], a
+ ret
+; 8c38f (23:438f)
+
+.StartingPoints: ; 8c38f
+ db 1, 9
+ db 16, 24
+; 8c393
+```
+
+*To do:* Fix this bug.
+
+
+## Slot machine payout sound effects cut each other off
+
+([Video](https://www.youtube.com/watch?v=ojq3xqfRF6I))
+
+This is a bug with `Slots_PayoutAnim` in [engine/slot_machine.asm](/engine/slot_machine.asm):
+
+```asm
+.okay
+ ld [hl], e
+ dec hl
+ ld [hl], d
+ ld a, [wcf64]
+ and $7
+ ret z ; ret nz would be more appropriate
+ ld de, SFX_GET_COIN_FROM_SLOTS
+ call PlaySFX
+ ret
+```
+
+**Fix:** Change `ret z` to `ret nz`.
+
+
+## Team Rocket battle music is not used for Executives or Scientists
+
+This is a bug with `PlayBattleMusic` in [main.asm](/main.asm):
+
+```asm
+ ; really, they should have included admins and scientists here too...
+ ld de, MUSIC_ROCKET_BATTLE
+ cp GRUNTM
+ jr z, .done
+ cp GRUNTF
+ jr z, .done
+```
+
+**Fix:**
+
+```asm
+ ld de, MUSIC_ROCKET_BATTLE
+ cp GRUNTM
+ jr z, .done
+ cp GRUNTF
+ jr z, .done
+ cp EXECUTIVEM
+ jr z, .done
+ cp EXECUTIVEF
+ jr z, .done
+ cp SCIENTIST
+ jr z, .done
+```
+
+
+## No bump noise if standing on tile `$3E`
+
+This is a bug with `DoPlayerMovement.CheckWarp` in [engine/player_movement.asm](/engine/player_movement.asm):
+
+```asm
+; Bug: Since no case is made for STANDING here, it will check
+; [.edgewarps + $ff]. This resolves to $3e at $8035a.
+; This causes wd041 to be nonzero when standing on tile $3e,
+; making bumps silent.
+
+ ld a, [WalkingDirection]
+ ; cp STANDING
+ ; jr z, .not_warp
+ ld e, a
+ ld d, 0
+ ld hl, .EdgeWarps
+ add hl, de
+ ld a, [PlayerStandingTile]
+ cp [hl]
+ jr nz, .not_warp
+
+ ld a, 1
+ ld [wd041], a
+ ld a, [WalkingDirection]
+ ; This is in the wrong place.
+ cp STANDING
+ jr z, .not_warp
+```
+
+**Fix:**
+
+```asm
+ ld a, [WalkingDirection]
+ cp STANDING
+ jr z, .not_warp
+ ld e, a
+ ld d, 0
+ ld hl, .EdgeWarps
+ add hl, de
+ ld a, [PlayerStandingTile]
+ cp [hl]
+ jr nz, .not_warp
+
+ ld a, 1
+ ld [wd041], a
+ ld a, [WalkingDirection]
+```
+
+
+## Playing Entei's Pokédex cry can distort Raikou's and Suicune's
+
+([Video](https://www.youtube.com/watch?v=z305e4sIO24))
+
+The exact cause is unknown, but a workaround exists for `DexEntryScreen_MenuActionJumptable.Cry` in [engine/pokedex.asm](/engine/pokedex.asm):
+
+```asm
+.Cry: ; 40340
+ call Pokedex_GetSelectedMon
+ ld a, [wd265]
+ call GetCryIndex
+ ld e, c
+ ld d, b
+ call PlayCryHeader
+ ret
+```
+
+**Workaround:**
+
+```asm
+.Cry: ; 40340
+ ld a, [CurPartySpecies]
+ call PlayCry
+ ret
+```
+
+
+## In-battle “`…`” ellipsis is too high
+
+This is a mistake with the “`…`” tile in [gfx/battle/hp_exp_bar_border.png](/gfx/battle/hp_exp_bar_border.png):
+
+![image](/docs/images/hp_exp_bar_border.png)
+
+**Fix:** Lower the ellipsis by two pixels:
+
+![image](/docs/images/hp_exp_bar_border_fix.png)
+
+
+## Two tiles in the `port` tileset are drawn incorrectly
+
+This is a mistake with the left-hand warp carpet corner tiles in [gfx/tilesets/port.png](/gfx/tilesets/port.png):
+
+![image](/docs/images/port.png)
+
+**Fix:** Adjust them to match the right-hand corner tiles:
+
+![image](/docs/images/port_fix.png)
+
+
+## `LoadMetatiles` wraps around past 128 blocks
+
+This bug prevents you from using blocksets with more than 128 blocks.
+
+In [home/map.asm](/home/map.asm):
+
+```asm
+ ; Set hl to the address of the current metatile data ([TilesetBlocksAddress] + (a) tiles).
+ ; This is buggy; it wraps around past 128 blocks.
+ ; To fix, uncomment the line below.
+ add a ; Comment or delete this line to fix the above bug.
+ ld l, a
+ ld h, 0
+ ; add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ ld a, [TilesetBlocksAddress]
+ add l
+ ld l, a
+ ld a, [TilesetBlocksAddress + 1]
+ adc h
+ ld h, a
+```
+
+**Fix:** Delete `add a` and uncomment `add hl, hl`.
+
+
+## Surfing directly across a map connection does not load the new map
+
+([Video](https://www.youtube.com/watch?v=XFOWvMNG-zw))
+
+*To do:* Identify specific code causing this bug and fix it.
+
+
+## `Function6ec1` does not correctly limit object movement
+
+This bug is why the Lapras in Union Cave, which uses `SPRITEMOVEDATA_LAPRAS`, is not restricted by its `1, 1` movement radius.
+
+In [engine/npc_movement.asm](/engine/npc_movement.asm):
+
+```asm
+ ld hl, OBJECT_FLAGS1
+ add hl, bc
+ bit 4, [hl] ; lost, uncomment next line to fix
+; jr nz, .resume
+```
+
+**Fix:** Uncomment `jr nz, .resume`.
+
+
+## `CheckOwnMon` only checks the first five letters of OT names
+
+([Video](https://www.youtube.com/watch?v=GVTTmReM4nQ))
+
+This bug can allow you to talk to Eusine in Celadon City and encounter Ho-Oh with only traded legendary beasts.
+
+In [engine/search.asm](/engine/search.asm):
+
+```asm
+; check OT
+; This only checks five characters, which is fine for the Japanese version,
+; but in the English version the player name is 7 characters, so this is wrong.
+
+ ld hl, PlayerName
+
+rept NAME_LENGTH_JAPANESE +- 2 ; should be PLAYER_NAME_LENGTH +- 2
+ ld a, [de]
+ cp [hl]
+ jr nz, .notfound
+ cp "@"
+ jr z, .found ; reached end of string
+ inc hl
+ inc de
+endr
+
+ ld a, [de]
+ cp [hl]
+ jr z, .found
+
+.notfound
+ pop de
+ pop hl
+ pop bc
+ and a
+ ret
+```
+
+**Fix:** Change `rept NAME_LENGTH_JAPANESE +- 2` to `rept PLAYER_NAME_LENGTH +- 2`.
+
+
+## Catching a Transformed Pokémon always catches a Ditto
+
+This bug can affect Mew or Pokémon other than Ditto that used Transform via Mirror Move or Sketch.
+
+This is a bug with `PokeBall` in [items/item_effects.asm](/items/item_effects.asm):
+
+```asm
+ ld hl, EnemySubStatus5
+ ld a, [hl]
+ push af
+ set SUBSTATUS_TRANSFORMED, [hl]
+
+; This code is buggy. Any wild Pokémon that has Transformed will be
+; caught as a Ditto, even if it was something else like Mew.
+; To fix, do not set [TempEnemyMonSpecies] to DITTO.
+ bit SUBSTATUS_TRANSFORMED, a
+ jr nz, .ditto
+ jr .not_ditto
+
+.ditto
+ ld a, DITTO
+ ld [TempEnemyMonSpecies], a
+ jr .load_data
+
+.not_ditto
+ set SUBSTATUS_TRANSFORMED, [hl]
+ ld hl, wEnemyBackupDVs
+ ld a, [EnemyMonDVs]
+ ld [hli], a
+ ld a, [EnemyMonDVs + 1]
+ ld [hl], a
+
+.load_data
+ ld a, [TempEnemyMonSpecies]
+ ld [CurPartySpecies], a
+ ld a, [EnemyMonLevel]
+ ld [CurPartyLevel], a
+ callba LoadEnemyMon
+
+ pop af
+ ld [EnemySubStatus5], a
+```
+
+**Fix:**
+
+```asm
+ ld hl, EnemySubStatus5
+ ld a, [hl]
+ push af
+ set SUBSTATUS_TRANSFORMED, [hl]
+
+ bit SUBSTATUS_TRANSFORMED, a
+ jr nz, .load_data
+
+ ld hl, wEnemyBackupDVs
+ ld a, [EnemyMonDVs]
+ ld [hli], a
+ ld a, [EnemyMonDVs + 1]
+ ld [hl], a
+
+.load_data
+ ld a, [TempEnemyMonSpecies]
+ ld [CurPartySpecies], a
+ ld a, [EnemyMonLevel]
+ ld [CurPartyLevel], a
+ callba LoadEnemyMon
+
+ pop af
+ ld [EnemySubStatus5], a
+```
+
+
+## Using a Park Ball in normal battles has a corrupt animation
+
+([Video](https://www.youtube.com/watch?v=v1ErZdLCIyU))
+
+This is a bug with `ParkBall` in [items/item_effects.asm](/items/item_effects.asm):
+
+```asm
+.room_in_party
+ xor a
+ ld [wWildMon], a
+ ld a, [CurItem]
+ cp PARK_BALL
+ call nz, ReturnToBattle_UseBall
+```
+
+**Fix:**
+
+```asm
+.room_in_party
+ xor a
+ ld [wWildMon], a
+ ld a, [BattleType]
+ cp BATTLETYPE_CONTEST
+ call nz, ReturnToBattle_UseBall
+```
+
+
+## `HELD_CATCH_CHANCE` has no effect
+
+This is a bug with `PokeBall` in [items/item_effects.asm](/items/item_effects.asm):
+
+```asm
+ ; BUG: callba overwrites a,
+ ; and GetItemHeldEffect takes b anyway.
+
+ ; This is probably the reason
+ ; the HELD_CATCH_CHANCE effect
+ ; is never used.
+
+ ; Uncomment the line below to fix.
+
+ ld a, [BattleMonItem]
+; ld b, a
+ callba GetItemHeldEffect
+ ld a, b
+ cp HELD_CATCH_CHANCE
+```
+
+**Fix:** Uncomment `ld b, a`.
+
+
+## Only the first three `EvosAttacks` evolution entries can have Stone compatibility reported correctly
+
+This is a bug with `PlacePartyMonEvoStoneCompatibility.DetermineCompatibility` in [engine/party_menu.asm](/engine/party_menu.asm):
+
+```asm
+.DetermineCompatibility: ; 50268
+ ld de, StringBuffer1
+ ld a, BANK(EvosAttacksPointers)
+ ld bc, 2
+ call FarCopyBytes
+ ld hl, StringBuffer1
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld de, StringBuffer1
+ ld a, BANK(EvosAttacks)
+ ld bc, $a
+ call FarCopyBytes
+```
+
+**Fix:** Change `ld bc, $a` to `ld bc, $10` to support up to five Stone entries.
+
+
+## `ScriptCall` can overflow `wScriptStack` and crash
+
+In [engine/scripting.asm](/engine/scripting.asm):
+
+```asm
+ScriptCall:
+; Bug: The script stack has a capacity of 5 scripts, yet there is
+; nothing to stop you from pushing a sixth script. The high part
+; of the script address can then be overwritten by modifications
+; to ScriptDelay, causing the script to return to the rst/interrupt
+; space.
+
+ push de
+ ld hl, wScriptStackSize
+ ld e, [hl]
+ inc [hl]
+ ld d, $0
+ ld hl, wScriptStack
+ add hl, de
+ add hl, de
+ add hl, de
+ pop de
+ ld a, [ScriptBank]
+ ld [hli], a
+ ld a, [ScriptPos]
+ ld [hli], a
+ ld a, [ScriptPos + 1]
+ ld [hl], a
+ ld a, b
+ ld [ScriptBank], a
+ ld a, e
+ ld [ScriptPos], a
+ ld a, d
+ ld [ScriptPos + 1], a
+ ret
+```
+
+
+## `LoadSpriteGFX` does not limit the capacity of `UsedSprites`
+
+In [engine/overworld.asm](/engine/overworld.asm):
+
+```asm
+LoadSpriteGFX: ; 14306
+; Bug: b is not preserved, so it's useless as a next count.
+; Uncomment the lines below to fix.
+
+ ld hl, UsedSprites
+ ld b, SPRITE_GFX_LIST_CAPACITY
+.loop
+ ld a, [hli]
+ and a
+ jr z, .done
+ push hl
+ call .LoadSprite
+ pop hl
+ ld [hli], a
+ dec b
+ jr nz, .loop
+
+.done
+ ret
+
+.LoadSprite:
+ ; push bc
+ call GetSprite
+ ; pop bc
+ ld a, l
+ ret
+; 1431e
+```
+
+**Fix:** Uncomment `push bc` and `pop bc`.
+
+
+## `ChooseWildEncounter` doesn't really validate the wild Pokémon species
+
+In [engine/wildmons.asm](/engine/wildmons.asm):
+
+```asm
+ChooseWildEncounter: ; 2a14f
+...
+
+ ld a, b
+ ld [CurPartyLevel], a
+ ld b, [hl]
+ ; ld a, b
+ call ValidateTempWildMonSpecies
+ jr c, .nowildbattle
+
+ ld a, b ; This is in the wrong place.
+ cp UNOWN
+ jr nz, .done
+
+...
+
+ValidateTempWildMonSpecies: ; 2a4a0
+; Due to a development oversight, this function is called with the wild Pokemon's level, not its species, in a.
+```
+
+**Fix:**
+
+```asm
+ ld a, b
+ ld [CurPartyLevel], a
+ ld b, [hl]
+ ld a, b
+ call ValidateTempWildMonSpecies
+ jr c, .nowildbattle
+
+ cp UNOWN
+ jr nz, .done
+```
+
+## `TryObjectEvent` arbitrary code execution
+
+In [engine/events.asm](/engine/events.asm):
+
+```asm
+; Bug: If IsInArray returns nc, data at bc will be executed as code.
+ push bc
+ ld de, 3
+ ld hl, .pointers
+ call IsInArray
+ jr nc, .nope_bugged
+ pop bc
+
+ inc hl
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+.nope_bugged
+ ; pop bc
+ xor a
+ ret
+```
+
+**Fix:** Uncomment `pop bc`.
+
+
+## `Special_CheckBugContestContestantFlag` can read beyond its data table
+
+In [event/bug_contest_2.asm](/event/bug_contest_2.asm):
+
+```asm
+Special_CheckBugContestContestantFlag: ; 139ed
+; Checks the flag of the Bug Catching Contestant whose index is loaded in a.
+
+; Bug: If a >= 10 when this is called, it will read beyond the table.
+
+ ld hl, BugCatchingContestantEventFlagTable
+ ld e, a
+ ld d, 0
+ add hl, de
+ add hl, de
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld b, CHECK_FLAG
+ call EventFlagAction
+ ret
+; 139fe
+
+BugCatchingContestantEventFlagTable: ; 139fe
+ dw EVENT_BUG_CATCHING_CONTESTANT_1A
+ dw EVENT_BUG_CATCHING_CONTESTANT_2A
+ dw EVENT_BUG_CATCHING_CONTESTANT_3A
+ dw EVENT_BUG_CATCHING_CONTESTANT_4A
+ dw EVENT_BUG_CATCHING_CONTESTANT_5A
+ dw EVENT_BUG_CATCHING_CONTESTANT_6A
+ dw EVENT_BUG_CATCHING_CONTESTANT_7A
+ dw EVENT_BUG_CATCHING_CONTESTANT_8A
+ dw EVENT_BUG_CATCHING_CONTESTANT_9A
+ dw EVENT_BUG_CATCHING_CONTESTANT_10A
+; 13a12
+```
+
+
+## `ClearWRAM` only clears WRAM bank 1
+
+In [home/init.asm](/home/init.asm):
+
+```asm
+ClearWRAM:: ; 25a
+; Wipe swappable WRAM banks (1-7)
+; Assumes CGB or AGB
+
+ ld a, 1
+.bank_loop
+ push af
+ ld [rSVBK], a
+ xor a
+ ld hl, $d000
+ ld bc, $1000
+ call ByteFill
+ pop af
+ inc a
+ cp 8
+ jr nc, .bank_loop ; Should be jr c
+ ret
+; 270
+```
+
+**Fix:** Change `jr nc, .bank_loop` to `jr c, .bank_loop`.
+
+
+## `GetForestTreeFrame` works, but it's still bad
+
+In [tilesets/animations.asm](/tilesets/animations.asm):
+
+```asm
+GetForestTreeFrame: ; fc54c
+; Return 0 if a is even, or 2 if odd.
+ and a
+ jr z, .even
+ cp 1
+ jr z, .odd
+ cp 2
+ jr z, .even
+ cp 3
+ jr z, .odd
+ cp 4
+ jr z, .even
+ cp 5
+ jr z, .odd
+ cp 6
+ jr z, .even
+.odd
+ ld a, 2
+ scf
+ ret
+.even
+ xor a
+ ret
+; fc56d
+```
+
+**Fix:**
+
+```asm
+GetForestTreeFrame: ; fc54c
+; Return 0 if a is even, or 2 if odd.
+ and 1
+ add a
+ ret
+; fc56d
+```
diff --git a/docs/effect_commands.md b/docs/effect_commands.md
new file mode 100644
index 000000000..3112950a9
--- /dev/null
+++ b/docs/effect_commands.md
@@ -0,0 +1,358 @@
+# Effect Commands
+
+Defined in [macros/scripts/effect_commands.asm](/macros/scripts/effect_commands.asm) and [battle/effect_command_pointers.asm:BattleCommandPointers](/battle/effect_command_pointers.asm).
+
+
+## `$01`: `checkturn`
+
+## `$02`: `checkobedience`
+
+## `$03`: `usedmovetext`
+
+## `$04`: `doturn`
+
+## `$05`: `critical`
+
+## `$06`: `damagestats`
+
+## `$07`: `stab`
+
+## `$08`: `damagevariation`
+
+## `$09`: `checkhit`
+
+## `$0A`: `lowersub`
+
+## `$0B`: `hittargetnosub`
+
+## `$0C`: `raisesub`
+
+## `$0D`: `failuretext`
+
+## `$0E`: `checkfaint`
+
+## `$0F`: `criticaltext`
+
+## `$10`: `supereffectivetext`
+
+## `$11`: `checkdestinybond`
+
+## `$12`: `buildopponentrage`
+
+## `$13`: `poisontarget`
+
+## `$14`: `sleeptarget`
+
+## `$15`: `draintarget`
+
+## `$16`: `eatdream`
+
+## `$17`: `burntarget`
+
+## `$18`: `freezetarget`
+
+## `$19`: `paralyzetarget`
+
+## `$1A`: `selfdestruct`
+
+## `$1B`: `mirrormove`
+
+## `$1C`: `statup`
+
+## `$1D`: `statdown`
+
+## `$1E`: `payday`
+
+## `$1F`: `conversion`
+
+## `$20`: `resetstats`
+
+## `$21`: `storeenergy`
+
+## `$22`: `unleashenergy`
+
+## `$23`: `forceswitch`
+
+## `$24`: `endloop`
+
+## `$25`: `flinchtarget`
+
+## `$26`: `ohko`
+
+## `$27`: `recoil`
+
+## `$28`: `mist`
+
+## `$29`: `focusenergy`
+
+## `$2A`: `confuse`
+
+## `$2B`: `confusetarget`
+
+## `$2C`: `heal`
+
+## `$2D`: `transform`
+
+## `$2E`: `screen`
+
+## `$2F`: `poison`
+
+## `$30`: `paralyze`
+
+## `$31`: `substitute`
+
+## `$32`: `rechargenextturn`
+
+## `$33`: `mimic`
+
+## `$34`: `metronome`
+
+## `$35`: `leechseed`
+
+## `$36`: `splash`
+
+## `$37`: `disable`
+
+## `$38`: `cleartext`
+
+## `$39`: `charge`
+
+## `$3A`: `checkcharge`
+
+## `$3B`: `traptarget`
+
+## `$3C`: `effect0x3c`
+
+## `$3D`: `rampage`
+
+## `$3E`: `checkrampage`
+
+## `$3F`: `constantdamage`
+
+## `$40`: `counter`
+
+## `$41`: `encore`
+
+## `$42`: `painsplit`
+
+## `$43`: `snore`
+
+## `$44`: `conversion2`
+
+## `$45`: `lockon`
+
+## `$46`: `sketch`
+
+## `$47`: `defrostopponent`
+
+## `$48`: `sleeptalk`
+
+## `$49`: `destinybond`
+
+## `$4A`: `spite`
+
+## `$4B`: `falseswipe`
+
+## `$4C`: `healbell`
+
+## `$4D`: `kingsrock`
+
+## `$4E`: `triplekick`
+
+## `$4F`: `kickcounter`
+
+## `$50`: `thief`
+
+## `$51`: `arenatrap`
+
+## `$52`: `nightmare`
+
+## `$53`: `defrost`
+
+## `$54`: `curse`
+
+## `$55`: `protect`
+
+## `$56`: `spikes`
+
+## `$57`: `foresight`
+
+## `$58`: `perishsong`
+
+## `$59`: `startsandstorm`
+
+## `$5A`: `endure`
+
+## `$5B`: `checkcurl`
+
+## `$5C`: `rolloutpower`
+
+## `$5D`: `effect0x5d`
+
+## `$5E`: `furycutter`
+
+## `$5F`: `attract`
+
+## `$60`: `happinesspower`
+
+## `$61`: `present`
+
+## `$62`: `damagecalc`
+
+## `$63`: `frustrationpower`
+
+## `$64`: `safeguard`
+
+## `$65`: `checksafeguard`
+
+## `$66`: `getmagnitude`
+
+## `$67`: `batonpass`
+
+## `$68`: `pursuit`
+
+## `$69`: `clearhazards`
+
+## `$6A`: `healmorn`
+
+## `$6B`: `healday`
+
+## `$6C`: `healnite`
+
+## `$6D`: `hiddenpower`
+
+## `$6E`: `startrain`
+
+## `$6F`: `startsun`
+
+## `$70`: `attackup`
+
+## `$71`: `defenseup`
+
+## `$72`: `speedup`
+
+## `$73`: `specialattackup`
+
+## `$74`: `specialdefenseup`
+
+## `$75`: `accuracyup`
+
+## `$76`: `evasionup`
+
+## `$77`: `attackup2`
+
+## `$78`: `defenseup2`
+
+## `$79`: `speedup2`
+
+## `$7A`: `specialattackup2`
+
+## `$7B`: `specialdefenseup2`
+
+## `$7C`: `accuracyup2`
+
+## `$7D`: `evasionup2`
+
+## `$7E`: `attackdown`
+
+## `$7F`: `defensedown`
+
+## `$80`: `speeddown`
+
+## `$81`: `specialattackdown`
+
+## `$82`: `specialdefensedown`
+
+## `$83`: `accuracydown`
+
+## `$84`: `evasiondown`
+
+## `$85`: `attackdown2`
+
+## `$86`: `defensedown2`
+
+## `$87`: `speeddown2`
+
+## `$88`: `specialattackdown2`
+
+## `$89`: `specialdefensedown2`
+
+## `$8A`: `accuracydown2`
+
+## `$8B`: `evasiondown2`
+
+## `$8C`: `statupmessage`
+
+## `$8D`: `statdownmessage`
+
+## `$8E`: `statupfailtext`
+
+## `$8F`: `statdownfailtext`
+
+## `$90`: `effectchance`
+
+## `$91`: `statdownanim`
+
+## `$92`: `statupanim`
+
+## `$93`: `switchturn`
+
+## `$94`: `fakeout`
+
+## `$95`: `bellydrum`
+
+## `$96`: `psychup`
+
+## `$97`: `rage`
+
+## `$98`: `doubleflyingdamage`
+
+## `$99`: `doubleundergrounddamage`
+
+## `$9A`: `mirrorcoat`
+
+## `$9B`: `checkfuturesight`
+
+## `$9C`: `futuresight`
+
+## `$9D`: `doubleminimizedamage`
+
+## `$9E`: `skipsuncharge`
+
+## `$9F`: `thunderaccuracy`
+
+## `$A0`: `teleport`
+
+## `$A1`: `beatup`
+
+## `$A2`: `ragedamage`
+
+## `$A3`: `resettypematchup`
+
+## `$A4`: `allstatsup`
+
+## `$A5`: `effect0xa5`
+
+## `$A6`: `raisesubnoanim`
+
+## `$A7`: `lowersubnoanim`
+
+## `$A8`: `effect0xa8`
+
+## `$A9`: `clearmissdamage`
+
+## `$AA`: `movedelay`
+
+## `$AB`: `hittarget`
+
+## `$AC`: `tristatuschance`
+
+## `$AD`: `supereffectivelooptext`
+
+## `$AE`: `startloop`
+
+## `$AF`: `curl`
+
+## `$FE`: `endturn`
+
+## `$FF`: `endmove`
diff --git a/docs/event_commands.md b/docs/event_commands.md
new file mode 100644
index 000000000..151d74b54
--- /dev/null
+++ b/docs/event_commands.md
@@ -0,0 +1,348 @@
+# Event Commands
+
+Defined in [macros/scripts/event.asm](/macros/scripts/event.asm) and [engine/scripting.asm:ScriptCommandTable](/engine/scripting.asm).
+
+
+## `$00`: `scall` *script*
+
+## `$01`: `farscall` *script*
+
+## `$02`: `ptcall` *script*
+
+## `$03`: `jump` *script*
+
+## `$04`: `farjump` *script*
+
+## `$05`: `ptjump` *script*
+
+## `$06`: `if_equal` *byte*, *script*
+
+## `$07`: `if_not_equal` *byte*, *script*
+
+## `$08`: `iffalse` *script*
+
+## `$09`: `iftrue` *script*
+
+## `$0A`: `if_greater_than` *byte*, *script*
+
+## `$0B`: `if_less_than` *byte*, *script*
+
+## `$0C`: `jumpstd` *std_script*
+
+## `$0D`: `callstd` *std_script*
+
+## `$0E`: `callasm` *asm*
+
+## `$0F`: `special` *special_pointer*
+
+## `$10`: `ptcallasm` *asm*
+
+## `$11`: `checkmapscene` *map*
+
+## `$12`: `setmapscene` *map*, *scene_id*
+
+## `$13`: `checkscene`
+
+## `$14`: `setscene` *scene_id*
+
+## `$15`: `writebyte` *value*
+
+## `$16`: `addvar` *value*
+
+## `$17`: `random` *value*
+
+## `$18`: `checkver`
+
+## `$19`: `copybytetovar` *address*
+
+## `$1A`: `copyvartobyte` *address*
+
+## `$1B`: `loadvar` *address*, *value*
+
+## `$1C`: `checkcode` *variable*
+
+## `$1D`: `writevarcode` *variable*
+
+## `$1E`: `writecode` *variable*, *value*
+
+## `$1F`: `giveitem` *item_id*[, *quantity*=1]
+
+## `$20`: `takeitem` *item_id*[, *quantity*=1]
+
+## `$21`: `checkitem` *item_id*
+
+## `$22`: `givemoney` *account*, *value*
+
+## `$23`: `takemoney` *account*, *value*
+
+## `$24`: `checkmoney` *account*, *value*
+
+## `$25`: `givecoins` *value*
+
+## `$26`: `takecoins` *value*
+
+## `$27`: `checkcoins` *value*
+
+## `$28`: `addcellnum` *contact_id*
+
+## `$29`: `delcellnum` *contact_id*
+
+## `$2A`: `checkcellnum` *contact_id*
+
+## `$2B`: `checktime` *time*
+
+- **`checkmorn`:** `checktime MORN`
+- **`checkday`:** `checktime DAY`
+- **`checknite`:** `checktime NITE`
+
+## `$2C`: `checkpoke` *mon_id*
+
+## `$2D`: `givepoke` *mon_id*, *level*[, *item*=0[, *trainer*=0, *ot_name*, *nickname*]]
+
+## `$2E`: `giveegg` *mon_id*, *level*
+
+## `$2F`: `givepokeitem` *pointer*
+
+## `$30`: `checkpokeitem` *pointer*
+
+## `$31`: `checkevent` *event_flag*
+
+## `$32`: `clearevent` *event_flag*
+
+## `$33`: `setevent` *event_flag*
+
+## `$34`: `checkflag` *engine_flag*
+
+## `$35`: `clearflag` *engine_flag*
+
+## `$36`: `setflag` *engine_flag*
+
+## `$37`: `wildon`
+
+## `$38`: `wildoff`
+
+## `$39`: `xycompare` *pointer*
+
+## `$3A`: `warpmod` *warp_id*, *map*
+
+## `$3B`: `blackoutmod` *map*
+
+## `$3C`: `warp` *map*, *x*, *y*
+
+## `$3D`: `readmoney` *account*, *memory*
+
+## `$3E`: `readcoins` *memory*
+
+## `$3F`: `RAM2MEM` *memory*
+
+## `$40`: `pokenamemem` *mon_id*, *memory*
+
+## `$41`: `itemtotext` *item_id*, *memory*
+
+## `$42`: `mapnametotext` *memory*
+
+## `$43`: `trainertotext` *trainer_id*, *trainer_group*, *memory*
+
+## `$44`: `stringtotext` *text_pointer*, *memory*
+
+## `$45`: `itemnotify`
+
+## `$46`: `pocketisfull`
+
+## `$47`: `opentext`
+
+## `$48`: `refreshscreen` *dummy*
+
+## `$49`: `closetext`
+
+## `$4A`: `loadbytec2cf` *byte*
+
+## `$4B`: `farwritetext` *text_pointer*
+
+## `$4C`: `writetext` *text_pointer*
+
+## `$4D`: `repeattext` *byte1*, *byte2*
+
+## `$4E`: `yesorno`
+
+## `$4F`: `loadmenudata` *data_pointer*
+
+## `$50`: `closewindow`
+
+## `$51`: `jumptextfaceplayer` *text_pointer*
+
+## `$52`: `farjumptext` *text_pointer*
+
+## `$53`: `jumptext` *text_pointer*
+
+## `$54`: `waitbutton`
+
+## `$55`: `buttonsound`
+
+## `$56`: `pokepic` *mon_id*
+
+## `$57`: `closepokepic`
+
+## `$58`: `_2dmenu`
+
+## `$59`: `verticalmenu`
+
+## `$5A`: `loadpikachudata`
+
+## `$5B`: `randomwildmon`
+
+## `$5C`: `loadmemtrainer`
+
+## `$5D`: `loadwildmon` *mon_id*, *level*
+
+## `$5E`: `loadtrainer` *trainer_group*, *trainer_id*
+
+## `$5F`: `startbattle`
+
+## `$60`: `reloadmapafterbattle`
+
+## `$61`: `catchtutorial` *byte*
+
+## `$62`: `trainertext` *which_text*
+
+## `$63`: `trainerflagaction` *action*
+
+## `$64`: `winlosstext` *win_text_pointer*, *loss_text_pointer*
+
+## `$65`: `scripttalkafter`
+
+## `$66`: `end_if_just_battled`
+
+## `$67`: `check_just_battled`
+
+## `$68`: `setlasttalked` *object_id*
+
+## `$69`: `applymovement` *object_id*, *data_pointer*
+
+## `$6A`: `applymovement2` *data_pointer*
+
+## `$6B`: `faceplayer`
+
+## `$6C`: `faceobject` *object1*, *object2*
+
+## `$6D`: `variablesprite` *variable_sprite_id*, *sprite_id*
+
+## `$6E`: `disappear` *object_id*
+
+## `$6F`: `appear` *object_id*
+
+## `$70`: `follow` *object2*, *object1*
+
+## `$71`: `stopfollow`
+
+## `$72`: `moveobject` *object_id*, *x*, *y*
+
+## `$73`: `writeobjectxy` *object_id*
+
+## `$74`: `loademote` *emote_id*
+
+## `$75`: `showemote` *emote_id*, *object_id*, *length*
+
+## `$76`: `spriteface` *object_id*, *facing*
+
+## `$77`: `follownotexact` *object2*, *object1*
+
+## `$78`: `earthquake` *param*
+
+## `$79`: `changemap` *bank*, *blockdata_pointer*
+
+## `$7A`: `changeblock` *x*, *y*, *block*
+
+## `$7B`: `reloadmap`
+
+## `$7C`: `reloadmappart`
+
+## `$7D`: `writecmdqueue` *queue_pointer*
+
+## `$7E`: `delcmdqueue` *byte*
+
+## `$7F`: `playmusic` *music_id*
+
+## `$80`: `encountermusic`
+
+## `$81`: `musicfadeout` *music_id*, *length*
+
+## `$82`: `playmapmusic`
+
+## `$83`: `dontrestartmapmusic`
+
+## `$84`: `cry` *mon_id*
+
+## `$85`: `playsound` *sfx_id*
+
+## `$86`: `waitsfx`
+
+## `$87`: `warpsound`
+
+## `$88`: `specialsound`
+
+## `$89`: `passtoengine` *data_pointer*
+
+## `$8A`: `newloadmap` *which_method*
+
+## `$8B`: `pause` *length*
+
+## `$8C`: `deactivatefacing` *length*
+
+## `$8D`: `priorityjump` *script*
+
+## `$8E`: `warpcheck`
+
+## `$8F`: `ptpriorityjump` *script*
+
+## `$90`: `return`
+
+## `$91`: `end`
+
+## `$92`: `reloadandreturn` *which_method*
+
+## `$93`: `end_all`
+
+## `$94`: `pokemart` *dialog_id*, *mart_id*
+
+## `$95`: `elevator` *floor_list*
+
+## `$96`: `trade` *trade_id*
+
+## `$97`: `askforphonenumber` *contact_id*
+
+## `$98`: `phonecall` *call_id*
+
+## `$99`: `hangup`
+
+## `$9A`: `describedecoration` *byte*
+
+## `$9B`: `fruittree` *tree_id*
+
+## `$9C`: `specialphonecall` *call_id*
+
+## `$9D`: `checkphonecall`
+
+## `$9E`: `verbosegiveitem` *item_id*[, *quantity*=1]
+
+## `$9F`: `verbosegiveitem2` *item_id*, *variable*
+
+## `$A0`: `swarm` *swarm_id*, *map*
+
+## `$A1`: `halloffame`
+
+## `$A2`: `credits`
+
+## `$A3`: `warpfacing` *facing*, *map*, *x*, *y*
+
+## `$A4`: `battletowertext` *memory*
+
+## `$A5`: `landmarktotext` *landmark_id*, *memory*
+
+## `$A6`: `trainerclassname` *trainer_group*, *memory*
+
+## `$A7`: `name` *type*, *id*, *memory*
+
+## `$A8`: `wait` *duration*
+
+## `$A9`: `check_save`
diff --git a/docs/images/hp_exp_bar_border.png b/docs/images/hp_exp_bar_border.png
new file mode 100644
index 000000000..9d16a042b
--- /dev/null
+++ b/docs/images/hp_exp_bar_border.png
Binary files differ
diff --git a/docs/images/hp_exp_bar_border_fix.png b/docs/images/hp_exp_bar_border_fix.png
new file mode 100644
index 000000000..02d36f8a0
--- /dev/null
+++ b/docs/images/hp_exp_bar_border_fix.png
Binary files differ
diff --git a/docs/images/port.png b/docs/images/port.png
new file mode 100644
index 000000000..8a18aad7d
--- /dev/null
+++ b/docs/images/port.png
Binary files differ
diff --git a/docs/images/port_fix.png b/docs/images/port_fix.png
new file mode 100644
index 000000000..055a377a5
--- /dev/null
+++ b/docs/images/port_fix.png
Binary files differ
diff --git a/docs/map_scripts.md b/docs/map_scripts.md
new file mode 100644
index 000000000..fa8fa308b
--- /dev/null
+++ b/docs/map_scripts.md
@@ -0,0 +1,139 @@
+# Map Scripts
+
+
+## `const_value set 2`
+
+- **`const` *`MAPNAME_OBJECTNAME`***
+
+
+## `MapName_MapScriptHeader:`
+
+
+## `.SceneScripts: db` *N*
+
+- **`scene_script` *script***
+
+
+## `.MapCallbacks: db` *N*
+
+- **`dbw` *type*, *script***
+
+Callback types:
+
+- **`MAPCALLBACK_NEWMAP`**
+
+- **`MAPCALLBACK_TILES`**
+
+- **`MAPCALLBACK_OBJECTS`**
+
+- **`MAPCALLBACK_SPRITES`**
+
+- **`MAPCALLBACK_CMDQUEUE`**
+
+ **`dbw CMDQUEUE_STONETABLE,` *table_pointer***
+
+ **`stonetable` *warp_id*, *person*, *script***
+
+ **`db -1 ; end`**
+
+
+## Event scripts
+
+[Event commands](event_commands.md)
+
+
+## Text
+
+[Text commands](text_commands.md)
+
+
+## Movement data
+
+[Movement commands](movement_commands.md)
+
+
+## `MapName_MapEventHeader:`
+
+ ; filler
+ db 0, 0
+
+
+## `.Warps: db` *N*
+
+- **`warp_def` *y*, *x*, *warp_id*, *map***
+
+
+## `.CoordEvents: db` *N*
+
+- **`coord_event` *scene id*, *y*, *x*, *script***
+
+
+## `.BGEvents: db` *N*
+
+- **`bg_event` *y*, *x*, *type*, *script***
+
+BG event types:
+
+- **`BGEVENT_READ`**
+
+- **`BGEVENT_UP/DOWN/LEFT/RIGHT`**
+
+- **`BGEVENT_IFSET/IFNOTSET`**
+
+ **`dw` *event_flag*, *script***
+
+- **`BGEVENT_ITEM`**
+
+ **`dwb` *event_flag*, *item_id***
+
+- **`BGEVENT_COPY`**
+
+## `.ObjectEvents: db` *N*
+
+- **`object_event` *sprite*, *y*, *x*, *movement*, *ry*, *rx*, *h1*, *h2*, *palette*, *type*, *range*, *script*, *event_flag***
+
+Movement types:
+
+- **`SPRITEMOVEDATA_ITEM_TREE`**
+
+- **`SPRITEMOVEDATA_WANDER`**
+
+- **`SPRITEMOVEDATA_SPINRANDOM_SLOW`**
+
+- **`SPRITEMOVEDATA_WALK_UP_DOWN`**
+
+- **`SPRITEMOVEDATA_WALK_LEFT_RIGHT`**
+
+- **`SPRITEMOVEDATA_STANDING_UP/DOWN/LEFT/RIGHT`**
+
+- **`SPRITEMOVEDATA_SPINRANDOM_FAST`**
+
+- **`SPRITEMOVEDATA_SNORLAX`**
+
+- **`SPRITEMOVEDATA_POKEMON`**
+
+- **`SPRITEMOVEDATA_SUDOWOODO`**
+
+- **`SPRITEMOVEDATA_SMASHABLE_ROCK`**
+
+- **`SPRITEMOVEDATA_STRENGTH_BOULDER`**
+
+- **`SPRITEMOVEDATA_SPINCOUNTERCLOCKWISE`**
+
+- **`SPRITEMOVEDATA_SPINCLOCKWISE`**
+
+- **`SPRITEMOVEDATA_BIGDOLL`**
+
+- **`SPRITEMOVEDATA_LAPRAS`**
+
+Object types:
+
+- **`OBJECTTYPE_SCRIPT`**
+
+- **`OBJECTTYPE_ITEMBALL`**
+
+ **`itemball` *item_id***
+
+- **`OBJECTTYPE_TRAINER`**
+
+ **`trainer` *event_flag*, *group_id*, *trainer_id*, *seen_text*, *beaten_text*, *loss_text*, *script***
diff --git a/docs/move_anim_commands.md b/docs/move_anim_commands.md
new file mode 100644
index 000000000..9772550c3
--- /dev/null
+++ b/docs/move_anim_commands.md
@@ -0,0 +1,102 @@
+# Move Animation Commands
+
+Defined in [macros/scripts/move_anim.asm](/macros/scripts/move_anim.asm) and [battle/anims.asm:BattleAnimations](/battle/anims.asm).
+
+
+## `$00`−`$EF`: `anim_wait` *length*
+
+## `$D0`: `anim_obj` *object*, *x*, *y*, *param*
+
+## `$D1`: `anim_1gfx` *gfx*
+
+## `$D2`: `anim_2gfx` *gfx1*, *gfx2*
+
+## `$D3`: `anim_3gfx` *gfx1*, *gfx2*, *gfx3*
+
+## `$D4`: `anim_4gfx` *gfx1*, *gfx2*, *gfx3*, *gfx4*
+
+## `$D5`: `anim_5gfx` *gfx1*, *gfx2*, *gfx3*, *gfx4*, *gfx5*
+
+## `$D6`: `anim_incobj` *id*
+
+## `$D7`: `anim_setobj` *id*, *object*
+
+## `$D8`: `anim_incbgeffect` *effect*
+
+## `$D9`: `anim_enemyfeetobj`
+
+## `$DA`: `anim_playerheadobj`
+
+## `$DB`: `anim_checkpokeball`
+
+## `$DC`: `anim_transform`
+
+## `$DD`: `anim_raisesub`
+
+## `$DE`: `anim_dropsub`
+
+## `$DF`: `anim_resetobp0`
+
+## `$E0`: `anim_sound` *duration*, *tracks*, *id*
+
+## `$E1`: `anim_cry` *pitch*
+
+## `$E2`: `anim_minimizeopp`
+
+## `$E3`: `anim_oamon`
+
+## `$E4`: `anim_oamoff`
+
+## `$E5`: `anim_clearobjs`
+
+## `$E6`: `anim_beatup`
+
+## `$E7`: `anim_0xe7`
+
+## `$E8`: `anim_updateactorpic`
+
+## `$E9`: `anim_minimize`
+
+## `$EA`: `anim_0xea`
+
+## `$EB`: `anim_0xeb`
+
+## `$EC`: `anim_0xec`
+
+## `$ED`: `anim_0xed`
+
+## `$EE`: `anim_if_param_and` *value*, *address*
+
+## `$EF`: `anim_jumpuntil` *address*
+
+## `$F0`: `anim_bgeffect` *effect*, *unknown1*, *unknown2*, *unknown3*
+
+## `$F1`: `anim_bgp` *colors*
+
+## `$F2`: `anim_obp0` *colors*
+
+## `$F3`: `anim_obp1` *colors*
+
+## `$F4`: `anim_clearsprites`
+
+## `$F5`: `anim_0xf5`
+
+## `$F6`: `anim_0xf6`
+
+## `$F7`: `anim_0xf7`
+
+## `$F8`: `anim_if_param_equal` *value*, *address*
+
+## `$F9`: `anim_setvar` *value*
+
+## `$FA`: `anim_incvar`
+
+## `$FB`: `anim_if_var_equal` *value*, *address*
+
+## `$FC`: `anim_jump` *address*
+
+## `$FD`: `anim_loop` *count*, *address*
+
+## `$FE`: `anim_call` *address*
+
+## `$FF`: `anim_ret`
diff --git a/docs/movement_commands.md b/docs/movement_commands.md
new file mode 100644
index 000000000..56b55f53e
--- /dev/null
+++ b/docs/movement_commands.md
@@ -0,0 +1,84 @@
+# Movement Commands
+
+Defined in [macros/scripts/movement.asm](/macros/scripts/movement.asm) and [engine/movement.asm:MovementPointers](/engine/movement.asm).
+
+
+## `$00`−`$03`: `turn_head` *direction*
+
+## `$04`−`$07`: `turn_step` *direction*
+
+## `$08`−`$0B`: `slow_step` *direction*
+
+## `$0C`−`$0F`: `step` *direction*
+
+## `$10`−`$13`: `big_step` *direction*
+
+## `$14`−`$17`: `slow_slide_step` *direction*
+
+## `$18`−`$1B`: `slide_step` *direction*
+
+## `$1C`−`$1F`: `fast_slide_step` *direction*
+
+## `$20`−`$23`: `turn_away` *direction*
+
+## `$24`−`$27`: `turn_in` *direction*
+
+## `$28`−`$2B`: `turn_waterfall` *direction*
+
+## `$2C`−`$2F`: `slow_jump_step` *direction*
+
+## `$30`−`$33`: `jump_step` *direction*
+
+## `$34`−`$37`: `fast_jump_step` *direction*
+
+## `$38`: `remove_sliding`
+
+## `$39`: `set_sliding`
+
+## `$3A`: `remove_fixed_facing`
+
+## `$3B`: `fix_facing`
+
+## `$3C`: `show_object`
+
+## `$3D`: `hide_object`
+
+## `$3E`−`$46`: `step_sleep` *length*
+
+## `$47`: `step_end`
+
+## `$48`: `step_48` *param*
+
+## `$49`: `remove_object`
+
+## `$4A`: `step_loop`
+
+## `$4B`: `step_4b`
+
+## `$4C`: `teleport_from`
+
+## `$4D`: `teleport_to`
+
+## `$4E`: `skyfall`
+
+## `$4F`: `step_dig` *length*
+
+## `$50`: `step_bump`
+
+## `$51`: `fish_got_bite`
+
+## `$52`: `fish_cast_rod`
+
+## `$53`: `hide_emote`
+
+## `$54`: `show_emote`
+
+## `$55`: `step_shake` *displacement*
+
+## `$56`: `tree_shake`
+
+## `$57`: `rock_smash` *length*
+
+## `$58`: `return_dig` *length*
+
+## `$59`: `skyfall_top`
diff --git a/docs/music_commands.md b/docs/music_commands.md
new file mode 100644
index 000000000..bf6359068
--- /dev/null
+++ b/docs/music_commands.md
@@ -0,0 +1,96 @@
+# Music Commands
+
+Defined in [macros/scripts/audio.asm](/macros/scripts/audio.asm) and [audio/engine.asm:MusicCommands](/audio/engine.asm).
+
+
+## `musicheader` *n*, *index*, *address*
+
+## `cry_header` *n*, *index*, *address*
+
+## `note` *pitch*, *octave*
+
+## `sound` *pitch*, *octave*, *intensity*, *frequency*
+
+## `noise` *pitch*, *duration*, *intensity*, *frequency*
+
+## `$D0`−`$D7`: `octave` *n*
+
+## `$D8`: `notetype` *length*[, *intensity*]
+
+## `$D9`: `pitchoffset` *octave*, *key*
+
+## `$DA`: `tempo` *tempo*
+
+## `$DB`: `dutycycle` *duty_cycle*
+
+## `$DC`: `intensity` *intensity*
+
+## `$DD`: `soundinput` *input*
+
+## `$DE`: `sound_duty` *a*, *b*, *c*, *d*
+
+## `$DF`: `togglesfx`
+
+## `$E0`: `slidepitchto` *duration*, *octave*, *pitch*
+
+## `$E1`: `vibrato` *delay*, *extent*
+
+## `$E2`: `unknownmusic0xe2` *unknown*
+
+## `$E3`: `togglenoise` *id*
+
+## `$E4`: `panning` *tracks*
+
+## `$E5`: `volume` *volume*
+
+## `$E6`: `tone` *tone*
+
+## `$E7`: `unknownmusic0xe7` *unknown*
+
+## `$E8`: `unknownmusic0xe8` *unknown*
+
+## `$E9`: `tempo_relative` *value*
+
+## `$EA`: `restartchannel` *address*
+
+## `$EB`: `newsong` *id*
+
+## `$EC`: `sfxpriorityon`
+
+## `$ED`: `sfxpriorityoff`
+
+## `$EE`: `unknownmusic0xee` *address*
+
+## `$EF`: `stereopanning` *tracks*
+
+## `$F0`: `sfxtogglenoise` *id*
+
+## `$F1`: `music0xf1`
+
+## `$F2`: `music0xf2`
+
+## `$F3`: `music0xf3`
+
+## `$F4`: `music0xf4`
+
+## `$F5`: `music0xf5`
+
+## `$F6`: `music0xf6`
+
+## `$F7`: `music0xf7`
+
+## `$F8`: `music0xf8`
+
+## `$F9`: `unknownmusic0xf9`
+
+## `$FA`: `setcondition` *condition*
+
+## `$FB`: `jumpif` *condition*, *address*
+
+## `$FC`: `jumpchannel` *address*
+
+## `$FD`: `loopchannel` *count*, *address*
+
+## `$FE`: `callchannel` *address*
+
+## `$FF`: `endchannel`
diff --git a/docs/pic_animations.md b/docs/pic_animations.md
new file mode 100644
index 000000000..57c7f151a
--- /dev/null
+++ b/docs/pic_animations.md
@@ -0,0 +1,31 @@
+# Pic Animations
+
+Defined in [macros/scripts/gfx_anim.asm](/macros/scripts/gfx_anim.asm) and [battle/anim_commands.asm:BattleAnimCommands](/battle/anim_commands.asm).
+
+
+Pic animations are assembled in 3 parts:
+
+- Top-level animations:
+ - `frame` *#*, *duration*: Frame 0 is the original pic (no change)
+ - `setrepeat` *#*: Sets the number of times to repeat
+ - `dorepeat` *#*: Repeats from command *#* (starting from 0)
+ - `end`
+
+- Bitmasks:
+ Layered over the pic to designate affected tiles
+
+- Frame definitions:
+ first byte is the bitmask used for this frame
+ following bytes are tile ids mapped to each bit in the mask
+
+Animation data is in these files:
+
+- gfx/pics/anims.asm:
+ Main animations (played everywhere)
+
+- gfx/pics/extras.asm:
+ Extra animations, appended to the main animation.
+ Used in the status screen (blinking, tail wags etc.)
+
+- gfx/pics/unown_anims.asm and gfx/pics/unown_extras.asm:
+ Unown has its own animation data despite having an entry in the main tables.
diff --git a/docs/text_commands.md b/docs/text_commands.md
new file mode 100644
index 000000000..cf3a53327
--- /dev/null
+++ b/docs/text_commands.md
@@ -0,0 +1,164 @@
+# Text Commands
+
+Defined in [macros/scripts/text.asm](/macros/scripts/text.asm) and [home/text.asm:TextCommands](/home/text.asm).
+
+
+## `$00`: `text` *text*
+
+Start writing text until `"@"`.
+
+
+## `$4E`: `next` *text*
+
+Move a line down.
+
+
+## `$4F`: `line` *text*
+
+Start writing at the bottom line.
+
+
+## `$50`: `page` *text*
+
+Start a new Pokédex page.
+
+
+## `$51`: `para` *text*
+
+Start a new paragraph.
+
+
+## `$55`: `cont` *text*
+
+Scroll to the next line.
+
+
+## `$57`: `done`
+
+End a text box.
+
+
+## `$58`: `prompt`
+
+Prompt the player to end a text box (initiating some other event).
+
+
+## `$01`: `text_from_ram` *address*
+
+Write text from a RAM address.
+
+
+## `$02`: `text_bcd` *address*, *flags*
+
+Write [BCD](bcd) from an address, typically RAM.
+
+[bcd]: https://en.wikipedia.org/wiki/Binary-coded_decimal
+
+
+## `$03`: `text_move` *address*
+
+Move to a new tile.
+
+
+## `$04`: `text_box` *address*, *height*, *width*
+
+Draw a box.
+
+
+## `$05`: `text_low`
+
+Write text at (1, 16).
+
+
+## `$06`: `text_waitbutton`
+
+Wait for button press; show arrow.
+
+
+## `$07`: `text_scroll`
+
+Pushes text up two lines and sets the `bc` cursor to the border tile below the
+first character column of the text box.
+
+
+## `$08`: `start_asm`
+
+Start interpreting assembly code.
+
+
+## `$09`: `deciram` *address*, *bytes*, *digits*
+
+Read *bytes* bytes from *address* and print them as a *digits*-digit number.
+
+
+## `$0A`: `interpret_data`
+
+Exit.
+
+
+## `$0B`: `sound_dex_fanfare_50_79`
+
+Play `SFX_DEX_FANFARE_50_79`.
+
+
+## `$0C`: `limited_interpret_data` *number*
+
+Print *number* `"…"`s.
+
+
+## `$0D`: `link_wait_button`
+
+Wait for button press; show arrow.
+
+
+## `$0E`: `sound_dex_fanfare_20_49`
+
+Play `SFX_DEX_FANFARE_20_49`.
+
+
+## `$0F`: `sound_item`
+
+Play `SFX_ITEM`.
+
+
+## `$10`: `sound_caught_mon`
+
+Play `SFX_CAUGHT_MON`.
+
+
+## `$11`: `sound_dex_fanfare_80_109`
+
+Play `SFX_DEX_FANFARE_80_109`.
+
+
+## `$12`: `sound_fanfare`
+
+Play `SFX_FANFARE`.
+
+
+## `$13`: `sound_slot_machine_start`
+
+Play `SFX_SLOT_MACHINE_START`.
+
+
+## `$14`: `text_buffer` *id*
+
+Write text from one of the following addresses (listed in `StringBufferPointers`):
+
+0. `StringBuffer3`
+1. `StringBuffer4`
+2. `StringBuffer5`
+3. `StringBuffer2`
+4. `StringBuffer1`
+5. `EnemyMonNick`
+6. `BattleMonNick`
+
+
+## `$15`: `current_day`
+
+Print the weekday.
+
+
+## `$16`: `text_jump` *address*
+
+Write text from a different bank.