shithub: pokered

Download patch

ref: 95ec2cf039f0efdc6dadfb6fe766ace231a1b6b1
parent: e1f6bb53939be34f55e05cbbd19cd758936b3422
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Thu Mar 25 12:33:05 EDT 2021

Verify data table and name list sizes with assertion macros

Fixes #312

--- a/audio/notes.asm
+++ b/audio/notes.asm
@@ -1,5 +1,6 @@
 ; This file is INCLUDEd three times, once in each audio engine.
 
+	table_width 2
 	dw $F82C ; C_
 	dw $F89D ; C#
 	dw $F907 ; D_
@@ -12,3 +13,4 @@
 	dw $FB58 ; A_
 	dw $FB9B ; A#
 	dw $FBDA ; B_
+	assert_table_length NUM_NOTES
--- a/constants.asm
+++ b/constants.asm
@@ -9,16 +9,16 @@
 INCLUDE "constants/input_constants.asm"
 INCLUDE "constants/serial_constants.asm"
 INCLUDE "constants/script_constants.asm"
-INCLUDE "constants/pokemon_constants.asm"
-INCLUDE "constants/pokedex_constants.asm"
-INCLUDE "constants/pokemon_data_constants.asm"
-INCLUDE "constants/trainer_constants.asm"
 INCLUDE "constants/type_constants.asm"
+INCLUDE "constants/battle_constants.asm"
 INCLUDE "constants/move_constants.asm"
 INCLUDE "constants/move_animation_constants.asm"
 INCLUDE "constants/move_effect_constants.asm"
-INCLUDE "constants/battle_constants.asm"
 INCLUDE "constants/item_constants.asm"
+INCLUDE "constants/pokemon_constants.asm"
+INCLUDE "constants/pokedex_constants.asm"
+INCLUDE "constants/pokemon_data_constants.asm"
+INCLUDE "constants/trainer_constants.asm"
 INCLUDE "constants/icon_constants.asm"
 INCLUDE "constants/sprite_constants.asm"
 INCLUDE "constants/sprite_data_constants.asm"
--- a/constants/audio_constants.asm
+++ b/constants/audio_constants.asm
@@ -13,6 +13,7 @@
 	const A_ ; 9
 	const A# ; A
 	const B_ ; B
+NUM_NOTES EQU const_value
 
 ; channel
 ; Audio[1|2|3]_HWChannelBaseAddresses, Audio[1|2|3]_HWChannelDisableMasks,
--- a/constants/battle_constants.asm
+++ b/constants/battle_constants.asm
@@ -1,9 +1,36 @@
 MAX_LEVEL EQU 100
 
-NUM_MOVES     EQU 4
-NUM_STATS     EQU 5
-NUM_STAT_MODS EQU 8
-NUM_DVS       EQU 2
+NUM_MOVES EQU 4
+
+; VitaminStats indexes (see data/battle/stat_names.asm)
+	const_def
+	const STAT_HEALTH
+	const STAT_ATTACK
+	const STAT_DEFENSE
+	const STAT_SPEED
+	const STAT_SPECIAL
+NUM_STATS EQU const_value
+
+; StatModTextStrings indexes (see data/battle/stat_mod_names.asm)
+	const_def
+	const MOD_ATTACK
+	const MOD_DEFENSE
+	const MOD_SPEED
+	const MOD_SPECIAL
+	const MOD_ACCURACY
+	const MOD_EVASION
+	const_skip 2
+NUM_STAT_MODS EQU const_value
+
+; Moves struct fields (see data/moves/moves.asm)
+rsreset
+MOVE_ANIM   rb
+MOVE_EFFECT rb
+MOVE_POWER  rb
+MOVE_TYPE   rb
+MOVE_ACC    rb
+MOVE_PP     rb
+MOVE_LENGTH EQU _RS
 
 ; D733 flags
 BIT_TEST_BATTLE EQU 0
--- a/constants/credits_constants.asm
+++ b/constants/credits_constants.asm
@@ -64,6 +64,7 @@
 	const CRED_FUKUI          ; $3D
 	const CRED_CLUB           ; $3E
 	const CRED_PAAD           ; $3F
+NUM_CRED_STRINGS EQU const_value
 
 	const_def -1, -1
 	const CRED_TEXT_FADE_MON ; $FF
--- a/constants/gfx_constants.asm
+++ b/constants/gfx_constants.asm
@@ -37,3 +37,4 @@
 	const TILEMAP_GENGAR_INTRO_3
 	const TILEMAP_GAME_BOY
 	const TILEMAP_LINK_CABLE
+NUM_TILEMAPS EQU const_value
--- a/constants/hide_show_constants.asm
+++ b/constants/hide_show_constants.asm
@@ -237,3 +237,4 @@
 	const HS_SEAFOAM_ISLANDS_B4F_BOULDER_1 ; E1
 	const HS_SEAFOAM_ISLANDS_B4F_BOULDER_2 ; E2
 	const HS_ARTICUNO                      ; E3 X
+NUM_HS_OBJECTS EQU const_value
--- a/constants/item_constants.asm
+++ b/constants/item_constants.asm
@@ -92,6 +92,9 @@
 	const MAX_ETHER     ; $51
 	const ELIXER        ; $52
 	const MAX_ELIXER    ; $53
+NUM_ITEMS EQU const_value - 1
+
+; elevator floors use item IDs
 	const FLOOR_B2F     ; $54
 	const FLOOR_B1F     ; $55
 	const FLOOR_1F      ; $56
@@ -106,6 +109,7 @@
 	const FLOOR_10F     ; $5F
 	const FLOOR_11F     ; $60
 	const FLOOR_B4F     ; $61
+NUM_FLOORS EQU const_value - 1 - NUM_ITEMS
 
 	const_next $C4
 
--- a/constants/map_constants.asm
+++ b/constants/map_constants.asm
@@ -266,6 +266,7 @@
 	mapconst LORELEIS_ROOM,                  6,  5 ; $F5
 	mapconst BRUNOS_ROOM,                    6,  5 ; $F6
 	mapconst AGATHAS_ROOM,                   6,  5 ; $F7
+NUM_MAPS EQU const_value
 
 ; Indoor maps, such as houses, use this as the Map ID in their exit warps
 ; This map ID takes the player back to the last outdoor map they were on, stored in wLastMap
--- a/constants/menu_constants.asm
+++ b/constants/menu_constants.asm
@@ -42,6 +42,7 @@
 	const TRADE_CANCEL_MENU ; 5
 	const HEAL_CANCEL_MENU  ; 6
 	const NO_YES_MENU       ; 7
+NUM_TWO_OPTION_MENUS EQU const_value
 
 ; menu exit method constants for list menus and the buy/sell/quit menu
 CHOSE_MENU_ITEM   EQU 1 ; pressed A
--- a/constants/move_animation_constants.asm
+++ b/constants/move_animation_constants.asm
@@ -132,6 +132,7 @@
 	const SUBANIM_53
 	const SUBANIM_54
 	const SUBANIM_55
+NUM_SUBANIMS EQU const_value
 
 ; types of subanimations
 	const_def
@@ -267,6 +268,7 @@
 	const FRAMEBLOCK_77
 	const FRAMEBLOCK_78
 	const FRAMEBLOCK_79
+NUM_FRAMEBLOCKS EQU const_value
 
 ; base coordinates that are part of subanimations
 ; FrameBlockBaseCoords indexes (see data/battle_anims/base_coords.asm)
@@ -448,6 +450,7 @@
 	const BASECOORD_AE
 	const BASECOORD_AF
 	const BASECOORD_B0
+NUM_BASECOORDS EQU const_value
 
 ; frame block modes that are part of subanimations
 	const_def
--- a/constants/move_constants.asm
+++ b/constants/move_constants.asm
@@ -170,11 +170,9 @@
 	const SUPER_FANG   ; a2
 	const SLASH        ; a3
 	const SUBSTITUTE   ; a4
-
+	const STRUGGLE     ; a5
 NUM_ATTACKS EQU const_value - 1
 
-	const STRUGGLE     ; a5
-
 	; Moves do double duty as animation identifiers.
 
 	const SHOWPIC_ANIM
@@ -214,3 +212,5 @@
 	const HIDEPIC_ANIM ; monster disappears
 	const ROCK_ANIM ; throw rock
 	const BAIT_ANIM ; throw bait
+
+NUM_ATTACK_ANIMS EQU const_value - 1
--- a/constants/move_effect_constants.asm
+++ b/constants/move_effect_constants.asm
@@ -91,3 +91,4 @@
 	const LEECH_SEED_EFFECT          ; $54
 	const SPLASH_EFFECT              ; $55
 	const DISABLE_EFFECT             ; $56
+NUM_MOVE_EFFECTS EQU const_value - 1
--- a/constants/palette_constants.asm
+++ b/constants/palette_constants.asm
@@ -70,3 +70,4 @@
 	const PAL_BADGE     ; $22
 	const PAL_CAVE      ; $23
 	const PAL_GAMEFREAK ; $24
+NUM_SGB_PALS EQU const_value
--- a/constants/pokemon_data_constants.asm
+++ b/constants/pokemon_data_constants.asm
@@ -1,3 +1,29 @@
+; base data struct members (see data/pokemon/base_stats/*.asm)
+rsreset
+BASE_DEX_NO      rb
+BASE_STATS       rb NUM_STATS
+rsset BASE_STATS
+BASE_HP          rb
+BASE_ATK         rb
+BASE_DEF         rb
+BASE_SPD         rb
+BASE_SPC         rb
+BASE_TYPES       rw
+rsset BASE_TYPES
+BASE_TYPE_1      rb
+BASE_TYPE_2      rb
+BASE_CATCH_RATE  rb
+BASE_EXP         rb
+BASE_PIC_SIZE    rb
+BASE_FRONTPIC    rw
+BASE_BACKPIC     rw
+BASE_MOVES       rb NUM_MOVES
+BASE_GROWTH_RATE rb
+BASE_TMHM        rb (NUM_TM_HM + 7) / 8
+                 rb_skip
+BASE_DATA_SIZE EQU _RS
+
+
 PARTY_LENGTH EQU 6
 
 MONS_PER_BOX EQU 20
@@ -39,3 +65,8 @@
 	const GROWTH_MEDIUM_SLOW
 	const GROWTH_FAST
 	const GROWTH_SLOW
+NUM_GROWTH_RATES EQU const_value
+
+; wild data (see data/wild/maps/*.asm)
+NUM_WILDMONS EQU 10
+WILDDATA_LENGTH EQU 1 + NUM_WILDMONS * 2
--- a/constants/script_constants.asm
+++ b/constants/script_constants.asm
@@ -30,6 +30,7 @@
 	const TRADE_FOR_DORIS
 	const TRADE_FOR_CRINKLES
 	const TRADE_FOR_SPOT
+NUM_NPC_TRADES EQU const_value
 
 ; in game trade dialog sets
 ; InGameTradeTextPointers indexes (see engine/events/in_game_trades.asm)
--- a/constants/sprite_constants.asm
+++ b/constants/sprite_constants.asm
@@ -75,3 +75,4 @@
 	const SPRITE_UNUSED_GAMBLER_ASLEEP_1 ; $46
 	const SPRITE_UNUSED_GAMBLER_ASLEEP_2 ; $47
 	const SPRITE_GAMBLER_ASLEEP          ; $48
+NUM_SPRITES EQU const_value - 1
--- a/constants/tileset_constants.asm
+++ b/constants/tileset_constants.asm
@@ -25,3 +25,4 @@
 	const CLUB         ; 21
 	const FACILITY     ; 22
 	const PLATEAU      ; 23
+NUM_TILESETS EQU const_value
--- a/constants/trainer_constants.asm
+++ b/constants/trainer_constants.asm
@@ -62,3 +62,4 @@
 	trainer_const CHANNELER      ; $2D
 	trainer_const AGATHA         ; $2E
 	trainer_const LANCE          ; $2F
+NUM_TRAINERS EQU const_value - 1
--- a/constants/type_constants.asm
+++ b/constants/type_constants.asm
@@ -22,3 +22,5 @@
 	const PSYCHIC_TYPE ; $18
 	const ICE          ; $19
 	const DRAGON       ; $1A
+
+NUM_TYPES EQU const_value
--- /dev/null
+++ b/data/battle/stat_mod_names.asm
@@ -1,0 +1,17 @@
+; Stats that move effects can raise or lower
+; The relevant move effect IDs correspond to the stats
+
+StatModTextStrings:
+	list_start StatModTextStrings
+	li "ATTACK"
+	li "DEFENSE"
+	li "SPEED"
+	li "SPECIAL"
+	assert_list_length SPECIAL_DOWN_SIDE_EFFECT - ATTACK_DOWN_SIDE_EFFECT + 1
+	li "ACCURACY"
+	li "EVADE"
+	assert_list_length NUM_STAT_MODS - 2 ; two bytes are unused
+	assert_list_length EVASION_UP1_EFFECT - ATTACK_UP1_EFFECT + 1
+	assert_list_length EVASION_DOWN1_EFFECT - ATTACK_DOWN1_EFFECT + 1
+	assert_list_length EVASION_UP2_EFFECT - ATTACK_UP2_EFFECT + 1
+	assert_list_length EVASION_DOWN2_EFFECT - ATTACK_DOWN2_EFFECT + 1
--- a/data/battle/stat_names.asm
+++ b/data/battle/stat_names.asm
@@ -1,7 +1,10 @@
-StatsTextStrings:
-	db "ATTACK@"
-	db "DEFENSE@"
-	db "SPEED@"
-	db "SPECIAL@"
-	db "ACCURACY@"
-	db "EVADE@"
+; Stats that vitamins can raise or lower
+
+VitaminStats:
+	list_start VitaminStats
+	li "HEALTH"
+	li "ATTACK"
+	li "DEFENSE"
+	li "SPEED"
+	li "SPECIAL"
+	assert_list_length NUM_STATS
--- a/data/battle_anims/base_coords.asm
+++ b/data/battle_anims/base_coords.asm
@@ -1,4 +1,5 @@
 FrameBlockBaseCoords:
+	table_width 2, FrameBlockBaseCoords
 	db $10, $68 ; BASECOORD_00
 	db $10, $70 ; BASECOORD_01
 	db $10, $78 ; BASECOORD_02
@@ -176,3 +177,4 @@
 	db $18, $4C ; BASECOORD_AE
 	db $1C, $48 ; BASECOORD_AF
 	db $48, $28 ; BASECOORD_B0
+	assert_table_length NUM_BASECOORDS
--- a/data/battle_anims/frame_blocks.asm
+++ b/data/battle_anims/frame_blocks.asm
@@ -1,4 +1,5 @@
 FrameBlockPointers:
+	table_width 2, FrameBlockPointers
 	dw FrameBlock00
 	dw FrameBlock01
 	dw FrameBlock02
@@ -121,6 +122,7 @@
 	dw FrameBlock77
 	dw FrameBlock78
 	dw FrameBlock79
+	assert_table_length NUM_FRAMEBLOCKS
 
 FrameBlock01:
 	db 9 ; #
--- a/data/battle_anims/subanimations.asm
+++ b/data/battle_anims/subanimations.asm
@@ -1,4 +1,5 @@
 SubanimationPointers:
+	table_width 2, SubanimationPointers
 	dw Subanimation00
 	dw Subanimation01
 	dw Subanimation02
@@ -85,6 +86,7 @@
 	dw Subanimation53
 	dw Subanimation54
 	dw Subanimation55
+	assert_table_length NUM_SUBANIMS
 
 ; format:
 ; subanim type, count
--- a/data/credits/credits_mons.asm
+++ b/data/credits/credits_mons.asm
@@ -1,4 +1,5 @@
 CreditsMons:
+; one entry per CRED_TEXT_MON or CRED_TEXT_FADE_MON in CreditsOrder
 	db VENUSAUR
 	db ARBOK
 	db RHYHORN
--- a/data/credits/credits_text.asm
+++ b/data/credits/credits_text.asm
@@ -1,5 +1,6 @@
 CreditsTextPointers:
 ; entries correspond to CRED_* constants
+	table_width 2, CreditsTextPointers
 	dw CredVersion
 	dw CredTajiri
 	dw CredTaOota
@@ -64,6 +65,7 @@
 	dw CredFukui
 	dw CredClub
 	dw CredPAAD
+	assert_table_length NUM_CRED_STRINGS
 
 CredVersion: ; this 1 byte difference makes all bank addresses offset by 1 in the blue version
 IF DEF(_RED)
--- a/data/events/trades.asm
+++ b/data/events/trades.asm
@@ -1,5 +1,6 @@
 TradeMons:
 ; entries correspond to TRADE_FOR_* constants
+	table_width 3 + NAME_LENGTH, TradeMons
 	; give mon, get mon, dialog id, nickname
 	db NIDORINO,   NIDORINA,  TRADE_DIALOGSET_CASUAL, "TERRY@@@@@@"
 	db ABRA,       MR_MIME,   TRADE_DIALOGSET_CASUAL, "MARCEL@@@@@"
@@ -11,3 +12,4 @@
 	db RAICHU,     ELECTRODE, TRADE_DIALOGSET_POLITE, "DORIS@@@@@@"
 	db VENONAT,    TANGELA,   TRADE_DIALOGSET_HAPPY,  "CRINKLES@@@"
 	db NIDORAN_M,  NIDORAN_F, TRADE_DIALOGSET_HAPPY,  "SPOT@@@@@@@"
+	assert_table_length NUM_NPC_TRADES
--- a/data/growth_rates.asm
+++ b/data/growth_rates.asm
@@ -11,6 +11,7 @@
 
 GrowthRateTable:
 ; entries correspond to GROWTH_* (see constants/pokemon_data_constants.asm)
+	table_width 4, GrowthRateTable
 	growth_rate 1, 1,   0,   0,   0 ; Medium Fast
 	growth_rate 3, 4,  10,   0,  30 ; Slightly Fast
 	growth_rate 3, 4,  20,   0,  70 ; Slightly Slow
@@ -17,3 +18,4 @@
 	growth_rate 6, 5, -15, 100, 140 ; Medium Slow
 	growth_rate 4, 5,   0,   0,   0 ; Fast
 	growth_rate 5, 4,   0,   0,   0 ; Slow
+	assert_table_length NUM_GROWTH_RATES
--- a/data/items/key_items.asm
+++ b/data/items/key_items.asm
@@ -17,6 +17,7 @@
 ENDM
 
 KeyItemBitfield:
+	table_width 1, KeyItemBitfield
 	key_item_bits \
 	FALSE, \ ; MASTER_BALL
 	FALSE, \ ; ULTRA_BALL
@@ -101,3 +102,4 @@
 	FALSE, \ ; MAX_ETHER
 	FALSE, \ ; ELIXER
 	FALSE    ; MAX_ELIXER
+	assert_table_length (NUM_ITEMS + 7) / 8
--- a/data/items/names.asm
+++ b/data/items/names.asm
@@ -1,98 +1,101 @@
 ItemNames::
-	db "MASTER BALL@"
-	db "ULTRA BALL@"
-	db "GREAT BALL@"
-	db "POKé BALL@"
-	db "TOWN MAP@"
-	db "BICYCLE@"
-	db "?????@"
-	db "SAFARI BALL@"
-	db "POKéDEX@"
-	db "MOON STONE@"
-	db "ANTIDOTE@"
-	db "BURN HEAL@"
-	db "ICE HEAL@"
-	db "AWAKENING@"
-	db "PARLYZ HEAL@"
-	db "FULL RESTORE@"
-	db "MAX POTION@"
-	db "HYPER POTION@"
-	db "SUPER POTION@"
-	db "POTION@"
-	db "BOULDERBADGE@"
-	db "CASCADEBADGE@"
-	db "THUNDERBADGE@"
-	db "RAINBOWBADGE@"
-	db "SOULBADGE@"
-	db "MARSHBADGE@"
-	db "VOLCANOBADGE@"
-	db "EARTHBADGE@"
-	db "ESCAPE ROPE@"
-	db "REPEL@"
-	db "OLD AMBER@"
-	db "FIRE STONE@"
-	db "THUNDERSTONE@"
-	db "WATER STONE@"
-	db "HP UP@"
-	db "PROTEIN@"
-	db "IRON@"
-	db "CARBOS@"
-	db "CALCIUM@"
-	db "RARE CANDY@"
-	db "DOME FOSSIL@"
-	db "HELIX FOSSIL@"
-	db "SECRET KEY@"
-	db "?????@"
-	db "BIKE VOUCHER@"
-	db "X ACCURACY@"
-	db "LEAF STONE@"
-	db "CARD KEY@"
-	db "NUGGET@"
-	db "PP UP@"
-	db "POKé DOLL@"
-	db "FULL HEAL@"
-	db "REVIVE@"
-	db "MAX REVIVE@"
-	db "GUARD SPEC.@"
-	db "SUPER REPEL@"
-	db "MAX REPEL@"
-	db "DIRE HIT@"
-	db "COIN@"
-	db "FRESH WATER@"
-	db "SODA POP@"
-	db "LEMONADE@"
-	db "S.S.TICKET@"
-	db "GOLD TEETH@"
-	db "X ATTACK@"
-	db "X DEFEND@"
-	db "X SPEED@"
-	db "X SPECIAL@"
-	db "COIN CASE@"
-	db "OAK's PARCEL@"
-	db "ITEMFINDER@"
-	db "SILPH SCOPE@"
-	db "POKé FLUTE@"
-	db "LIFT KEY@"
-	db "EXP.ALL@"
-	db "OLD ROD@"
-	db "GOOD ROD@"
-	db "SUPER ROD@"
-	db "PP UP@"
-	db "ETHER@"
-	db "MAX ETHER@"
-	db "ELIXER@"
-	db "MAX ELIXER@"
-	db "B2F@"
-	db "B1F@"
-	db "1F@"
-	db "2F@"
-	db "3F@"
-	db "4F@"
-	db "5F@"
-	db "6F@"
-	db "7F@"
-	db "8F@"
-	db "9F@"
-	db "10F@"
-	db "11F@"
-	db "B4F@"
+	list_start ItemNames
+	li "MASTER BALL"
+	li "ULTRA BALL"
+	li "GREAT BALL"
+	li "POKé BALL"
+	li "TOWN MAP"
+	li "BICYCLE"
+	li "?????"
+	li "SAFARI BALL"
+	li "POKéDEX"
+	li "MOON STONE"
+	li "ANTIDOTE"
+	li "BURN HEAL"
+	li "ICE HEAL"
+	li "AWAKENING"
+	li "PARLYZ HEAL"
+	li "FULL RESTORE"
+	li "MAX POTION"
+	li "HYPER POTION"
+	li "SUPER POTION"
+	li "POTION"
+	li "BOULDERBADGE"
+	li "CASCADEBADGE"
+	li "THUNDERBADGE"
+	li "RAINBOWBADGE"
+	li "SOULBADGE"
+	li "MARSHBADGE"
+	li "VOLCANOBADGE"
+	li "EARTHBADGE"
+	li "ESCAPE ROPE"
+	li "REPEL"
+	li "OLD AMBER"
+	li "FIRE STONE"
+	li "THUNDERSTONE"
+	li "WATER STONE"
+	li "HP UP"
+	li "PROTEIN"
+	li "IRON"
+	li "CARBOS"
+	li "CALCIUM"
+	li "RARE CANDY"
+	li "DOME FOSSIL"
+	li "HELIX FOSSIL"
+	li "SECRET KEY"
+	li "?????"
+	li "BIKE VOUCHER"
+	li "X ACCURACY"
+	li "LEAF STONE"
+	li "CARD KEY"
+	li "NUGGET"
+	li "PP UP"
+	li "POKé DOLL"
+	li "FULL HEAL"
+	li "REVIVE"
+	li "MAX REVIVE"
+	li "GUARD SPEC."
+	li "SUPER REPEL"
+	li "MAX REPEL"
+	li "DIRE HIT"
+	li "COIN"
+	li "FRESH WATER"
+	li "SODA POP"
+	li "LEMONADE"
+	li "S.S.TICKET"
+	li "GOLD TEETH"
+	li "X ATTACK"
+	li "X DEFEND"
+	li "X SPEED"
+	li "X SPECIAL"
+	li "COIN CASE"
+	li "OAK's PARCEL"
+	li "ITEMFINDER"
+	li "SILPH SCOPE"
+	li "POKé FLUTE"
+	li "LIFT KEY"
+	li "EXP.ALL"
+	li "OLD ROD"
+	li "GOOD ROD"
+	li "SUPER ROD"
+	li "PP UP"
+	li "ETHER"
+	li "MAX ETHER"
+	li "ELIXER"
+	li "MAX ELIXER"
+	assert_list_length NUM_ITEMS
+	li "B2F"
+	li "B1F"
+	li "1F"
+	li "2F"
+	li "3F"
+	li "4F"
+	li "5F"
+	li "6F"
+	li "7F"
+	li "8F"
+	li "9F"
+	li "10F"
+	li "11F"
+	li "B4F"
+	assert_list_length NUM_ITEMS + NUM_FLOORS
--- a/data/items/prices.asm
+++ b/data/items/prices.asm
@@ -1,4 +1,5 @@
 ItemPrices::
+	table_width 3, ItemPrices
 	money 0     ; MASTER_BALL
 	money 1200  ; ULTRA_BALL
 	money 600   ; GREAT_BALL
@@ -82,6 +83,7 @@
 	money 0     ; MAX_ETHER
 	money 0     ; ELIXER
 	money 0     ; MAX_ELIXER
+	assert_table_length NUM_ITEMS
 	money 0     ; FLOOR_B2F
 	money 0     ; FLOOR_B1F
 	money 0     ; FLOOR_1F
@@ -96,3 +98,4 @@
 	money 0     ; FLOOR_10F
 	money 0     ; FLOOR_11F
 	money 0     ; FLOOR_B4F
+	assert_table_length NUM_ITEMS + NUM_FLOORS
--- a/data/items/tm_prices.asm
+++ b/data/items/tm_prices.asm
@@ -1,5 +1,6 @@
 TechnicalMachinePrices:
 ; In thousands (nybbles).
+	table_width 1, TechnicalMachinePrices
 	dn 3, 2  ; TM01, TM02
 	dn 2, 1  ; TM03, TM04
 	dn 3, 4  ; TM05, TM06
@@ -25,3 +26,4 @@
 	dn 2, 4  ; TM45, TM46
 	dn 3, 4  ; TM47, TM48
 	dn 4, 2  ; TM49, TM50
+	assert_table_length (NUM_TMS + 1) / 2
--- a/data/maps/hide_show_data.asm
+++ b/data/maps/hide_show_data.asm
@@ -2,6 +2,7 @@
 
 MapHSPointers:
 ; entries correspond to map ids
+	table_width 2, MapHSPointers
 	dw PalletTownHS
 	dw ViridianCityHS
 	dw PewterCityHS
@@ -250,6 +251,7 @@
 	dw NoHS
 	dw NoHS
 	dw NoHS
+	assert_table_length NUM_MAPS
 	dw -1 ; end
 
 NoHS:
@@ -257,6 +259,7 @@
 
 MissableObjects:
 ; entries correspond to HS_* constants (see constants/hide_show_constants)
+	table_width 3, MissableObjects
 ; format: map id, object id, HIDE/SHOW
 
 PalletTownHS:
@@ -562,3 +565,4 @@
 	db SEAFOAM_ISLANDS_B4F, $02, HIDE
 	db SEAFOAM_ISLANDS_B4F, $03, SHOW
 	db $FF, $01, SHOW ; end
+	assert_table_length NUM_HS_OBJECTS + 1
--- a/data/maps/map_header_banks.asm
+++ b/data/maps/map_header_banks.asm
@@ -1,5 +1,6 @@
 ; see also MapHeaderPointers
 MapHeaderBanks::
+	table_width 1, MapHeaderBanks
 	db BANK(PalletTown_h)
 	db BANK(ViridianCity_h)
 	db BANK(PewterCity_h)
@@ -248,3 +249,4 @@
 	db BANK(LoreleisRoom_h)
 	db BANK(BrunosRoom_h)
 	db BANK(AgathasRoom_h)
+	assert_table_length NUM_MAPS
--- a/data/maps/map_header_pointers.asm
+++ b/data/maps/map_header_pointers.asm
@@ -1,5 +1,6 @@
 ; see also MapHeaderBanks
 MapHeaderPointers::
+	table_width 2, MapHeaderPointers
 	dw PalletTown_h
 	dw ViridianCity_h
 	dw PewterCity_h
@@ -247,4 +248,5 @@
 	dw SilphCo2F_h ; UNUSED_MAP_F4
 	dw LoreleisRoom_h
 	dw BrunosRoom_h
-	dw AgathasRoom_h ;247
+	dw AgathasRoom_h
+	assert_table_length NUM_MAPS
--- a/data/maps/names.asm
+++ b/data/maps/names.asm
@@ -1,4 +1,3 @@
-MapNames:
 PalletTownName:      db "PALLET TOWN@"
 ViridianCityName:    db "VIRIDIAN CITY@"
 PewterCityName:      db "PEWTER CITY@"
--- a/data/maps/songs.asm
+++ b/data/maps/songs.asm
@@ -1,4 +1,5 @@
 MapSongBanks::
+	table_width 2, MapSongBanks
 	db MUSIC_PALLET_TOWN, BANK(Music_PalletTown) ; PALLET_TOWN
 	db MUSIC_CITIES1, BANK(Music_Cities1) ; VIRIDIAN_CITY
 	db MUSIC_CITIES1, BANK(Music_Cities1) ; PEWTER_CITY
@@ -247,3 +248,4 @@
 	db MUSIC_GYM, BANK(Music_Gym) ; LORELEIS_ROOM
 	db MUSIC_DUNGEON1, BANK(Music_Dungeon1) ; BRUNOS_ROOM
 	db MUSIC_POKEMON_TOWER, BANK(Music_PokemonTower) ; AGATHAS_ROOM
+	assert_table_length NUM_MAPS
--- a/data/maps/sprite_sets.asm
+++ b/data/maps/sprite_sets.asm
@@ -1,4 +1,5 @@
 MapSpriteSets:
+	table_width 1, MapSpriteSets
 	db $01 ; PALLET_TOWN
 	db $01 ; VIRIDIAN_CITY
 	db $02 ; PEWTER_CITY
@@ -36,6 +37,7 @@
 	db $06 ; ROUTE_23
 	db $02 ; ROUTE_24
 	db $02 ; ROUTE_25
+	assert_table_length FIRST_INDOOR_MAP
 
 EAST_WEST   EQU 1
 NORTH_SOUTH EQU 2
@@ -60,7 +62,12 @@
 	db EAST_WEST,    3, $07, $03 ; $fc
 
 SpriteSets:
+
+; each sprite set has 9 walking sprites and 2 still sprites
+SPRITE_SET_LENGTH EQU 9 + 2
+
 ; sprite set $01
+	table_width 1
 	db SPRITE_BLUE
 	db SPRITE_YOUNGSTER
 	db SPRITE_GIRL
@@ -72,8 +79,10 @@
 	db SPRITE_SWIMMER
 	db SPRITE_POKE_BALL
 	db SPRITE_GAMBLER_ASLEEP
+	assert_table_length SPRITE_SET_LENGTH
 
 ; sprite set $02
+	table_width 1
 	db SPRITE_YOUNGSTER
 	db SPRITE_ROCKET
 	db SPRITE_SUPER_NERD
@@ -85,8 +94,10 @@
 	db SPRITE_COOLTRAINER_M
 	db SPRITE_POKE_BALL
 	db SPRITE_UNUSED_GAMBLER_ASLEEP_2
+	assert_table_length SPRITE_SET_LENGTH
 
 ; sprite set $03
+	table_width 1
 	db SPRITE_LITTLE_GIRL
 	db SPRITE_GIRL
 	db SPRITE_SUPER_NERD
@@ -98,8 +109,10 @@
 	db SPRITE_GUARD
 	db SPRITE_POKE_BALL
 	db SPRITE_UNUSED_GAMBLER_ASLEEP_2
+	assert_table_length SPRITE_SET_LENGTH
 
 ; sprite set $04
+	table_width 1
 	db SPRITE_BEAUTY
 	db SPRITE_SUPER_NERD
 	db SPRITE_YOUNGSTER
@@ -111,8 +124,10 @@
 	db SPRITE_COOLTRAINER_M
 	db SPRITE_POKE_BALL
 	db SPRITE_UNUSED_GAMBLER_ASLEEP_2
+	assert_table_length SPRITE_SET_LENGTH
 
 ; sprite set $05
+	table_width 1
 	db SPRITE_LITTLE_GIRL
 	db SPRITE_LITTLE_BOY
 	db SPRITE_GIRL
@@ -124,8 +139,10 @@
 	db SPRITE_ROCKET
 	db SPRITE_POKE_BALL
 	db SPRITE_SNORLAX
+	assert_table_length SPRITE_SET_LENGTH
 
 ; sprite set $06
+	table_width 1
 	db SPRITE_YOUNGSTER
 	db SPRITE_GYM_GUIDE
 	db SPRITE_MONSTER
@@ -137,8 +154,10 @@
 	db SPRITE_GAMBLER
 	db SPRITE_POKE_BALL
 	db SPRITE_UNUSED_GAMBLER_ASLEEP_2
+	assert_table_length SPRITE_SET_LENGTH
 
 ; sprite set $07
+	table_width 1
 	db SPRITE_ROCKET
 	db SPRITE_SCIENTIST
 	db SPRITE_SILPH_WORKER
@@ -150,8 +169,10 @@
 	db SPRITE_MONSTER
 	db SPRITE_POKE_BALL
 	db SPRITE_UNUSED_GAMBLER_ASLEEP_2
+	assert_table_length SPRITE_SET_LENGTH
 
 ; sprite set $08
+	table_width 1
 	db SPRITE_BIKER
 	db SPRITE_SUPER_NERD
 	db SPRITE_MIDDLE_AGED_MAN
@@ -163,8 +184,10 @@
 	db SPRITE_MONSTER
 	db SPRITE_POKE_BALL
 	db SPRITE_SNORLAX
+	assert_table_length SPRITE_SET_LENGTH
 
 ; sprite set $09
+	table_width 1
 	db SPRITE_BIKER
 	db SPRITE_COOLTRAINER_M
 	db SPRITE_SILPH_WORKER
@@ -176,8 +199,10 @@
 	db SPRITE_SUPER_NERD
 	db SPRITE_POKE_BALL
 	db SPRITE_SNORLAX
+	assert_table_length SPRITE_SET_LENGTH
 
 ; sprite set $0a
+	table_width 1
 	db SPRITE_BIRD
 	db SPRITE_COOLTRAINER_M
 	db SPRITE_FAIRY
@@ -189,3 +214,4 @@
 	db SPRITE_YOUNGSTER
 	db SPRITE_POKE_BALL
 	db SPRITE_FOSSIL
+	assert_table_length SPRITE_SET_LENGTH
--- a/data/maps/town_map_entries.asm
+++ b/data/maps/town_map_entries.asm
@@ -5,6 +5,7 @@
 
 ; the appearance of towns and routes in the town map
 ExternalMapEntries:
+	table_width 3, ExternalMapEntries
 	; x, y, name
 	external_map  2, 11, PalletTownName
 	external_map  2,  8, ViridianCityName
@@ -43,6 +44,7 @@
 	external_map  0,  6, Route23Name
 	external_map 10,  1, Route24Name
 	external_map 11,  0, Route25Name
+	assert_table_length FIRST_INDOOR_MAP
 
 
 internal_map: MACRO
--- a/data/moves/animations.asm
+++ b/data/moves/animations.asm
@@ -1,4 +1,5 @@
 AttackAnimationPointers:
+	table_width 2, AttackAnimationPointers
 	dw PoundAnim
 	dw KarateChopAnim
 	dw DoubleSlapAnim
@@ -164,6 +165,7 @@
 	dw SlashAnim
 	dw SubstituteAnim
 	dw StruggleAnim
+	assert_table_length NUM_ATTACKS
 	dw ShowPicAnim
 	dw EnemyFlashAnim
 	dw PlayerFlashAnim
@@ -201,6 +203,7 @@
 	dw HidePicAnim
 	dw ThrowRockAnim
 	dw ThrowBaitAnim
+	assert_table_length NUM_ATTACK_ANIMS
 	dw ZigZagScreenAnim
 
 ; each animation is a list of subanimations
--- a/data/moves/effects_pointers.asm
+++ b/data/moves/effects_pointers.asm
@@ -1,5 +1,6 @@
 MoveEffectPointerTable:
 ; entries correspond to *_EFFECT constants
+	table_width 2, MoveEffectPointerTable
 	dw SleepEffect               ; EFFECT_01
 	dw PoisonEffect              ; POISON_SIDE_EFFECT1
 	dw DrainHPEffect             ; DRAIN_HP_EFFECT
@@ -86,3 +87,4 @@
 	dw LeechSeedEffect           ; LEECH_SEED_EFFECT
 	dw SplashEffect              ; SPLASH_EFFECT
 	dw DisableEffect             ; DISABLE_EFFECT
+	assert_table_length NUM_MOVE_EFFECTS
--- a/data/moves/field_move_names.asm
+++ b/data/moves/field_move_names.asm
@@ -1,3 +1,4 @@
+; see also FieldMoveDisplayData
 FieldMoveNames:
 	db "CUT@"
 	db "FLY@"
--- a/data/moves/moves.asm
+++ b/data/moves/moves.asm
@@ -1,6 +1,3 @@
-Moves:
-; Characteristics of each move.
-
 move: MACRO
 	db \1 ; animation (interchangeable with move id)
 	db \2 ; effect
@@ -10,8 +7,10 @@
 	db \6 ; pp
 ENDM
 
+Moves:
+; Characteristics of each move.
+	table_width MOVE_LENGTH, Moves
 	move POUND,        NO_ADDITIONAL_EFFECT,        40, NORMAL,       100, 35
-MoveEnd:
 	move KARATE_CHOP,  NO_ADDITIONAL_EFFECT,        50, NORMAL,       100, 25
 	move DOUBLESLAP,   TWO_TO_FIVE_ATTACKS_EFFECT,  15, NORMAL,        85, 10
 	move COMET_PUNCH,  TWO_TO_FIVE_ATTACKS_EFFECT,  18, NORMAL,        85, 15
@@ -176,3 +175,4 @@
 	move SLASH,        NO_ADDITIONAL_EFFECT,        70, NORMAL,       100, 20
 	move SUBSTITUTE,   SUBSTITUTE_EFFECT,            0, NORMAL,       100, 10
 	move STRUGGLE,     RECOIL_EFFECT,               50, NORMAL,       100, 10
+	assert_table_length NUM_ATTACKS
--- a/data/moves/names.asm
+++ b/data/moves/names.asm
@@ -1,166 +1,168 @@
 MoveNames::
-	db "POUND@"
-	db "KARATE CHOP@"
-	db "DOUBLESLAP@"
-	db "COMET PUNCH@"
-	db "MEGA PUNCH@"
-	db "PAY DAY@"
-	db "FIRE PUNCH@"
-	db "ICE PUNCH@"
-	db "THUNDERPUNCH@"
-	db "SCRATCH@"
-	db "VICEGRIP@"
-	db "GUILLOTINE@"
-	db "RAZOR WIND@"
-	db "SWORDS DANCE@"
-	db "CUT@"
-	db "GUST@"
-	db "WING ATTACK@"
-	db "WHIRLWIND@"
-	db "FLY@"
-	db "BIND@"
-	db "SLAM@"
-	db "VINE WHIP@"
-	db "STOMP@"
-	db "DOUBLE KICK@"
-	db "MEGA KICK@"
-	db "JUMP KICK@"
-	db "ROLLING KICK@"
-	db "SAND-ATTACK@"
-	db "HEADBUTT@"
-	db "HORN ATTACK@"
-	db "FURY ATTACK@"
-	db "HORN DRILL@"
-	db "TACKLE@"
-	db "BODY SLAM@"
-	db "WRAP@"
-	db "TAKE DOWN@"
-	db "THRASH@"
-	db "DOUBLE-EDGE@"
-	db "TAIL WHIP@"
-	db "POISON STING@"
-	db "TWINEEDLE@"
-	db "PIN MISSILE@"
-	db "LEER@"
-	db "BITE@"
-	db "GROWL@"
-	db "ROAR@"
-	db "SING@"
-	db "SUPERSONIC@"
-	db "SONICBOOM@"
-	db "DISABLE@"
-	db "ACID@"
-	db "EMBER@"
-	db "FLAMETHROWER@"
-	db "MIST@"
-	db "WATER GUN@"
-	db "HYDRO PUMP@"
-	db "SURF@"
-	db "ICE BEAM@"
-	db "BLIZZARD@"
-	db "PSYBEAM@"
-	db "BUBBLEBEAM@"
-	db "AURORA BEAM@"
-	db "HYPER BEAM@"
-	db "PECK@"
-	db "DRILL PECK@"
-	db "SUBMISSION@"
-	db "LOW KICK@"
-	db "COUNTER@"
-	db "SEISMIC TOSS@"
-	db "STRENGTH@"
-	db "ABSORB@"
-	db "MEGA DRAIN@"
-	db "LEECH SEED@"
-	db "GROWTH@"
-	db "RAZOR LEAF@"
-	db "SOLARBEAM@"
-	db "POISONPOWDER@"
-	db "STUN SPORE@"
-	db "SLEEP POWDER@"
-	db "PETAL DANCE@"
-	db "STRING SHOT@"
-	db "DRAGON RAGE@"
-	db "FIRE SPIN@"
-	db "THUNDERSHOCK@"
-	db "THUNDERBOLT@"
-	db "THUNDER WAVE@"
-	db "THUNDER@"
-	db "ROCK THROW@"
-	db "EARTHQUAKE@"
-	db "FISSURE@"
-	db "DIG@"
-	db "TOXIC@"
-	db "CONFUSION@"
-	db "PSYCHIC@"
-	db "HYPNOSIS@"
-	db "MEDITATE@"
-	db "AGILITY@"
-	db "QUICK ATTACK@"
-	db "RAGE@"
-	db "TELEPORT@"
-	db "NIGHT SHADE@"
-	db "MIMIC@"
-	db "SCREECH@"
-	db "DOUBLE TEAM@"
-	db "RECOVER@"
-	db "HARDEN@"
-	db "MINIMIZE@"
-	db "SMOKESCREEN@"
-	db "CONFUSE RAY@"
-	db "WITHDRAW@"
-	db "DEFENSE CURL@"
-	db "BARRIER@"
-	db "LIGHT SCREEN@"
-	db "HAZE@"
-	db "REFLECT@"
-	db "FOCUS ENERGY@"
-	db "BIDE@"
-	db "METRONOME@"
-	db "MIRROR MOVE@"
-	db "SELFDESTRUCT@"
-	db "EGG BOMB@"
-	db "LICK@"
-	db "SMOG@"
-	db "SLUDGE@"
-	db "BONE CLUB@"
-	db "FIRE BLAST@"
-	db "WATERFALL@"
-	db "CLAMP@"
-	db "SWIFT@"
-	db "SKULL BASH@"
-	db "SPIKE CANNON@"
-	db "CONSTRICT@"
-	db "AMNESIA@"
-	db "KINESIS@"
-	db "SOFTBOILED@"
-	db "HI JUMP KICK@"
-	db "GLARE@"
-	db "DREAM EATER@"
-	db "POISON GAS@"
-	db "BARRAGE@"
-	db "LEECH LIFE@"
-	db "LOVELY KISS@"
-	db "SKY ATTACK@"
-	db "TRANSFORM@"
-	db "BUBBLE@"
-	db "DIZZY PUNCH@"
-	db "SPORE@"
-	db "FLASH@"
-	db "PSYWAVE@"
-	db "SPLASH@"
-	db "ACID ARMOR@"
-	db "CRABHAMMER@"
-	db "EXPLOSION@"
-	db "FURY SWIPES@"
-	db "BONEMERANG@"
-	db "REST@"
-	db "ROCK SLIDE@"
-	db "HYPER FANG@"
-	db "SHARPEN@"
-	db "CONVERSION@"
-	db "TRI ATTACK@"
-	db "SUPER FANG@"
-	db "SLASH@"
-	db "SUBSTITUTE@"
-	db "STRUGGLE@"
+	list_start MoveNames
+	li "POUND"
+	li "KARATE CHOP"
+	li "DOUBLESLAP"
+	li "COMET PUNCH"
+	li "MEGA PUNCH"
+	li "PAY DAY"
+	li "FIRE PUNCH"
+	li "ICE PUNCH"
+	li "THUNDERPUNCH"
+	li "SCRATCH"
+	li "VICEGRIP"
+	li "GUILLOTINE"
+	li "RAZOR WIND"
+	li "SWORDS DANCE"
+	li "CUT"
+	li "GUST"
+	li "WING ATTACK"
+	li "WHIRLWIND"
+	li "FLY"
+	li "BIND"
+	li "SLAM"
+	li "VINE WHIP"
+	li "STOMP"
+	li "DOUBLE KICK"
+	li "MEGA KICK"
+	li "JUMP KICK"
+	li "ROLLING KICK"
+	li "SAND-ATTACK"
+	li "HEADBUTT"
+	li "HORN ATTACK"
+	li "FURY ATTACK"
+	li "HORN DRILL"
+	li "TACKLE"
+	li "BODY SLAM"
+	li "WRAP"
+	li "TAKE DOWN"
+	li "THRASH"
+	li "DOUBLE-EDGE"
+	li "TAIL WHIP"
+	li "POISON STING"
+	li "TWINEEDLE"
+	li "PIN MISSILE"
+	li "LEER"
+	li "BITE"
+	li "GROWL"
+	li "ROAR"
+	li "SING"
+	li "SUPERSONIC"
+	li "SONICBOOM"
+	li "DISABLE"
+	li "ACID"
+	li "EMBER"
+	li "FLAMETHROWER"
+	li "MIST"
+	li "WATER GUN"
+	li "HYDRO PUMP"
+	li "SURF"
+	li "ICE BEAM"
+	li "BLIZZARD"
+	li "PSYBEAM"
+	li "BUBBLEBEAM"
+	li "AURORA BEAM"
+	li "HYPER BEAM"
+	li "PECK"
+	li "DRILL PECK"
+	li "SUBMISSION"
+	li "LOW KICK"
+	li "COUNTER"
+	li "SEISMIC TOSS"
+	li "STRENGTH"
+	li "ABSORB"
+	li "MEGA DRAIN"
+	li "LEECH SEED"
+	li "GROWTH"
+	li "RAZOR LEAF"
+	li "SOLARBEAM"
+	li "POISONPOWDER"
+	li "STUN SPORE"
+	li "SLEEP POWDER"
+	li "PETAL DANCE"
+	li "STRING SHOT"
+	li "DRAGON RAGE"
+	li "FIRE SPIN"
+	li "THUNDERSHOCK"
+	li "THUNDERBOLT"
+	li "THUNDER WAVE"
+	li "THUNDER"
+	li "ROCK THROW"
+	li "EARTHQUAKE"
+	li "FISSURE"
+	li "DIG"
+	li "TOXIC"
+	li "CONFUSION"
+	li "PSYCHIC"
+	li "HYPNOSIS"
+	li "MEDITATE"
+	li "AGILITY"
+	li "QUICK ATTACK"
+	li "RAGE"
+	li "TELEPORT"
+	li "NIGHT SHADE"
+	li "MIMIC"
+	li "SCREECH"
+	li "DOUBLE TEAM"
+	li "RECOVER"
+	li "HARDEN"
+	li "MINIMIZE"
+	li "SMOKESCREEN"
+	li "CONFUSE RAY"
+	li "WITHDRAW"
+	li "DEFENSE CURL"
+	li "BARRIER"
+	li "LIGHT SCREEN"
+	li "HAZE"
+	li "REFLECT"
+	li "FOCUS ENERGY"
+	li "BIDE"
+	li "METRONOME"
+	li "MIRROR MOVE"
+	li "SELFDESTRUCT"
+	li "EGG BOMB"
+	li "LICK"
+	li "SMOG"
+	li "SLUDGE"
+	li "BONE CLUB"
+	li "FIRE BLAST"
+	li "WATERFALL"
+	li "CLAMP"
+	li "SWIFT"
+	li "SKULL BASH"
+	li "SPIKE CANNON"
+	li "CONSTRICT"
+	li "AMNESIA"
+	li "KINESIS"
+	li "SOFTBOILED"
+	li "HI JUMP KICK"
+	li "GLARE"
+	li "DREAM EATER"
+	li "POISON GAS"
+	li "BARRAGE"
+	li "LEECH LIFE"
+	li "LOVELY KISS"
+	li "SKY ATTACK"
+	li "TRANSFORM"
+	li "BUBBLE"
+	li "DIZZY PUNCH"
+	li "SPORE"
+	li "FLASH"
+	li "PSYWAVE"
+	li "SPLASH"
+	li "ACID ARMOR"
+	li "CRABHAMMER"
+	li "EXPLOSION"
+	li "FURY SWIPES"
+	li "BONEMERANG"
+	li "REST"
+	li "ROCK SLIDE"
+	li "HYPER FANG"
+	li "SHARPEN"
+	li "CONVERSION"
+	li "TRI ATTACK"
+	li "SUPER FANG"
+	li "SLASH"
+	li "SUBSTITUTE"
+	li "STRUGGLE"
+	assert_list_length NUM_ATTACKS
--- a/data/moves/sfx.asm
+++ b/data/moves/sfx.asm
@@ -1,4 +1,5 @@
 MoveSoundTable:
+	table_width 3, MoveSoundTable
 	; ID, pitch mod, tempo mod
 	db SFX_POUND,              $00, $80 ; POUND
 	db SFX_BATTLE_0C,          $10, $80 ; KARATE_CHOP
@@ -165,4 +166,5 @@
 	db SFX_NOT_VERY_EFFECTIVE, $01, $ff ; SLASH
 	db SFX_BATTLE_2C,          $d8, $04 ; SUBSTITUTE
 	db SFX_BATTLE_0B,          $00, $80 ; STRUGGLE
+	assert_table_length NUM_ATTACKS
 	db SFX_BATTLE_0B,          $00, $80
--- a/data/moves/tmhm_moves.asm
+++ b/data/moves/tmhm_moves.asm
@@ -2,6 +2,7 @@
 ; define constants for the item IDs and for the corresponding move values.
 
 TechnicalMachines:
+	table_width 1, TechnicalMachines
 
 n = 1
 REPT NUM_TMS
@@ -14,6 +15,7 @@
 PURGE MOVE_FOR_TM
 n = n + 1
 ENDR
+	assert_table_length NUM_TMS
 
 n = 1
 REPT NUM_HMS
@@ -26,3 +28,4 @@
 PURGE MOVE_FOR_HM
 n = n + 1
 ENDR
+	assert_table_length NUM_TM_HM
--- a/data/pokemon/base_stats.asm
+++ b/data/pokemon/base_stats.asm
@@ -1,7 +1,6 @@
 BaseStats::
-MonBaseStats::
+	table_width BASE_DATA_SIZE, BaseStats
 INCLUDE "data/pokemon/base_stats/bulbasaur.asm"
-MonBaseStatsEnd::
 INCLUDE "data/pokemon/base_stats/ivysaur.asm"
 INCLUDE "data/pokemon/base_stats/venusaur.asm"
 INCLUDE "data/pokemon/base_stats/charmander.asm"
@@ -151,5 +150,4 @@
 INCLUDE "data/pokemon/base_stats/dragonair.asm"
 INCLUDE "data/pokemon/base_stats/dragonite.asm"
 INCLUDE "data/pokemon/base_stats/mewtwo.asm"
-BaseStatsEnd::
-	assert BaseStatsEnd - BaseStats == (wMonHeaderEnd - wMonHeader) * (NUM_POKEMON - 1) ; discount Mew
+	assert_table_length NUM_POKEMON - 1 ; discount Mew
--- a/data/pokemon/cries.asm
+++ b/data/pokemon/cries.asm
@@ -4,6 +4,7 @@
 ENDM
 
 CryData::
+	table_width 3, CryData
 	; base cry, pitch, length
 	mon_cry SFX_CRY_11, $00, $80 ; Rhydon
 	mon_cry SFX_CRY_03, $00, $80 ; Kangaskhan
@@ -195,3 +196,4 @@
 	mon_cry SFX_CRY_21, $55, $01 ; Bellsprout
 	mon_cry SFX_CRY_25, $44, $20 ; Weepinbell
 	mon_cry SFX_CRY_25, $66, $CC ; Victreebel
+	assert_table_length NUM_POKEMON_INDEXES
--- a/data/pokemon/dex_entries.asm
+++ b/data/pokemon/dex_entries.asm
@@ -1,4 +1,5 @@
 PokedexEntryPointers:
+	table_width 2, PokedexEntryPointers
 	dw RhydonDexEntry
 	dw KangaskhanDexEntry
 	dw NidoranMDexEntry
@@ -189,6 +190,7 @@
 	dw BellsproutDexEntry
 	dw WeepinbellDexEntry
 	dw VictreebelDexEntry
+	assert_table_length NUM_POKEMON_INDEXES
 
 ; string: species name
 ; height in feet, inches
--- a/data/pokemon/dex_order.asm
+++ b/data/pokemon/dex_order.asm
@@ -1,4 +1,5 @@
 PokedexOrder:
+	table_width 1, PokedexOrder
 	db DEX_RHYDON
 	db DEX_KANGASKHAN
 	db DEX_NIDORAN_M
@@ -189,3 +190,4 @@
 	db DEX_BELLSPROUT
 	db DEX_WEEPINBELL
 	db DEX_VICTREEBEL
+	assert_table_length NUM_POKEMON_INDEXES
--- a/data/pokemon/evos_moves.asm
+++ b/data/pokemon/evos_moves.asm
@@ -2,6 +2,7 @@
 ; The max number of evolutions per monster is MAX_EVOLUTIONS
 
 EvosMovesPointerTable:
+	table_width 2, EvosMovesPointerTable
 	dw RhydonEvosMoves
 	dw KangaskhanEvosMoves
 	dw NidoranMEvosMoves
@@ -192,6 +193,7 @@
 	dw BellsproutEvosMoves
 	dw WeepinbellEvosMoves
 	dw VictreebelEvosMoves
+	assert_table_length NUM_POKEMON_INDEXES
 
 RhydonEvosMoves:
 ; Evolutions
--- a/data/pokemon/menu_icons.asm
+++ b/data/pokemon/menu_icons.asm
@@ -1,4 +1,5 @@
 MonPartyData:
+	table_width 1, MonPartyData
 	dn ICON_GRASS,     ICON_GRASS     ; Bulbasaur / Ivysaur
 	dn ICON_GRASS,     ICON_MON       ; Venusaur / Charmander
 	dn ICON_MON,       ICON_MON       ; Charmeleon / Charizard
@@ -75,3 +76,4 @@
 	dn ICON_SNAKE,     ICON_SNAKE     ; Dratini / Dragonair
 	dn ICON_SNAKE,     ICON_MON       ; Dragonite / Mewtwo
 	dn ICON_MON,       0              ; Mew / padding
+	assert_table_length (NUM_POKEMON + 1) / 2
--- a/data/pokemon/names.asm
+++ b/data/pokemon/names.asm
@@ -1,4 +1,5 @@
 MonsterNames::
+	table_width NAME_LENGTH - 1, MonsterNames
 	db "RHYDON@@@@"
 	db "KANGASKHAN"
 	db "NIDORAN♂@@"
@@ -189,3 +190,4 @@
 	db "BELLSPROUT"
 	db "WEEPINBELL"
 	db "VICTREEBEL"
+	assert_table_length NUM_POKEMON_INDEXES
--- a/data/pokemon/palettes.asm
+++ b/data/pokemon/palettes.asm
@@ -1,4 +1,5 @@
 MonsterPalettes:
+	table_width 1, MonsterPalettes
 	db PAL_MEWMON    ; MISSINGNO
 	db PAL_GREENMON  ; BULBASAUR
 	db PAL_GREENMON  ; IVYSAUR
@@ -151,3 +152,4 @@
 	db PAL_BROWNMON  ; DRAGONITE
 	db PAL_MEWMON    ; MEWTWO
 	db PAL_MEWMON    ; MEW
+	assert_table_length NUM_POKEMON + 1
--- a/data/sgb/sgb_palettes.asm
+++ b/data/sgb/sgb_palettes.asm
@@ -1,5 +1,6 @@
 SuperPalettes:
 ; entries correspond to PAL_* constants
+	table_width 2 * 4, SuperPalettes
 	RGB 31,29,31, 21,28,11, 20,26,31, 03,02,02 ; PAL_ROUTE
 	RGB 31,29,31, 25,28,27, 20,26,31, 03,02,02 ; PAL_PALLET
 	RGB 31,29,31, 17,26,03, 20,26,31, 03,02,02 ; PAL_VIRIDIAN
@@ -49,3 +50,4 @@
 	RGB 31,29,31, 30,22,17, 11,15,23, 03,02,02 ; PAL_BADGE
 	RGB 31,29,31, 21,14,09, 18,24,22, 03,02,02 ; PAL_CAVE
 	RGB 31,29,31, 31,28,14, 24,20,10, 03,02,02 ; PAL_GAMEFREAK
+	assert_table_length NUM_SGB_PALS
--- a/data/sprites/sprites.asm
+++ b/data/sprites/sprites.asm
@@ -5,6 +5,7 @@
 ENDM
 
 SpriteSheetPointerTable:
+	table_width 4, SpriteSheetPointerTable
 	; graphics, tile count
 	overworld_sprite RedSprite, 12              ; SPRITE_RED
 	overworld_sprite BlueSprite, 12             ; SPRITE_BLUE
@@ -78,3 +79,4 @@
 	overworld_sprite GamblerAsleepSprite, 4     ; SPRITE_UNUSED_GAMBLER_ASLEEP_1
 	overworld_sprite GamblerAsleepSprite, 4     ; SPRITE_UNUSED_GAMBLER_ASLEEP_2
 	overworld_sprite GamblerAsleepSprite, 4     ; SPRITE_GAMBLER_ASLEEP
+	assert_table_length NUM_SPRITES
--- a/data/tilemaps.asm
+++ b/data/tilemaps.asm
@@ -5,6 +5,7 @@
 
 TileIDListPointerTable:
 ; entries correspond to TILEMAP_* constants (see constants/gfx_constants.asm)
+	table_width 3, TileIDListPointerTable
 	; tilemap pointer, width, height
 	tile_ids MonTiles,               7,  7
 	tile_ids SlideDownMonTiles_7x5,  7,  5
@@ -14,6 +15,7 @@
 	tile_ids GengarIntroTiles3,      7,  7
 	tile_ids GameBoyTiles,           6,  8
 	tile_ids LinkCableTiles,        12,  3
+	assert_table_length NUM_TILEMAPS
 
 DownscaledMonTiles_5x5:
 	INCBIN "gfx/pokemon/downscaled_5x5.tilemap"
--- a/data/tilesets/tileset_headers.asm
+++ b/data/tilesets/tileset_headers.asm
@@ -7,6 +7,7 @@
 ENDM
 
 Tilesets:
+	table_width 12, Tilesets
 	; block, gfx, coll, 3 counter tiles, grass tile, animations
 	tileset Overworld_Block,   Overworld_GFX,   Overworld_Coll,   $FF,$FF,$FF, $52, TILEANIM_WATER_FLOWER
 	tileset RedsHouse1_Block,  RedsHouse1_GFX,  RedsHouse1_Coll,  $FF,$FF,$FF, $FF, TILEANIM_NONE
@@ -32,3 +33,4 @@
 	tileset Club_Block,        Club_GFX,        Club_Coll,        $07,$17,$FF, $FF, TILEANIM_NONE
 	tileset Facility_Block,    Facility_GFX,    Facility_Coll,    $12,$FF,$FF, $FF, TILEANIM_WATER
 	tileset Plateau_Block,     Plateau_GFX,     Plateau_Coll,     $FF,$FF,$FF, $45, TILEANIM_WATER
+	assert_table_length NUM_TILESETS
--- a/data/tilesets/warp_tile_ids.asm
+++ b/data/tilesets/warp_tile_ids.asm
@@ -1,4 +1,5 @@
 WarpTileIDPointers:
+	table_width 2, WarpTileIDPointers
 	dw .OverworldWarpTileIDs
 	dw .RedsHouse1WarpTileIDs
 	dw .MartWarpTileIDs
@@ -23,6 +24,7 @@
 	dw .ClubWarpTileIDs
 	dw .FacilityWarpTileIDs
 	dw .PlateauWarpTileIDs
+	assert_table_length NUM_TILESETS
 
 warp_tiles: MACRO
 REPT _NARG
--- a/data/trainers/ai_pointers.asm
+++ b/data/trainers/ai_pointers.asm
@@ -1,4 +1,5 @@
 TrainerAIPointers:
+	table_width 3, TrainerAIPointers
 	; one entry per trainer class
 	; first byte, number of times (per Pokémon) it can occur
 	; next two bytes, pointer to AI subroutine for trainer class
@@ -50,3 +51,4 @@
 	dbw 3, GenericAI
 	dbw 2, AgathaAI ; agatha
 	dbw 1, LanceAI ; lance
+	assert_table_length NUM_TRAINERS
--- a/data/trainers/move_choices.asm
+++ b/data/trainers/move_choices.asm
@@ -4,10 +4,12 @@
 	shift
 ENDR
 	db 0 ; end
+list_index = list_index + 1
 ENDM
 
 ; move choice modification methods that are applied for each trainer class
 TrainerClassMoveChoiceModifications:
+	list_start TrainerClassMoveChoiceModifications
 	move_choices         ; YOUNGSTER
 	move_choices 1       ; BUG CATCHER
 	move_choices 1       ; LASS
@@ -55,3 +57,4 @@
 	move_choices 1       ; CHANNELER
 	move_choices 1       ; AGATHA
 	move_choices 1, 3    ; LANCE
+	assert_list_length NUM_TRAINERS
--- a/data/trainers/parties.asm
+++ b/data/trainers/parties.asm
@@ -269,7 +269,7 @@
 ; Route 17
 	; From https://www.smogon.com/smog/issue27/glitch:
 	; 0E:5FC2 is offset of the ending 0 for this first Biker on Route 17.
-	; BaseStats + (MonBaseStatsEnd - MonBaseStats) * (000 - 1) = $5FC2;
+	; BaseStats + (BASE_DATA_SIZE) * (000 - 1) = $5FC2;
 	; that's the formula from GetMonHeader for the base stats of mon #000.
 	; (BaseStats = $43DE and BANK(BaseStats) = $0E.)
 	; Finally, PokedexOrder lists 0 as the dex ID for every MissingNo.
--- a/data/trainers/pic_pointers_money.asm
+++ b/data/trainers/pic_pointers_money.asm
@@ -4,6 +4,7 @@
 ENDM
 
 TrainerPicAndMoneyPointers::
+	table_width 5, TrainerPicAndMoneyPointers
 	; pic pointer, base reward money
 	; money received after battle = base money × level of highest-level enemy mon
 	pic_money YoungsterPic,    1500
@@ -53,3 +54,4 @@
 	pic_money ChannelerPic,    3000
 	pic_money AgathaPic,       9900
 	pic_money LancePic,        9900
+	assert_table_length NUM_TRAINERS
--- a/data/types/names.asm
+++ b/data/types/names.asm
@@ -1,4 +1,5 @@
 TypeNames:
+	table_width 2, TypeNames
 
 	dw .Normal
 	dw .Fighting
@@ -21,6 +22,8 @@
 	dw .Psychic
 	dw .Ice
 	dw .Dragon
+
+	assert_table_length NUM_TYPES
 
 .Normal:   db "NORMAL@"
 .Fighting: db "FIGHTING@"
--- a/data/wild/grass_water.asm
+++ b/data/wild/grass_water.asm
@@ -1,4 +1,5 @@
 WildDataPointers:
+	table_width 2, WildDataPointers
 	dw NoMons      ; PALLET_TOWN
 	dw NoMons      ; VIRIDIAN_CITY
 	dw NoMons      ; PEWTER_CITY
@@ -247,6 +248,7 @@
 	dw NoMons
 	dw NoMons
 	dw NoMons
+	assert_table_length NUM_MAPS
 	dw -1 ; end
 
 ; wild pokemon data is divided into two parts.
--- a/data/wild/maps/CeruleanCave1F.asm
+++ b/data/wild/maps/CeruleanCave1F.asm
@@ -1,5 +1,5 @@
 DungeonMons1:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db 46, GOLBAT
 	db 46, HYPNO
 	db 46, MAGNETON
@@ -15,5 +15,7 @@
 	db 52, PARASECT
 	db 53, RAICHU
 	db 53, DITTO
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/CeruleanCave2F.asm
+++ b/data/wild/maps/CeruleanCave2F.asm
@@ -1,5 +1,5 @@
 DungeonMons2:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 51, DODRIO
 	db 51, VENOMOTH
 	db 51, KADABRA
@@ -10,5 +10,7 @@
 	db 54, WIGGLYTUFF
 	db 55, DITTO
 	db 60, DITTO
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/CeruleanCaveB1F.asm
+++ b/data/wild/maps/CeruleanCaveB1F.asm
@@ -1,5 +1,5 @@
 DungeonMonsB1:
-	db 25 ; grass encounter rate
+	def_grass_wildmons 25 ; encounter rate
 	db 55, RHYDON
 	db 55, MAROWAK
 	db 55, ELECTRODE
@@ -15,5 +15,7 @@
 	db 65, DITTO
 	db 63, DITTO
 	db 67, DITTO
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/DiglettsCave.asm
+++ b/data/wild/maps/DiglettsCave.asm
@@ -1,5 +1,5 @@
 CaveMons:
-	db 20 ; grass encounter rate
+	def_grass_wildmons 20 ; encounter rate
 	db 18, DIGLETT
 	db 19, DIGLETT
 	db 17, DIGLETT
@@ -10,5 +10,7 @@
 	db 22, DIGLETT
 	db 29, DUGTRIO
 	db 31, DUGTRIO
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/MtMoon1F.asm
+++ b/data/wild/maps/MtMoon1F.asm
@@ -1,5 +1,5 @@
 MoonMons1:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db  8, ZUBAT
 	db  7, ZUBAT
 	db  9, ZUBAT
@@ -10,5 +10,7 @@
 	db  8, PARAS
 	db 11, ZUBAT
 	db  8, CLEFAIRY
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/MtMoonB1F.asm
+++ b/data/wild/maps/MtMoonB1F.asm
@@ -1,5 +1,5 @@
 MoonMonsB1:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db  8, ZUBAT
 	db  7, ZUBAT
 	db  7, GEODUDE
@@ -10,5 +10,7 @@
 	db 11, ZUBAT
 	db  9, CLEFAIRY
 	db  9, GEODUDE
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/MtMoonB2F.asm
+++ b/data/wild/maps/MtMoonB2F.asm
@@ -1,5 +1,5 @@
 MoonMonsB2:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db  9, ZUBAT
 	db  9, GEODUDE
 	db 10, ZUBAT
@@ -10,5 +10,7 @@
 	db 10, CLEFAIRY
 	db 12, ZUBAT
 	db 12, CLEFAIRY
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonMansion1F.asm
+++ b/data/wild/maps/PokemonMansion1F.asm
@@ -1,5 +1,5 @@
 MansionMons1:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 IF DEF(_RED)
 	db 32, KOFFING
 	db 30, KOFFING
@@ -24,5 +24,7 @@
 	db 37, MUK
 	db 39, WEEZING
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonMansion2F.asm
+++ b/data/wild/maps/PokemonMansion2F.asm
@@ -1,5 +1,5 @@
 MansionMons2:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 IF DEF(_RED)
 	db 32, GROWLITHE
 	db 34, KOFFING
@@ -24,5 +24,7 @@
 	db 39, MUK
 	db 37, WEEZING
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonMansion3F.asm
+++ b/data/wild/maps/PokemonMansion3F.asm
@@ -1,5 +1,5 @@
 MansionMons3:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 IF DEF(_RED)
 	db 31, KOFFING
 	db 33, GROWLITHE
@@ -24,5 +24,7 @@
 	db 36, PONYTA
 	db 42, WEEZING
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonMansionB1F.asm
+++ b/data/wild/maps/PokemonMansionB1F.asm
@@ -1,5 +1,5 @@
 MansionMonsB1:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 IF DEF(_RED)
 	db 33, KOFFING
 	db 31, KOFFING
@@ -24,5 +24,7 @@
 	db 38, MAGMAR
 	db 42, WEEZING
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonTower1F.asm
+++ b/data/wild/maps/PokemonTower1F.asm
@@ -1,4 +1,6 @@
 TowerMons1:
-	db 0 ; grass encounter rate
+	def_grass_wildmons 0 ; encounter rate
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonTower2F.asm
+++ b/data/wild/maps/PokemonTower2F.asm
@@ -1,4 +1,6 @@
 TowerMons2:
-	db 0 ; grass encounter rate
+	def_grass_wildmons 0 ; encounter rate
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonTower3F.asm
+++ b/data/wild/maps/PokemonTower3F.asm
@@ -1,5 +1,5 @@
 TowerMons3:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db 20, GASTLY
 	db 21, GASTLY
 	db 22, GASTLY
@@ -10,5 +10,7 @@
 	db 20, CUBONE
 	db 22, CUBONE
 	db 25, HAUNTER
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonTower4F.asm
+++ b/data/wild/maps/PokemonTower4F.asm
@@ -1,5 +1,5 @@
 TowerMons4:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db 20, GASTLY
 	db 21, GASTLY
 	db 22, GASTLY
@@ -10,5 +10,7 @@
 	db 20, CUBONE
 	db 22, CUBONE
 	db 24, GASTLY
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonTower5F.asm
+++ b/data/wild/maps/PokemonTower5F.asm
@@ -1,5 +1,5 @@
 TowerMons5:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db 20, GASTLY
 	db 21, GASTLY
 	db 22, GASTLY
@@ -10,5 +10,7 @@
 	db 20, CUBONE
 	db 22, CUBONE
 	db 24, GASTLY
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonTower6F.asm
+++ b/data/wild/maps/PokemonTower6F.asm
@@ -1,5 +1,5 @@
 TowerMons6:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 21, GASTLY
 	db 22, GASTLY
 	db 23, GASTLY
@@ -10,5 +10,7 @@
 	db 22, CUBONE
 	db 24, CUBONE
 	db 28, HAUNTER
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PokemonTower7F.asm
+++ b/data/wild/maps/PokemonTower7F.asm
@@ -1,5 +1,5 @@
 TowerMons7:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 21, GASTLY
 	db 22, GASTLY
 	db 23, GASTLY
@@ -10,5 +10,7 @@
 	db 24, CUBONE
 	db 28, HAUNTER
 	db 30, HAUNTER
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/PowerPlant.asm
+++ b/data/wild/maps/PowerPlant.asm
@@ -1,5 +1,5 @@
 PowerPlantMons:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db 21, VOLTORB
 	db 21, MAGNEMITE
 	db 20, PIKACHU
@@ -16,5 +16,7 @@
 	db 33, RAICHU
 	db 36, RAICHU
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/RockTunnel1F.asm
+++ b/data/wild/maps/RockTunnel1F.asm
@@ -1,5 +1,5 @@
 TunnelMonsB1:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 16, ZUBAT
 	db 17, ZUBAT
 	db 17, GEODUDE
@@ -10,5 +10,7 @@
 	db 17, MACHOP
 	db 13, ONIX
 	db 15, ONIX
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/RockTunnelB1F.asm
+++ b/data/wild/maps/RockTunnelB1F.asm
@@ -1,5 +1,5 @@
 TunnelMonsB2:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 16, ZUBAT
 	db 17, ZUBAT
 	db 17, GEODUDE
@@ -10,5 +10,7 @@
 	db 17, ONIX
 	db 13, ONIX
 	db 18, GEODUDE
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route1.asm
+++ b/data/wild/maps/Route1.asm
@@ -1,5 +1,5 @@
 Route1Mons:
-	db 25 ; grass encounter rate
+	def_grass_wildmons 25 ; encounter rate
 	db  3, PIDGEY
 	db  3, RATTATA
 	db  3, RATTATA
@@ -10,5 +10,7 @@
 	db  4, RATTATA
 	db  4, PIDGEY
 	db  5, PIDGEY
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route10.asm
+++ b/data/wild/maps/Route10.asm
@@ -1,5 +1,5 @@
 Route10Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 16, VOLTORB
 	db 16, SPEAROW
 	db 14, VOLTORB
@@ -21,5 +21,7 @@
 	db 13, SANDSHREW
 	db 17, SANDSHREW
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route11.asm
+++ b/data/wild/maps/Route11.asm
@@ -1,5 +1,5 @@
 Route11Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 IF DEF(_RED)
 	db 14, EKANS
 	db 15, SPEAROW
@@ -21,5 +21,7 @@
 	db 17, SPEAROW
 	db 11, DROWZEE
 	db 15, DROWZEE
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route12.asm
+++ b/data/wild/maps/Route12.asm
@@ -1,5 +1,5 @@
 Route12Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 IF DEF(_RED)
 	db 24, ODDISH
 	db 25, PIDGEY
@@ -24,5 +24,7 @@
 	db 28, WEEPINBELL
 	db 30, WEEPINBELL
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route13.asm
+++ b/data/wild/maps/Route13.asm
@@ -1,5 +1,5 @@
 Route13Mons:
-	db 20 ; grass encounter rate
+	def_grass_wildmons 20 ; encounter rate
 IF DEF(_RED)
 	db 24, ODDISH
 	db 25, PIDGEY
@@ -24,5 +24,7 @@
 	db 28, WEEPINBELL
 	db 30, WEEPINBELL
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route14.asm
+++ b/data/wild/maps/Route14.asm
@@ -1,5 +1,5 @@
 Route14Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 IF DEF(_RED)
 	db 24, ODDISH
 	db 26, PIDGEY
@@ -22,5 +22,7 @@
 ENDC
 	db 28, PIDGEOTTO
 	db 30, PIDGEOTTO
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route15.asm
+++ b/data/wild/maps/Route15.asm
@@ -1,5 +1,5 @@
 Route15Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 IF DEF(_RED)
 	db 24, ODDISH
 	db 26, DITTO
@@ -22,5 +22,7 @@
 ENDC
 	db 28, PIDGEOTTO
 	db 30, PIDGEOTTO
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route16.asm
+++ b/data/wild/maps/Route16.asm
@@ -1,5 +1,5 @@
 Route16Mons:
-	db 25 ; grass encounter rate
+	def_grass_wildmons 25 ; encounter rate
 	db 20, SPEAROW
 	db 22, SPEAROW
 	db 18, RATTATA
@@ -10,5 +10,7 @@
 	db 22, RATTATA
 	db 23, RATICATE
 	db 25, RATICATE
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route17.asm
+++ b/data/wild/maps/Route17.asm
@@ -1,5 +1,5 @@
 Route17Mons:
-	db 25 ; grass encounter rate
+	def_grass_wildmons 25 ; encounter rate
 	db 20, SPEAROW
 	db 22, SPEAROW
 	db 25, RATICATE
@@ -10,5 +10,7 @@
 	db 29, RATICATE
 	db 25, FEAROW
 	db 27, FEAROW
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route18.asm
+++ b/data/wild/maps/Route18.asm
@@ -1,5 +1,5 @@
 Route18Mons:
-	db 25 ; grass encounter rate
+	def_grass_wildmons 25 ; encounter rate
 	db 20, SPEAROW
 	db 22, SPEAROW
 	db 25, RATICATE
@@ -10,5 +10,7 @@
 	db 29, RATICATE
 	db 27, FEAROW
 	db 29, FEAROW
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route2.asm
+++ b/data/wild/maps/Route2.asm
@@ -1,5 +1,5 @@
 Route2Mons:
-	db 25 ; grass encounter rate
+	def_grass_wildmons 25 ; encounter rate
 	db  3, RATTATA
 	db  3, PIDGEY
 	db  4, PIDGEY
@@ -19,5 +19,7 @@
 	db  4, CATERPIE
 	db  5, CATERPIE
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route21.asm
+++ b/data/wild/maps/Route21.asm
@@ -1,5 +1,5 @@
 Route21Mons:
-	db 25 ; grass encounter rate
+	def_grass_wildmons 25 ; encounter rate
 	db 21, RATTATA
 	db 23, PIDGEY
 	db 30, RATICATE
@@ -10,8 +10,9 @@
 	db 28, TANGELA
 	db 30, TANGELA
 	db 32, TANGELA
+	end_grass_wildmons
 
-	db 5 ; water encounter rate
+	def_water_wildmons 5 ; encounter rate
 	db  5, TENTACOOL
 	db 10, TENTACOOL
 	db 15, TENTACOOL
@@ -22,3 +23,4 @@
 	db 30, TENTACOOL
 	db 35, TENTACOOL
 	db 40, TENTACOOL
+	end_water_wildmons
--- a/data/wild/maps/Route22.asm
+++ b/data/wild/maps/Route22.asm
@@ -1,5 +1,5 @@
 Route22Mons:
-	db 25 ; grass encounter rate
+	def_grass_wildmons 25 ; encounter rate
 	db  3, RATTATA
 IF DEF(_RED)
 	db  3, NIDORAN_M
@@ -23,5 +23,7 @@
 	db  3, NIDORAN_M
 	db  4, NIDORAN_M
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route23.asm
+++ b/data/wild/maps/Route23.asm
@@ -1,5 +1,5 @@
 Route23Mons:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 IF DEF(_RED)
 	db 26, EKANS
 ENDC
@@ -20,5 +20,7 @@
 	db 43, DITTO
 	db 41, FEAROW
 	db 43, FEAROW
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route24.asm
+++ b/data/wild/maps/Route24.asm
@@ -1,5 +1,5 @@
 Route24Mons:
-	db 25 ; grass encounter rate
+	def_grass_wildmons 25 ; encounter rate
 IF DEF(_RED)
 	db  7, WEEDLE
 	db  8, KAKUNA
@@ -21,5 +21,7 @@
 	db 13, PIDGEY
 	db  8, ABRA
 	db 12, ABRA
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route25.asm
+++ b/data/wild/maps/Route25.asm
@@ -1,5 +1,5 @@
 Route25Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 IF DEF(_RED)
 	db  8, WEEDLE
 	db  9, KAKUNA
@@ -24,5 +24,7 @@
 	db  7, KAKUNA
 	db  8, WEEDLE
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route3.asm
+++ b/data/wild/maps/Route3.asm
@@ -1,5 +1,5 @@
 Route3Mons:
-	db 20 ; grass encounter rate
+	def_grass_wildmons 20 ; encounter rate
 	db  6, PIDGEY
 	db  5, SPEAROW
 	db  7, PIDGEY
@@ -10,5 +10,7 @@
 	db  3, JIGGLYPUFF
 	db  5, JIGGLYPUFF
 	db  7, JIGGLYPUFF
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route4.asm
+++ b/data/wild/maps/Route4.asm
@@ -1,5 +1,5 @@
 Route4Mons:
-	db 20 ; grass encounter rate
+	def_grass_wildmons 20 ; encounter rate
 	db 10, RATTATA
 	db 10, SPEAROW
 	db  8, RATTATA
@@ -21,4 +21,7 @@
 	db  8, SANDSHREW
 	db 12, SANDSHREW
 ENDC
-	db 0 ; water encounter rate
+	end_grass_wildmons
+
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route5.asm
+++ b/data/wild/maps/Route5.asm
@@ -1,5 +1,5 @@
 Route5Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 IF DEF(_RED)
 	db 13, ODDISH
 	db 13, PIDGEY
@@ -24,5 +24,7 @@
 	db 14, MEOWTH
 	db 16, MEOWTH
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route6.asm
+++ b/data/wild/maps/Route6.asm
@@ -1,5 +1,5 @@
 Route6Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 IF DEF(_RED)
 	db 13, ODDISH
 	db 13, PIDGEY
@@ -24,5 +24,7 @@
 	db 14, MEOWTH
 	db 16, MEOWTH
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route7.asm
+++ b/data/wild/maps/Route7.asm
@@ -1,5 +1,5 @@
 Route7Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 19, PIDGEY
 IF DEF(_RED)
 	db 19, ODDISH
@@ -23,5 +23,7 @@
 	db 19, MEOWTH
 	db 20, MEOWTH
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route8.asm
+++ b/data/wild/maps/Route8.asm
@@ -1,5 +1,5 @@
 Route8Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 18, PIDGEY
 IF DEF(_RED)
 	db 18, MANKEY
@@ -23,5 +23,7 @@
 	db 15, VULPIX
 	db 18, VULPIX
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/Route9.asm
+++ b/data/wild/maps/Route9.asm
@@ -1,5 +1,5 @@
 Route9Mons:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 16, RATTATA
 	db 16, SPEAROW
 	db 14, RATTATA
@@ -21,5 +21,7 @@
 	db 13, SANDSHREW
 	db 17, SANDSHREW
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/SafariZoneCenter.asm
+++ b/data/wild/maps/SafariZoneCenter.asm
@@ -1,5 +1,5 @@
 ZoneMonsCenter:
-	db 30 ; grass encounter rate
+	def_grass_wildmons 30 ; encounter rate
 IF DEF(_RED)
 	db 22, NIDORAN_M
 	db 25, RHYHORN
@@ -23,5 +23,7 @@
 	db 23, PINSIR
 ENDC
 	db 23, CHANSEY
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/SafariZoneEast.asm
+++ b/data/wild/maps/SafariZoneEast.asm
@@ -1,5 +1,5 @@
 ZoneMons1:
-	db 30 ; grass encounter rate
+	def_grass_wildmons 30 ; encounter rate
 IF DEF(_RED)
 	db 24, NIDORAN_M
 	db 26, DODUO
@@ -24,5 +24,7 @@
 	db 25, KANGASKHAN
 	db 28, PINSIR
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/SafariZoneNorth.asm
+++ b/data/wild/maps/SafariZoneNorth.asm
@@ -1,5 +1,5 @@
 ZoneMons2:
-	db 30 ; grass encounter rate
+	def_grass_wildmons 30 ; encounter rate
 IF DEF(_RED)
 	db 22, NIDORAN_M
 	db 26, RHYHORN
@@ -21,5 +21,7 @@
 	db 32, VENOMOTH
 	db 26, CHANSEY
 	db 28, TAUROS
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/SafariZoneWest.asm
+++ b/data/wild/maps/SafariZoneWest.asm
@@ -1,5 +1,5 @@
 ZoneMons3:
-	db 30 ; grass encounter rate
+	def_grass_wildmons 30 ; encounter rate
 IF DEF(_RED)
 	db 25, NIDORAN_M
 	db 26, DODUO
@@ -21,5 +21,7 @@
 	db 31, VENOMOTH
 	db 26, TAUROS
 	db 28, KANGASKHAN
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/SeaRoutes.asm
+++ b/data/wild/maps/SeaRoutes.asm
@@ -1,7 +1,8 @@
 WaterMons:
-	db 0 ; grass encounter rate
+	def_grass_wildmons 0 ; encounter rate
+	end_grass_wildmons
 
-	db 5 ; water encounter rate
+	def_water_wildmons 5 ; encounter rate
 	db  5, TENTACOOL
 	db 10, TENTACOOL
 	db 15, TENTACOOL
--- a/data/wild/maps/SeafoamIslands1F.asm
+++ b/data/wild/maps/SeafoamIslands1F.asm
@@ -1,5 +1,5 @@
 IslandMons1:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 30, SEEL
 IF DEF(_RED)
 	db 30, SLOWPOKE
@@ -23,5 +23,7 @@
 	db 28, STARYU
 	db 38, SLOWBRO
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/SeafoamIslandsB1F.asm
+++ b/data/wild/maps/SeafoamIslandsB1F.asm
@@ -1,5 +1,5 @@
 IslandMonsB1:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 IF DEF(_RED)
 	db 30, STARYU
 	db 30, HORSEA
@@ -24,5 +24,7 @@
 	db 38, DEWGONG
 	db 37, KINGLER
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/SeafoamIslandsB2F.asm
+++ b/data/wild/maps/SeafoamIslandsB2F.asm
@@ -1,5 +1,5 @@
 IslandMonsB2:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db 30, SEEL
 IF DEF(_RED)
 	db 30, SLOWPOKE
@@ -23,5 +23,7 @@
 	db 30, GOLBAT
 	db 37, GOLDUCK
 ENDC
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/SeafoamIslandsB3F.asm
+++ b/data/wild/maps/SeafoamIslandsB3F.asm
@@ -1,5 +1,5 @@
 IslandMonsB3:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 IF DEF(_RED)
 	db 31, SLOWPOKE
 	db 31, SEEL
@@ -23,5 +23,7 @@
 	db 39, KINGLER
 ENDC
 	db 37, DEWGONG
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/SeafoamIslandsB4F.asm
+++ b/data/wild/maps/SeafoamIslandsB4F.asm
@@ -1,5 +1,5 @@
 IslandMonsB4:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 IF DEF(_RED)
 	db 31, HORSEA
 	db 31, SHELLDER
@@ -23,5 +23,7 @@
 	db 39, GOLDUCK
 ENDC
 	db 32, GOLBAT
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/VictoryRoad1F.asm
+++ b/data/wild/maps/VictoryRoad1F.asm
@@ -1,5 +1,5 @@
 PlateauMons1:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 24, MACHOP
 	db 26, GEODUDE
 	db 22, ZUBAT
@@ -10,5 +10,7 @@
 	db 41, GOLBAT
 	db 42, MACHOKE
 	db 43, MAROWAK
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/VictoryRoad2F.asm
+++ b/data/wild/maps/VictoryRoad2F.asm
@@ -1,5 +1,5 @@
 PlateauMons2:
-	db 10 ; grass encounter rate
+	def_grass_wildmons 10 ; encounter rate
 	db 22, MACHOP
 	db 24, GEODUDE
 	db 26, ZUBAT
@@ -10,5 +10,7 @@
 	db 40, GOLBAT
 	db 40, MAROWAK
 	db 43, GRAVELER
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/VictoryRoad3F.asm
+++ b/data/wild/maps/VictoryRoad3F.asm
@@ -1,5 +1,5 @@
 PlateauMons3:
-	db 15 ; grass encounter rate
+	def_grass_wildmons 15 ; encounter rate
 	db 24, MACHOP
 	db 26, GEODUDE
 	db 22, ZUBAT
@@ -10,5 +10,7 @@
 	db 41, GOLBAT
 	db 42, MACHOKE
 	db 45, MACHOKE
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/ViridianForest.asm
+++ b/data/wild/maps/ViridianForest.asm
@@ -1,5 +1,5 @@
 ForestMons:
-	db 8 ; grass encounter rate
+	def_grass_wildmons 8 ; encounter rate
 IF DEF(_RED)
 	db  4, WEEDLE
 	db  5, KAKUNA
@@ -22,5 +22,7 @@
 ENDC
 	db  3, PIKACHU
 	db  5, PIKACHU
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/wild/maps/nothing.asm
+++ b/data/wild/maps/nothing.asm
@@ -1,4 +1,6 @@
 NoMons:
-	db 0 ; grass encounter rate
+	def_grass_wildmons 0 ; encounter rate
+	end_grass_wildmons
 
-	db 0 ; water encounter rate
+	def_water_wildmons 0 ; encounter rate
+	end_water_wildmons
--- a/data/yes_no_menu_strings.asm
+++ b/data/yes_no_menu_strings.asm
@@ -5,6 +5,7 @@
 
 TwoOptionMenuStrings:
 ; entries correspond to *_MENU constants
+	table_width 5, TwoOptionMenuStrings
 	; width, height, blank line before first menu item?, text pointer
 	two_option_menu 4, 3, FALSE, .YesNoMenu
 	two_option_menu 6, 3, FALSE, .NorthWestMenu
@@ -14,6 +15,7 @@
 	two_option_menu 7, 3, FALSE, .TradeCancelMenu
 	two_option_menu 7, 4, TRUE,  .HealCancelMenu
 	two_option_menu 4, 3, FALSE, .NoYesMenu
+	assert_table_length NUM_TWO_OPTION_MENUS
 
 .NoYesMenu:
 	db   "NO"
--- a/engine/battle/core.asm
+++ b/engine/battle/core.asm
@@ -1633,7 +1633,7 @@
 	ld bc, wPartyMon1DVs - wPartyMon1OTID
 	add hl, bc
 	ld de, wBattleMonDVs
-	ld bc, NUM_DVS
+	ld bc, wPartyMon1PP - wPartyMon1DVs
 	call CopyData
 	ld de, wBattleMonPP
 	ld bc, NUM_MOVES
@@ -1677,7 +1677,7 @@
 	ld bc, wEnemyMon1DVs - wEnemyMon1OTID
 	add hl, bc
 	ld de, wEnemyMonDVs
-	ld bc, NUM_DVS
+	ld bc, wEnemyMon1PP - wEnemyMon1DVs
 	call CopyData
 	ld de, wEnemyMonPP
 	ld bc, NUM_MOVES
@@ -5101,7 +5101,7 @@
 	ld [wd11e], a
 	dec a
 	ld hl, Moves
-	ld bc, MoveEnd - Moves
+	ld bc, MOVE_LENGTH
 	call AddNTimes
 	ld a, BANK(Moves)
 	call FarCopyData ; copy the move's stats
@@ -5133,7 +5133,7 @@
 	call BattleRandom
 	and a
 	jr z, .pickMoveLoop
-	cp NUM_ATTACKS + 1 ; max normal move number + 1 (this is Struggle's move number)
+	cp NUM_ATTACKS ; max move number (including Struggle)
 	jr nc, .pickMoveLoop
 	cp METRONOME
 	jr z, .pickMoveLoop
@@ -6077,7 +6077,7 @@
 	ld [wd0b5], a
 	dec a
 	ld hl, Moves
-	ld bc, MoveEnd - Moves
+	ld bc, MOVE_LENGTH
 	call AddNTimes
 	ld a, BANK(Moves)
 	call FarCopyData
--- a/engine/battle/effects.asm
+++ b/engine/battle/effects.asm
@@ -740,7 +740,7 @@
 	text_end
 
 PrintStatText:
-	ld hl, StatsTextStrings
+	ld hl, StatModTextStrings
 	ld c, "@"
 .findStatName_outer
 	dec b
@@ -755,7 +755,7 @@
 	ld bc, $a
 	jp CopyData
 
-INCLUDE "data/battle/stat_names.asm"
+INCLUDE "data/battle/stat_mod_names.asm"
 
 INCLUDE "data/battle/stat_modifiers.asm"
 
--- a/engine/battle/trainer_ai.asm
+++ b/engine/battle/trainer_ai.asm
@@ -264,7 +264,7 @@
 	push bc
 	dec a
 	ld hl, Moves
-	ld bc, MoveEnd - Moves
+	ld bc, MOVE_LENGTH
 	call AddNTimes
 	ld de, wEnemyMoveNum
 	call CopyData
--- a/engine/events/heal_party.asm
+++ b/engine/events/heal_party.asm
@@ -35,7 +35,7 @@
 	push bc
 
 	ld hl, Moves
-	ld bc, MoveEnd - Moves
+	ld bc, MOVE_LENGTH
 	call AddNTimes
 	ld de, wcd6d
 	ld a, BANK(Moves)
--- a/engine/items/item_effects.asm
+++ b/engine/items/item_effects.asm
@@ -1292,7 +1292,7 @@
 	ld [hl], a
 	pop hl
 	call .recalculateStats
-	ld hl, VitaminText
+	ld hl, VitaminStats
 	ld a, [wcf91]
 	sub HP_UP - 1
 	ld c, a
@@ -1425,12 +1425,7 @@
 	text_far _VitaminNoEffectText
 	text_end
 
-VitaminText:
-	db "HEALTH@"
-	db "ATTACK@"
-	db "DEFENSE@"
-	db "SPEED@"
-	db "SPECIAL@"
+INCLUDE "data/battle/stat_names.asm"
 
 ItemUseBait:
 	ld hl, ThrewBaitText
@@ -2490,7 +2485,7 @@
 	dec a
 	push hl
 	ld hl, Moves
-	ld bc, MoveEnd - Moves
+	ld bc, MOVE_LENGTH
 	call AddNTimes
 	ld de, wcd6d
 	ld a, BANK(Moves)
--- a/engine/pokemon/add_mon.asm
+++ b/engine/pokemon/add_mon.asm
@@ -259,7 +259,7 @@
 	push de
 	push bc
 	ld hl, Moves
-	ld bc, MoveEnd - Moves
+	ld bc, MOVE_LENGTH
 	call AddNTimes
 	ld de, wcd6d
 	ld a, BANK(Moves)
--- a/engine/pokemon/evos_moves.asm
+++ b/engine/pokemon/evos_moves.asm
@@ -161,7 +161,7 @@
 	ld a, [wd11e]
 	dec a
 	ld hl, BaseStats
-	ld bc, MonBaseStatsEnd - MonBaseStats
+	ld bc, BASE_DATA_SIZE
 	call AddNTimes
 	ld de, wMonHeader
 	call CopyData
@@ -479,7 +479,7 @@
 	push hl
 	dec a
 	ld hl, Moves
-	ld bc, MoveEnd - Moves
+	ld bc, MOVE_LENGTH
 	call AddNTimes
 	ld de, wBuffer
 	ld a, BANK(Moves)
--- a/engine/pokemon/learn_move.asm
+++ b/engine/pokemon/learn_move.asm
@@ -44,7 +44,7 @@
 	push de
 	dec a
 	ld hl, Moves
-	ld bc, MoveEnd - Moves
+	ld bc, MOVE_LENGTH
 	call AddNTimes
 	ld de, wBuffer
 	ld a, BANK(Moves)
--- a/home/pokemon.asm
+++ b/home/pokemon.asm
@@ -401,11 +401,11 @@
 	predef IndexToPokedex   ; convert pokemon ID in [wd11e] to pokedex number
 	ld a, [wd11e]
 	dec a
-	ld bc, MonBaseStatsEnd - MonBaseStats
+	ld bc, BASE_DATA_SIZE
 	ld hl, BaseStats
 	call AddNTimes
 	ld de, wMonHeader
-	ld bc, MonBaseStatsEnd - MonBaseStats
+	ld bc, BASE_DATA_SIZE
 	call CopyData
 	jr .done
 .specialID
@@ -419,7 +419,7 @@
 .mew
 	ld hl, MewBaseStats
 	ld de, wMonHeader
-	ld bc, MonBaseStatsEnd - MonBaseStats
+	ld bc, BASE_DATA_SIZE
 	ld a, BANK(MewBaseStats)
 	call FarCopyData
 .done
--- a/macros.asm
+++ b/macros.asm
@@ -1,3 +1,4 @@
+INCLUDE "macros/asserts.asm"
 INCLUDE "macros/const.asm"
 INCLUDE "macros/predef.asm"
 INCLUDE "macros/farcall.asm"
--- /dev/null
+++ b/macros/asserts.asm
@@ -1,0 +1,87 @@
+; Macros to verify assumptions about the data or code
+
+table_width: MACRO
+CURRENT_TABLE_WIDTH = \1
+IF DEF(CURRENT_TABLE_START)
+PURGE CURRENT_TABLE_START
+ENDC
+IF _NARG == 2
+CURRENT_TABLE_START EQUS "\2"
+ELSE
+CURRENT_TABLE_START EQUS "._table_width\@"
+CURRENT_TABLE_START:
+ENDC
+ENDM
+
+assert_table_length: MACRO
+x = \1
+	ASSERT x * CURRENT_TABLE_WIDTH == @ - CURRENT_TABLE_START, \
+		"{CURRENT_TABLE_START}: expected {d:x} entries, each {d:CURRENT_TABLE_WIDTH} bytes"
+ENDM
+
+list_start: MACRO
+list_index = 0
+IF DEF(CURRENT_LIST_START)
+PURGE CURRENT_LIST_START
+ENDC
+IF _NARG == 1
+CURRENT_LIST_START EQUS "\1"
+ELSE
+CURRENT_LIST_START EQUS "._list_start\@"
+CURRENT_LIST_START:
+ENDC
+ENDM
+
+li: MACRO
+	ASSERT !STRIN(\1, "@"), STRCAT("String terminator \"@\" in list entry: ", \1)
+	db \1, "@"
+list_index = list_index + 1
+ENDM
+
+assert_list_length: MACRO
+x = \1
+	ASSERT x == list_index, \
+		"{CURRENT_LIST_START}: expected {d:x} entries, got {d:list_index}"
+ENDM
+
+def_grass_wildmons: MACRO
+;\1: encounter rate
+if DEF(CURRENT_GRASS_WILDMONS_LABEL)
+PURGE CURRENT_GRASS_WILDMONS_LABEL
+endc
+CURRENT_GRASS_WILDMONS_RATE = \1
+CURRENT_GRASS_WILDMONS_LABEL EQUS "._def_grass_wildmons_\1"
+CURRENT_GRASS_WILDMONS_LABEL:
+	db \1
+ENDM
+
+end_grass_wildmons: MACRO
+	IF CURRENT_GRASS_WILDMONS_RATE == 0
+		assert 1 == @ - CURRENT_GRASS_WILDMONS_LABEL, \
+			"def_grass_wildmons {d:CURRENT_GRASS_WILDMONS_RATE}: expected 1 byte"
+	ELSE
+		assert WILDDATA_LENGTH == @ - CURRENT_GRASS_WILDMONS_LABEL, \
+			"def_grass_wildmons {d:CURRENT_GRASS_WILDMONS_RATE}: expected {d:WILDDATA_LENGTH} bytes"
+	ENDC
+ENDM
+
+def_water_wildmons: MACRO
+;\1: encounter rate
+if DEF(CURRENT_WATER_WILDMONS_LABEL)
+PURGE CURRENT_WATER_WILDMONS_LABEL
+endc
+CURRENT_WATER_WILDMONS_RATE = \1
+CURRENT_WATER_WILDMONS_LABEL EQUS "._def_water_wildmons_\1"
+CURRENT_WATER_WILDMONS_LABEL:
+	db \1
+ENDM
+
+end_water_wildmons: MACRO
+	IF CURRENT_WATER_WILDMONS_RATE == 0
+		assert 1 == @ - CURRENT_WATER_WILDMONS_LABEL, \
+			"def_water_wildmons {d:CURRENT_WATER_WILDMONS_RATE}: expected 1 byte"
+	ELSE
+		assert WILDDATA_LENGTH == @ - CURRENT_WATER_WILDMONS_LABEL, \
+			"def_water_wildmons {d:CURRENT_WATER_WILDMONS_RATE}: expected {d:WILDDATA_LENGTH} bytes"
+	ENDC
+ENDM
--- a/macros/const.asm
+++ b/macros/const.asm
@@ -38,3 +38,11 @@
 const_value = \1
 endc
 ENDM
+
+rb_skip: MACRO
+IF _NARG == 1
+rsset _RS + \1
+ELSE
+rsset _RS + 1
+ENDC
+ENDM
--- a/wram.asm
+++ b/wram.asm
@@ -703,8 +703,11 @@
 wPlayerMonEvasionMod::
 	ds 1
 
-	ds 3
+	ds 2
+wPlayerMonStatModsEnd::
 
+	ds 1
+
 wEnemyMonUnmodifiedLevel::
 	ds 1
 wEnemyMonUnmodifiedMaxHP::
@@ -748,8 +751,11 @@
 wInGameTradeReceiveMonSpecies::
 	ds 1
 
-	ds 2
+	ds 1
+wEnemyMonStatModsEnd::
 
+	ds 1
+
 wNPCMovementDirections2Index::
 
 wUnusedCD37::
@@ -1997,6 +2003,7 @@
 wMonHLearnset::
 ; bit field
 	flag_array NUM_TMS + NUM_HMS
+
 	ds 1
 wMonHeaderEnd::