shithub: pokecrystal

Download patch

ref: 40902ffe244544d638567642e4e01b9c5801db23
parent: 9c17fb14c8068d6662b9ca1cb048ed206b6770ee
author: Rangi <[email protected]>
date: Wed Mar 3 17:22:41 EST 2021

Verify data table sizes with table_width and assert_table_length macros

This was discussed in #706

It also uncovered some off-by-one issues with defining some constants.

A few structs now use rsreset/_RS to define their offset constants, as discussed in #739

--- a/audio/cry_pointers.asm
+++ b/audio/cry_pointers.asm
@@ -1,4 +1,6 @@
 Cries:
+; entries correspond to CRY_* constants (see constants/cry_constants.asm)
+	table_width 3, Cries
 	dba Cry_Nidoran_M
 	dba Cry_Nidoran_F
 	dba Cry_Slowpoke
@@ -67,3 +69,4 @@
 	dba Cry_Aipom
 	dba Cry_Dunsparce
 	dba Cry_Donphan
+	assert_table_length NUM_CRIES
--- a/audio/music_pointers.asm
+++ b/audio/music_pointers.asm
@@ -2,6 +2,7 @@
 
 Music:
 ; entries correspond to MUSIC_* constants
+	table_width 3, Music
 	dba Music_Nothing
 	dba Music_TitleScreen
 	dba Music_Route1
@@ -106,3 +107,4 @@
 	dba Music_SuicuneBattle
 	dba Music_BattleTowerLobby
 	dba Music_MobileCenter
+	assert_table_length NUM_MUSIC_SONGS
--- a/audio/sfx_pointers.asm
+++ b/audio/sfx_pointers.asm
@@ -1,5 +1,6 @@
 SFX:
 ; entries correspond to SFX_* constants
+	table_width 3, SFX
 	dba Sfx_DexFanfare5079
 	dba Sfx_Item
 	dba Sfx_CaughtMon
@@ -208,3 +209,4 @@
 	dba Sfx_TwoPcBeeps
 	dba Sfx_4NoteDitty
 	dba Sfx_Twinkle
+	assert_table_length NUM_SFX
--- a/constants.asm
+++ b/constants.asm
@@ -12,7 +12,6 @@
 INCLUDE "constants/audio_constants.asm"
 INCLUDE "constants/battle_anim_constants.asm"
 INCLUDE "constants/battle_constants.asm"
-INCLUDE "constants/battle_tower_constants.asm"
 INCLUDE "constants/collision_constants.asm"
 INCLUDE "constants/credits_constants.asm"
 INCLUDE "constants/cry_constants.asm"
@@ -50,3 +49,4 @@
 INCLUDE "constants/trainer_constants.asm"
 INCLUDE "constants/trainer_data_constants.asm"
 INCLUDE "constants/type_constants.asm"
+INCLUDE "constants/battle_tower_constants.asm"
--- a/constants/audio_constants.asm
+++ b/constants/audio_constants.asm
@@ -30,44 +30,45 @@
 NUM_CHANNELS EQU const_value
 
 ; channel_struct members (see macros/wram.asm)
-CHANNEL_MUSIC_ID                    EQUS "(wChannel1MusicID - wChannel1)"
-CHANNEL_MUSIC_BANK                  EQUS "(wChannel1MusicBank - wChannel1)"
-CHANNEL_FLAGS1                      EQUS "(wChannel1Flags1 - wChannel1)"
-CHANNEL_FLAGS2                      EQUS "(wChannel1Flags2 - wChannel1)"
-CHANNEL_FLAGS3                      EQUS "(wChannel1Flags3 - wChannel1)"
-CHANNEL_MUSIC_ADDRESS               EQUS "(wChannel1MusicAddress - wChannel1)"
-CHANNEL_LAST_MUSIC_ADDRESS          EQUS "(wChannel1LastMusicAddress - wChannel1)"
-CHANNEL_NOTE_FLAGS                  EQUS "(wChannel1NoteFlags - wChannel1)"
-CHANNEL_CONDITION                   EQUS "(wChannel1Condition - wChannel1)"
-CHANNEL_DUTY_CYCLE                  EQUS "(wChannel1DutyCycle - wChannel1)"
-CHANNEL_VOLUME_ENVELOPE             EQUS "(wChannel1VolumeEnvelope - wChannel1)"
-CHANNEL_FREQUENCY                   EQUS "(wChannel1Frequency - wChannel1)"
-CHANNEL_PITCH                       EQUS "(wChannel1Pitch - wChannel1)"
-CHANNEL_OCTAVE                      EQUS "(wChannel1Octave - wChannel1)"
-CHANNEL_TRANSPOSITION               EQUS "(wChannel1Transposition - wChannel1)"
-CHANNEL_NOTE_DURATION               EQUS "(wChannel1NoteDuration - wChannel1)"
-CHANNEL_FIELD16                     EQUS "(wChannel1Field16 - wChannel1)"
-CHANNEL_LOOP_COUNT                  EQUS "(wChannel1LoopCount - wChannel1)"
-CHANNEL_TEMPO                       EQUS "(wChannel1Tempo - wChannel1)"
-CHANNEL_TRACKS                      EQUS "(wChannel1Tracks - wChannel1)"
-CHANNEL_DUTY_CYCLE_PATTERN          EQUS "(wChannel1DutyCyclePattern - wChannel1)"
-CHANNEL_VIBRATO_DELAY_COUNT         EQUS "(wChannel1VibratoDelayCount - wChannel1)"
-CHANNEL_VIBRATO_DELAY               EQUS "(wChannel1VibratoDelay - wChannel1)"
-CHANNEL_VIBRATO_EXTENT              EQUS "(wChannel1VibratoExtent - wChannel1)"
-CHANNEL_VIBRATO_RATE                EQUS "(wChannel1VibratoRate - wChannel1)"
-CHANNEL_PITCH_SLIDE_TARGET          EQUS "(wChannel1PitchSlideTarget - wChannel1)"
-CHANNEL_PITCH_SLIDE_AMOUNT          EQUS "(wChannel1PitchSlideAmount - wChannel1)"
-CHANNEL_PITCH_SLIDE_AMOUNT_FRACTION EQUS "(wChannel1PitchSlideAmountFraction - wChannel1)"
-CHANNEL_FIELD25                     EQUS "(wChannel1Field25 - wChannel1)"
-CHANNEL_PITCH_OFFSET                EQUS "(wChannel1PitchOffset - wChannel1)"
-CHANNEL_FIELD29                     EQUS "(wChannel1Field29 - wChannel1)"
-CHANNEL_FIELD2A                     EQUS "(wChannel1Field2a - wChannel1)"
-CHANNEL_FIELD2C                     EQUS "(wChannel1Field2c - wChannel1)"
-CHANNEL_NOTE_LENGTH                 EQUS "(wChannel1NoteLength - wChannel1)"
-CHANNEL_FIELD2E                     EQUS "(wChannel1Field2e - wChannel1)"
-CHANNEL_FIELD2F                     EQUS "(wChannel1Field2f - wChannel1)"
-CHANNEL_FIELD30                     EQUS "(wChannel1Field30 - wChannel1)"
-CHANNEL_STRUCT_LENGTH               EQUS "(wChannel2 - wChannel1)"
+rsreset
+CHANNEL_MUSIC_ID                    rw
+CHANNEL_MUSIC_BANK                  rb
+CHANNEL_FLAGS1                      rb
+CHANNEL_FLAGS2                      rb
+CHANNEL_FLAGS3                      rb
+CHANNEL_MUSIC_ADDRESS               rw
+CHANNEL_LAST_MUSIC_ADDRESS          rw 2
+CHANNEL_NOTE_FLAGS                  rb
+CHANNEL_CONDITION                   rb
+CHANNEL_DUTY_CYCLE                  rb
+CHANNEL_VOLUME_ENVELOPE             rb
+CHANNEL_FREQUENCY                   rw
+CHANNEL_PITCH                       rb
+CHANNEL_OCTAVE                      rb
+CHANNEL_TRANSPOSITION               rb
+CHANNEL_NOTE_DURATION               rb
+CHANNEL_FIELD16                     rb 2
+CHANNEL_LOOP_COUNT                  rb
+CHANNEL_TEMPO                       rw
+CHANNEL_TRACKS                      rb
+CHANNEL_DUTY_CYCLE_PATTERN          rb
+CHANNEL_VIBRATO_DELAY_COUNT         rb
+CHANNEL_VIBRATO_DELAY               rb
+CHANNEL_VIBRATO_EXTENT              rb
+CHANNEL_VIBRATO_RATE                rb
+CHANNEL_PITCH_SLIDE_TARGET          rw
+CHANNEL_PITCH_SLIDE_AMOUNT          rb
+CHANNEL_PITCH_SLIDE_AMOUNT_FRACTION rb
+CHANNEL_FIELD25                     rb 2
+CHANNEL_PITCH_OFFSET                rw
+CHANNEL_FIELD29                     rb
+CHANNEL_FIELD2A                     rw
+CHANNEL_FIELD2C                     rb
+CHANNEL_NOTE_LENGTH                 rb
+CHANNEL_FIELD2E                     rb
+CHANNEL_FIELD2F                     rb
+CHANNEL_FIELD30                     rb 2
+CHANNEL_STRUCT_LENGTH EQU _RS
 
 NOISE_CHAN_F EQU 2 ; bit set in CHAN5-CHAN7
 
--- a/constants/battle_anim_constants.asm
+++ b/constants/battle_anim_constants.asm
@@ -7,6 +7,7 @@
 	const BATTLEANIMSTRUCT_FUNCTION
 	const BATTLEANIMSTRUCT_PALETTE
 	const BATTLEANIMSTRUCT_TILEID
+BATTLEANIMOBJ_LENGTH EQU const_value - 1 ; discount BATTLEANIMSTRUCT_INDEX
 	const BATTLEANIMSTRUCT_XCOORD
 	const BATTLEANIMSTRUCT_YCOORD
 	const BATTLEANIMSTRUCT_XOFFSET
@@ -217,6 +218,7 @@
 	const ANIM_OBJ_PLAYERHEAD_1ROW
 	const ANIM_OBJ_ENEMYFEET_2ROW
 	const ANIM_OBJ_PLAYERHEAD_2ROW
+NUM_ANIM_OBJS EQU const_value
 
 ; DoBattleAnimFrame arguments (see engine/battle_anims/functions.asm)
 	const_def
@@ -300,6 +302,7 @@
 	const BATTLEANIMFUNC_ANCIENT_POWER
 	const BATTLEANIMFUNC_ROCK_SMASH
 	const BATTLEANIMFUNC_COTTON
+NUM_BATTLEANIMFUNCS EQU const_value
 
 ; BattleAnimFrameData indexes (see data/battle_anims/framesets.asm)
 	const_def
@@ -488,6 +491,7 @@
 	const BATTLEANIMFRAMESET_B6
 	const BATTLEANIMFRAMESET_B7
 	const BATTLEANIMFRAMESET_B8
+NUM_BATTLEANIMFRAMESETS EQU const_value
 
 ; BattleAnimOAMData indexes (see data/battle_anims/oam.asm)
 	const_def
@@ -707,6 +711,7 @@
 	const BATTLEANIMOAMSET_D5
 	const BATTLEANIMOAMSET_D6
 	const BATTLEANIMOAMSET_D7
+NUM_BATTLEANIMOAMSETS EQU const_value
 
 ; BattleBGEffects indexes (see engine/battle_anims/bg_effects.asm)
 	const_def 1
@@ -763,6 +768,7 @@
 	const ANIM_BG_VIBRATE_MON
 	const ANIM_BG_WOBBLE_PLAYER
 	const ANIM_BG_WOBBLE_SCREEN
+NUM_ANIM_BGS EQU const_value - 1
 
 ; wBattleAnimTileDict keys (see wram.asm)
 ; AnimObjGFX indexes (see data/battle_anims/object_gfx.asm)
@@ -808,6 +814,7 @@
 	const ANIM_GFX_AEROBLAST
 	const ANIM_GFX_PLAYERHEAD
 	const ANIM_GFX_ENEMYFEET
+NUM_ANIM_GFX EQU const_value - 1
 
 ; battle_bg_effect struct members (see macros/wram.asm)
 	const_def
--- a/constants/battle_constants.asm
+++ b/constants/battle_constants.asm
@@ -120,6 +120,7 @@
 	const BATTLE_VARS_LAST_COUNTER_MOVE_OPP
 	const BATTLE_VARS_LAST_MOVE
 	const BATTLE_VARS_LAST_MOVE_OPP
+NUM_BATTLE_VARS EQU const_value
 
 ; BattleVarLocations indexes (see home/battle.asm)
 	const_def
@@ -149,6 +150,8 @@
 	const ENEMY_COUNTER_MOVE
 	const PLAYER_LAST_MOVE
 	const ENEMY_LAST_MOVE
+assert const_value % 2 == 0
+NUM_BATTLE_VAR_LOCATION_PAIRS EQU const_value / 2
 
 ; status condition bit flags
 SLP EQU %111 ; 0-7 turns
--- a/constants/battle_tower_constants.asm
+++ b/constants/battle_tower_constants.asm
@@ -5,7 +5,7 @@
 BATTLETOWER_NUM_UNIQUE_TRAINERS EQU 70
 
 BATTLETOWER_TRAINERDATALENGTH EQU $24
-BATTLE_TOWER_STRUCT_LENGTH EQUS "(NAME_LENGTH + BATTLETOWER_PARTY_LENGTH * NICKNAMED_MON_STRUCT_LENGTH + BATTLETOWER_TRAINERDATALENGTH)"
+BATTLE_TOWER_STRUCT_LENGTH EQU NAME_LENGTH + BATTLETOWER_PARTY_LENGTH * NICKNAMED_MON_STRUCT_LENGTH + BATTLETOWER_TRAINERDATALENGTH
 
 ; BattleTowerAction setval arguments (see engine/events/battle_tower/battle_tower.asm)
 	const_def
--- a/constants/credits_constants.asm
+++ b/constants/credits_constants.asm
@@ -103,6 +103,7 @@
 	const US_COORDINATION
 	const TEXT_TRANSLATION
 	const PAAD_TESTING
+NUM_CREDITS_STRINGS EQU const_value
 
 ; CreditsScript indexes (see data/credits_script.asm)
 	const_def -1, -1
--- a/constants/cry_constants.asm
+++ b/constants/cry_constants.asm
@@ -72,3 +72,5 @@
 	const CRY_AIPOM
 	const CRY_DUNSPARCE
 	const CRY_DONPHAN
+
+NUM_CRIES EQU const_value
--- a/constants/deco_constants.asm
+++ b/constants/deco_constants.asm
@@ -1,3 +1,13 @@
+; decoration attributes
+	const_def
+	const DECOATTR_TYPE
+	const DECOATTR_NAME
+	const DECOATTR_ACTION
+	const DECOATTR_EVENT_FLAG
+	const_skip ; high DECOATTR_EVENT_FLAG byte
+	const DECOATTR_SPRITE
+DECOATTR_STRUCT_LENGTH EQU const_value
+
 ; decoration types
 	const_def 1
 	const DECO_PLANT
@@ -6,6 +16,7 @@
 	const DECO_POSTER
 	const DECO_DOLL
 	const DECO_BIGDOLL
+NUM_DECO_TYPES EQU const_value - 1
 
 ; DecorationNames indexes (see data/decorations/names.asm)
 	const_def 1
@@ -51,8 +62,7 @@
 	const PUT_AWAY_BIG_DOLL
 	const SET_UP_DOLL
 	const PUT_AWAY_DOLL
-	const SET_UP_ORNAMENT
-	const PUT_AWAY_ORNAMENT
+NUM_DECO_ACTIONS EQU const_value - 1
 
 __deco_value__ = 0
 
@@ -127,3 +137,4 @@
 	deco  GOLD_TROPHY_DOLL
 	deco  SILVER_TROPHY_DOLL
 NUM_DECOS EQU __deco_value__
+NUM_DECO_CATEGORIES EQU const_value - 1 - NUM_DECOS
--- a/constants/gfx_constants.asm
+++ b/constants/gfx_constants.asm
@@ -6,7 +6,7 @@
 PAL_COLOR_SIZE EQU 2
 PALETTE_SIZE EQU NUM_PAL_COLORS * PAL_COLOR_SIZE
 
-PALRGB_WHITE EQUS "palred 31 + palgreen 31 + palblue 31" ; $7fff
+PALRGB_WHITE EQU palred 31 + palgreen 31 + palblue 31 ; $7fff
 
 SCREEN_WIDTH  EQU 20 ; tiles
 SCREEN_HEIGHT EQU 18 ; tiles
--- a/constants/icon_constants.asm
+++ b/constants/icon_constants.asm
@@ -39,6 +39,7 @@
 	const ICON_SLOWPOKE
 	const ICON_SUDOWOODO
 	const ICON_BIGMON
+NUM_ICONS EQU const_value - 1
 
 ; LoadMenuMonIcon.Jumptable indexes (see engine/gfx/mon_icons.asm)
 	const_def
--- a/constants/item_constants.asm
+++ b/constants/item_constants.asm
@@ -196,6 +196,7 @@
 	const MUSIC_MAIL   ; bc
 	const MIRAGE_MAIL  ; bd
 	const ITEM_BE      ; be
+NUM_ITEMS EQU const_value - 1
 
 __tmhm_value__ = 1
 
--- a/constants/landmark_constants.asm
+++ b/constants/landmark_constants.asm
@@ -1,6 +1,5 @@
 ; Landmarks indexes (see data/maps/landmarks.asm)
 	const_def
-
 ; Johto landmarks
 	const LANDMARK_SPECIAL           ; 00
 	const LANDMARK_NEW_BARK_TOWN     ; 01
@@ -49,7 +48,6 @@
 	const LANDMARK_DARK_CAVE         ; 2c
 	const LANDMARK_ROUTE_46          ; 2d
 	const LANDMARK_SILVER_CAVE       ; 2e
-
 KANTO_LANDMARK EQU const_value
 	const LANDMARK_PALLET_TOWN       ; 2f
 	const LANDMARK_ROUTE_1           ; 30
@@ -100,6 +98,7 @@
 	const LANDMARK_TOHJO_FALLS       ; 5d
 	const LANDMARK_ROUTE_28          ; 5e
 	const LANDMARK_FAST_SHIP         ; 5f
+NUM_LANDMARKS EQU const_value
 
 ; used in CaughtData
 	const_def $7f, -1
--- a/constants/map_data_constants.asm
+++ b/constants/map_data_constants.asm
@@ -50,6 +50,7 @@
 	const FISHGROUP_QWILFISH
 	const FISHGROUP_REMORAID
 	const FISHGROUP_QWILFISH_NO_SWARM
+NUM_FISHGROUPS EQU const_value - 1
 
 ; connection directions (see data/maps/data.asm)
 	const_def
@@ -66,8 +67,7 @@
 	shift_const NORTH
 
 ; SpawnPoints indexes (see data/maps/spawn_points.asm)
-	const_def -1
-	const SPAWN_N_A
+	const_def
 	const SPAWN_HOME
 	const SPAWN_DEBUG
 ; kanto
@@ -99,6 +99,8 @@
 	const SPAWN_MT_SILVER
 	const SPAWN_FAST_SHIP
 NUM_SPAWNS EQU const_value
+
+SPAWN_N_A EQU -1
 
 ; Flypoints indexes (see data/maps/flypoints.asm)
 	const_def
--- a/constants/map_object_constants.asm
+++ b/constants/map_object_constants.asm
@@ -199,6 +199,7 @@
 	const SPRITEMOVEFN_SPIN_COUNTERCLOCKWISE ; 19
 	const SPRITEMOVEFN_BOULDERDUST           ; 1a
 	const SPRITEMOVEFN_GRASS                 ; 1b
+NUM_SPRITEMOVEFN EQU const_value
 
 ; StepTypesJumptable indexes (see engine/overworld/map_objects.asm)
 	const_def
@@ -228,6 +229,7 @@
 	const STEP_TYPE_17               ; 17
 	const STEP_TYPE_DELETE           ; 18
 	const STEP_TYPE_SKYFALL_TOP      ; 19
+NUM_STEP_TYPES EQU const_value
 
 ; ObjectActionPairPointers indexes (see engine/overworld/map_object_action.asm)
 	const_def
@@ -248,6 +250,7 @@
 	const OBJECT_ACTION_BOULDER_DUST  ; 0e
 	const OBJECT_ACTION_GRASS_SHAKE   ; 0f
 	const OBJECT_ACTION_SKYFALL       ; 10
+NUM_OBJECT_ACTIONS EQU const_value
 
 ; Facings indexes (see data/sprites/facings.asm)
 	const_def
@@ -283,6 +286,7 @@
 	const FACING_BOULDER_DUST_2 ; 1d
 	const FACING_GRASS_1        ; 1e
 	const FACING_GRASS_2        ; 1f
+NUM_FACINGS EQU const_value
 
 ; DoPlayerMovement.DoStep arguments (see engine/overworld/player_movement.asm)
 	const_def
@@ -294,3 +298,4 @@
 	const STEP_TURN          ; 5
 	const STEP_BACK_LEDGE    ; 6
 	const STEP_WALK_IN_PLACE ; 7
+NUM_STEPS EQU const_value
--- a/constants/menu_constants.asm
+++ b/constants/menu_constants.asm
@@ -63,6 +63,7 @@
 	const MONMENUITEM_MOVE       ; 19
 	const MONMENUITEM_MAIL       ; 20
 	const MONMENUITEM_ERROR      ; 21
+NUM_MONMENUITEMS EQU const_value - 1
 
 ; MonMenuOptions categories
 MONMENU_FIELD_MOVE EQU 0
@@ -86,6 +87,7 @@
 	const PARTYMENUACTION_GIVE_MON_FEMALE ; unused
 	const PARTYMENUACTION_GIVE_ITEM
 	const PARTYMENUACTION_MOBILE ; mobile
+NUM_PARTYMENUACTIONS EQU const_value
 ; PrintPartyMenuActionText arguments (see engine/pokemon/party_menu.asm)
 	const_next $f0
 	const PARTYMENUTEXT_HEAL_PSN
--- a/constants/move_constants.asm
+++ b/constants/move_constants.asm
@@ -285,6 +285,7 @@
 	const ANIM_WOBBLE            ; 113
 	const ANIM_SHAKE             ; 114
 	const ANIM_HIT_CONFUSION     ; 115
+NUM_BATTLE_ANIMS EQU const_value - 1
 
 ; wNumHits uses offsets from ANIM_MISS
 	const_def
--- a/constants/move_effect_constants.asm
+++ b/constants/move_effect_constants.asm
@@ -157,3 +157,4 @@
 	const EFFECT_BEAT_UP
 	const EFFECT_FLY
 	const EFFECT_DEFENSE_CURL
+NUM_MOVE_EFECTS EQU const_value
--- a/constants/music_constants.asm
+++ b/constants/music_constants.asm
@@ -107,6 +107,7 @@
 	const MUSIC_SUICUNE_BATTLE               ; 64
 	const MUSIC_BATTLE_TOWER_LOBBY           ; 65
 	const MUSIC_MOBILE_CENTER                ; 66
+NUM_MUSIC_SONGS EQU const_value
 
 ; GetMapMusic picks music for this value (see home/map.asm)
 MUSIC_MAHOGANY_MART EQU $64
--- a/constants/npc_trade_constants.asm
+++ b/constants/npc_trade_constants.asm
@@ -1,14 +1,16 @@
 	; npctrade struct members (see data/events/npc_trades.asm)
-NPCTRADE_DIALOG  EQU  0 ; db
-NPCTRADE_GIVEMON EQU  1 ; db
-NPCTRADE_GETMON  EQU  2 ; db
-NPCTRADE_NICK    EQU  3 ; ds MON_NAME_LENGTH
-NPCTRADE_DVS     EQU 14 ; dw
-NPCTRADE_ITEM    EQU 16 ; db
-NPCTRADE_OT_ID   EQU 17 ; dw
-NPCTRADE_OT_NAME EQU 19 ; ds NAME_LENGTH
-NPCTRADE_GENDER  EQU 30 ; db
-NPCTRADE_PADDING EQU 31 ; db
+rsreset
+NPCTRADE_DIALOG  rb
+NPCTRADE_GIVEMON rb
+NPCTRADE_GETMON  rb
+NPCTRADE_NICK    rb MON_NAME_LENGTH
+NPCTRADE_DVS     rw
+NPCTRADE_ITEM    rb
+NPCTRADE_OT_ID   rw
+NPCTRADE_OT_NAME rb NAME_LENGTH
+NPCTRADE_GENDER  rb
+NPCTRADE_PADDING rb
+NPCTRADE_STRUCT_LENGTH EQU _RS
 
 ; NPCTrades indexes (see data/events/npc_trades.asm)
 	const_def
--- a/constants/phone_constants.asm
+++ b/constants/phone_constants.asm
@@ -38,6 +38,7 @@
 	const PHONE_HIKER_PARRY
 	const PHONE_PICNICKER_ERIN
 	const PHONE_BUENA
+NUM_PHONE_CONTACTS EQU const_value - 1
 
 ; SpecialPhoneCallList indexes (see data/phone/special_calls.asm)
 	const_def
@@ -50,6 +51,8 @@
 	const SPECIALCALL_BIKESHOP
 	const SPECIALCALL_WORRIED
 	const SPECIALCALL_MASTERBALL
+NUM_SPECIALCALLS EQU const_value - 1
+SPECIALCALL_SIZE EQU 6
 
 ; phone struct members
 	const_def
--- a/constants/pokemon_data_constants.asm
+++ b/constants/pokemon_data_constants.asm
@@ -1,30 +1,35 @@
 ; base data struct members (see data/pokemon/base_stats/*.asm)
-BASE_DEX_NO      EQUS "(wBaseDexNo - wCurBaseData)"
-BASE_STATS       EQUS "(wBaseStats - wCurBaseData)"
-BASE_HP          EQUS "(wBaseHP - wCurBaseData)"
-BASE_ATK         EQUS "(wBaseAttack - wCurBaseData)"
-BASE_SPD         EQUS "(wBaseSpeed - wCurBaseData)"
-BASE_SAT         EQUS "(wBaseSpecialAttack - wCurBaseData)"
-BASE_SDF         EQUS "(wBaseSpecialDefense - wCurBaseData)"
-BASE_TYPES       EQUS "(wBaseType - wCurBaseData)"
-BASE_TYPE_1      EQUS "(wBaseType1 - wCurBaseData)"
-BASE_TYPE_2      EQUS "(wBaseType2 - wCurBaseData)"
-BASE_CATCH_RATE  EQUS "(wBaseCatchRate - wCurBaseData)"
-BASE_EXP         EQUS "(wBaseExp - wCurBaseData)"
-BASE_ITEMS       EQUS "(wBaseItems - wCurBaseData)"
-BASE_ITEM_1      EQUS "(wBaseItem1 - wCurBaseData)"
-BASE_ITEM_2      EQUS "(wBaseItem2 - wCurBaseData)"
-BASE_GENDER      EQUS "(wBaseGender - wCurBaseData)"
-BASE_UNKNOWN_1   EQUS "(wBaseUnknown1 - wCurBaseData)"
-BASE_EGG_STEPS   EQUS "(wBaseEggSteps - wCurBaseData)"
-BASE_UNKNOWN_2   EQUS "(wBaseUnknown2 - wCurBaseData)"
-BASE_PIC_SIZE    EQUS "(wBasePicSize - wCurBaseData)"
-BASE_FRONTPIC    EQUS "(wBaseUnusedFrontpic - wCurBaseData)"
-BASE_BACKPIC     EQUS "(wBaseUnusedBackpic - wCurBaseData)"
-BASE_GROWTH_RATE EQUS "(wBaseGrowthRate - wCurBaseData)"
-BASE_EGG_GROUPS  EQUS "(wBaseEggGroups - wCurBaseData)"
-BASE_TMHM        EQUS "(wBaseTMHM - wCurBaseData)"
-BASE_DATA_SIZE   EQUS "(wCurBaseDataEnd - wCurBaseData)"
+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_SAT         rb
+BASE_SDF         rb
+BASE_TYPES       rw
+rsset BASE_TYPES
+BASE_TYPE_1      rb
+BASE_TYPE_2      rb
+BASE_CATCH_RATE  rb
+BASE_EXP         rb
+BASE_ITEMS       rw
+rsset BASE_ITEMS
+BASE_ITEM_1      rb
+BASE_ITEM_2      rb
+BASE_GENDER      rb
+BASE_UNKNOWN_1   rb
+BASE_EGG_STEPS   rb
+BASE_UNKNOWN_2   rb
+BASE_PIC_SIZE    rb
+BASE_FRONTPIC    rw
+BASE_BACKPIC     rw
+BASE_GROWTH_RATE rb
+BASE_EGG_GROUPS  rb
+BASE_TMHM        rb (NUM_TM_HM_TUTOR + 7) / 8
+BASE_DATA_SIZE EQU _RS
 
 ; gender ratio constants
 GENDER_F0      EQU   0 percent
@@ -67,39 +72,43 @@
 NUM_DEX_ENTRY_BANKS EQU 4
 
 ; party_struct members (see macros/wram.asm)
-MON_SPECIES            EQUS "(wPartyMon1Species - wPartyMon1)"
-MON_ITEM               EQUS "(wPartyMon1Item - wPartyMon1)"
-MON_MOVES              EQUS "(wPartyMon1Moves - wPartyMon1)"
-MON_ID                 EQUS "(wPartyMon1ID - wPartyMon1)"
-MON_EXP                EQUS "(wPartyMon1Exp - wPartyMon1)"
-MON_STAT_EXP           EQUS "(wPartyMon1StatExp - wPartyMon1)"
-MON_HP_EXP             EQUS "(wPartyMon1HPExp - wPartyMon1)"
-MON_ATK_EXP            EQUS "(wPartyMon1AtkExp - wPartyMon1)"
-MON_DEF_EXP            EQUS "(wPartyMon1DefExp - wPartyMon1)"
-MON_SPD_EXP            EQUS "(wPartyMon1SpdExp - wPartyMon1)"
-MON_SPC_EXP            EQUS "(wPartyMon1SpcExp - wPartyMon1)"
-MON_DVS                EQUS "(wPartyMon1DVs - wPartyMon1)"
-MON_PP                 EQUS "(wPartyMon1PP - wPartyMon1)"
-MON_HAPPINESS          EQUS "(wPartyMon1Happiness - wPartyMon1)"
-MON_PKRUS              EQUS "(wPartyMon1PokerusStatus - wPartyMon1)"
-MON_CAUGHTDATA         EQUS "(wPartyMon1CaughtData - wPartyMon1)"
-MON_CAUGHTLEVEL        EQUS "(wPartyMon1CaughtLevel - wPartyMon1)"
-MON_CAUGHTTIME         EQUS "(wPartyMon1CaughtTime - wPartyMon1)"
-MON_CAUGHTGENDER       EQUS "(wPartyMon1CaughtGender - wPartyMon1)"
-MON_CAUGHTLOCATION     EQUS "(wPartyMon1CaughtLocation - wPartyMon1)"
-MON_LEVEL              EQUS "(wPartyMon1Level - wPartyMon1)"
-MON_STATUS             EQUS "(wPartyMon1Status - wPartyMon1)"
-MON_HP                 EQUS "(wPartyMon1HP - wPartyMon1)"
-MON_MAXHP              EQUS "(wPartyMon1MaxHP - wPartyMon1)"
-MON_ATK                EQUS "(wPartyMon1Attack - wPartyMon1)"
-MON_DEF                EQUS "(wPartyMon1Defense - wPartyMon1)"
-MON_SPD                EQUS "(wPartyMon1Speed - wPartyMon1)"
-MON_SAT                EQUS "(wPartyMon1SpclAtk - wPartyMon1)"
-MON_SDF                EQUS "(wPartyMon1SpclDef - wPartyMon1)"
-BOXMON_STRUCT_LENGTH   EQUS "(wPartyMon1BoxEnd - wPartyMon1)"
-PARTYMON_STRUCT_LENGTH EQUS "(wPartyMon1StructEnd - wPartyMon1)"
+rsreset
+MON_SPECIES            rb
+MON_ITEM               rb
+MON_MOVES              rb NUM_MOVES
+MON_ID                 rw
+MON_EXP                rb 3
+MON_STAT_EXP           rw NUM_EXP_STATS
+rsset MON_STAT_EXP
+MON_HP_EXP             rw
+MON_ATK_EXP            rw
+MON_DEF_EXP            rw
+MON_SPD_EXP            rw
+MON_SPC_EXP            rw
+MON_DVS                rw
+MON_PP                 rb NUM_MOVES
+MON_HAPPINESS          rb
+MON_PKRUS              rb
+MON_CAUGHTDATA         rw
+rsset MON_CAUGHTDATA
+MON_CAUGHTTIME         rb
+MON_CAUGHTGENDER       rb
+rsset MON_CAUGHTDATA
+MON_CAUGHTLEVEL        rb
+MON_CAUGHTLOCATION     rb
+MON_LEVEL              rb
+BOXMON_STRUCT_LENGTH EQU _RS
+MON_STATUS             rb 2
+MON_HP                 rw
+MON_MAXHP              rw
+MON_ATK                rw
+MON_DEF                rw
+MON_SPD                rw
+MON_SAT                rw
+MON_SDF                rw
+PARTYMON_STRUCT_LENGTH EQU _RS
 
-NICKNAMED_MON_STRUCT_LENGTH EQUS "(PARTYMON_STRUCT_LENGTH + MON_NAME_LENGTH)"
+NICKNAMED_MON_STRUCT_LENGTH EQU PARTYMON_STRUCT_LENGTH + MON_NAME_LENGTH
 REDMON_STRUCT_LENGTH EQU 44
 
 ; caught data
@@ -116,6 +125,8 @@
 
 CAUGHT_EGG_LEVEL EQU 1
 
+MON_CRY_LENGTH EQU 6
+
 ; maximum number of party pokemon
 PARTY_LENGTH EQU 6
 
@@ -199,6 +210,7 @@
 	const HAPPINESS_REVIVALHERB       ; 11
 	const HAPPINESS_GROOMING          ; 12
 	const HAPPINESS_GAINLEVELATHOME   ; 13
+NUM_HAPPINESS_CHANGES EQU const_value - 1
 
 ; significant happiness values
 BASE_HAPPINESS        EQU 70
--- a/constants/radio_constants.asm
+++ b/constants/radio_constants.asm
@@ -15,6 +15,7 @@
 	const POKE_FLUTE_RADIO       ; 08
 	const UNOWN_RADIO            ; 09
 	const EVOLUTION_RADIO        ; 0a
+NUM_RADIO_CHANNELS EQU const_value
 ; internal indexes for channel segments
 	const OAKS_POKEMON_TALK_2    ; 0b
 	const OAKS_POKEMON_TALK_3    ; 0c
@@ -93,6 +94,7 @@
 	const POKEDEX_SHOW_6         ; 55
 	const POKEDEX_SHOW_7         ; 56
 	const POKEDEX_SHOW_8         ; 57
+NUM_RADIO_SEGMENTS EQU const_value
 
 ; PlayRadio.StationPointers indexes (see engine/pokegear/pokegear.asm)
 	const_def
@@ -105,9 +107,6 @@
 	const MAPRADIO_PLACES_PEOPLE
 	const MAPRADIO_LETS_ALL_SING
 	const MAPRADIO_ROCKET
-
-; OaksPKMNTalkRoutes size (see data/radio/oaks_pkmn_talk_routes.asm)
-NUM_OAKS_POKEMON_TALK_ROUTES EQU 15
 
 ; These tables in engine/pokegear/radio.asm are all sized to a power of 2
 ; so there's no need for a rejection sampling loop
--- a/constants/script_constants.asm
+++ b/constants/script_constants.asm
@@ -175,6 +175,7 @@
 	const FLOOR_10F
 	const FLOOR_11F
 	const FLOOR_ROOF
+NUM_FLOORS EQU const_value
 
 ; showemote arguments
 ; Emotes indexes (see data/sprites/emotes.asm)
@@ -191,7 +192,9 @@
 	const EMOTE_ROD ; 9
 	const EMOTE_BOULDER_DUST ; 10
 	const EMOTE_GRASS_RUSTLE ; 11
+NUM_EMOTES EQU const_value
 EMOTE_FROM_MEM EQU -1
+EMOTE_LENGTH EQU 6
 
 ; fruittree arguments
 ; FruitTreeItems indexes (see data/items/fruit_trees.asm)
@@ -236,6 +239,7 @@
 	const DECODESC_RIGHT_DOLL ; 2
 	const DECODESC_BIG_DOLL   ; 3
 	const DECODESC_CONSOLE    ; 4
+NUM_DECODESCS EQU const_value
 
 ; swarm arguments
 ; StoreSwarmMapIndices arguments
--- a/constants/sfx_constants.asm
+++ b/constants/sfx_constants.asm
@@ -210,3 +210,5 @@
 	const SFX_TWO_PC_BEEPS                ; cc
 	const SFX_4_NOTE_DITTY                ; cd
 	const SFX_TWINKLE                     ; ce
+
+NUM_SFX EQU const_value
--- a/constants/sprite_anim_constants.asm
+++ b/constants/sprite_anim_constants.asm
@@ -79,6 +79,7 @@
 	const SPRITE_ANIM_INDEX_INTRO_UNOWN_F             ; 2a
 	const SPRITE_ANIM_INDEX_INTRO_SUICUNE_AWAY        ; 2b
 	const SPRITE_ANIM_INDEX_CELEBI                    ; 2c
+NUM_SPRITE_ANIM_INDEXES EQU const_value
 
 ; DoAnimFrame.Jumptable indexes (see engine/gfx/sprite_anims.asm)
 	const_def
@@ -117,6 +118,7 @@
 	const SPRITE_ANIM_SEQ_INTRO_UNOWN               ; 20
 	const SPRITE_ANIM_SEQ_INTRO_UNOWN_F             ; 21
 	const SPRITE_ANIM_SEQ_INTRO_SUICUNE_AWAY        ; 22
+NUM_SPRITE_ANIM_SEQS EQU const_value
 
 ; SpriteAnimFrameData indexes (see data/sprite_anims/framesets.asm)
 	const_def
@@ -186,6 +188,7 @@
 	const SPRITE_ANIM_FRAMESET_INTRO_UNOWN_F             ; 3f
 	const SPRITE_ANIM_FRAMESET_CELEBI_LEFT               ; 40
 	const SPRITE_ANIM_FRAMESET_CELEBI_RIGHT              ; 41
+NUM_SPRITE_ANIM_FRAMESETS EQU const_value
 
 ; SpriteAnimOAMData indexes (see data/sprite_anims/oam.asm)
 	const_def
@@ -329,3 +332,4 @@
 	const SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_9            ; 89
 	const SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_10           ; 8a
 	const SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_11           ; 8b
+NUM_SPRITE_ANIM_OAMSETS EQU const_value
--- a/constants/sprite_constants.asm
+++ b/constants/sprite_constants.asm
@@ -104,6 +104,7 @@
 	const SPRITE_ENTEI ; 64
 	const SPRITE_RAIKOU ; 65
 	const SPRITE_STANDING_YOUNGSTER ; 66
+NUM_OVERWORLD_SPRITES EQU const_value - 1
 
 ; SpriteMons indexes (see data/sprites/sprite_mons.asm)
 	const_next $80
@@ -143,6 +144,7 @@
 	const SPRITE_GYARADOS ; a0
 	const SPRITE_LUGIA ; a1
 	const SPRITE_HO_OH ; a2
+NUM_POKEMON_SPRITES EQU const_value - SPRITE_POKEMON
 
 ; special GetMonSprite values (see engine/overworld/overworld.asm)
 	const_next $e0
--- a/constants/tileset_constants.asm
+++ b/constants/tileset_constants.asm
@@ -36,6 +36,13 @@
 	const TILESET_KABUTO_WORD_ROOM     ; 22
 	const TILESET_OMANYTE_WORD_ROOM    ; 23
 	const TILESET_AERODACTYL_WORD_ROOM ; 24
+NUM_TILESETS EQU const_value - 1
+
+; wTileset struct size
+TILESET_LENGTH EQU 15
+
+; roof length (see gfx/tilesets/roofs)
+ROOF_LENGTH EQU 9
 
 ; bg palette values (see gfx/tilesets/*_palette_map.asm)
 ; TilesetBGPalette indexes (see gfx/tilesets/bg_tiles.pal)
--- a/constants/trainer_constants.asm
+++ b/constants/trainer_constants.asm
@@ -25,6 +25,7 @@
 	const PHONECONTACT_BILL
 	const PHONECONTACT_ELM
 	const PHONECONTACT_BUENA
+NUM_NONTRAINER_PHONECONTACTS EQU const_value - 1
 
 KRIS EQU __trainer_class__
 	trainerclass FALKNER ; 1
@@ -702,4 +703,4 @@
 	trainerclass MYSTICALMAN ; 43
 	const EUSINE
 
-NUM_TRAINER_CLASSES EQU __trainer_class__
+NUM_TRAINER_CLASSES EQU __trainer_class__ - 1
--- a/constants/type_constants.asm
+++ b/constants/type_constants.asm
@@ -34,4 +34,6 @@
 	const DARK
 TYPES_END EQU const_value
 
-NUM_TYPES EQU TYPES_END + UNUSED_TYPES - UNUSED_TYPES_END
+NUM_TYPES EQU TYPES_END + UNUSED_TYPES - UNUSED_TYPES_END - 1 ; discount BIRD
+
+POKEDEX_TYPE_STRING_LENGTH EQU 9
--- a/data/battle/effect_command_pointers.asm
+++ b/data/battle/effect_command_pointers.asm
@@ -4,6 +4,7 @@
 
 BattleCommandPointers:
 ; entries correspond to macros/scripts/battle_commands.asm
+	table_width 2, BattleCommandPointers
 	dw BattleCommand_CheckTurn
 	dw BattleCommand_CheckObedience
 	dw BattleCommand_UsedMoveText
@@ -179,3 +180,4 @@
 	dw BattleCommand_SuperEffectiveLoopText
 	dw BattleCommand_StartLoop
 	dw BattleCommand_Curl
+	assert_table_length NUM_EFFECT_COMMANDS
--- a/data/battle_anims/framesets.asm
+++ b/data/battle_anims/framesets.asm
@@ -1,5 +1,6 @@
 BattleAnimFrameData:
 ; entries correspond to BATTLEANIMFRAMESET_* constants
+	table_width 2, BattleAnimFrameData
 	dw .Frameset_00 ; BATTLEANIMFRAMESET_00
 	dw .Frameset_01 ; BATTLEANIMFRAMESET_01
 	dw .Frameset_02 ; BATTLEANIMFRAMESET_02
@@ -185,6 +186,7 @@
 	dw .Frameset_b6 ; BATTLEANIMFRAMESET_B6
 	dw .Frameset_b7 ; BATTLEANIMFRAMESET_B7
 	dw .Frameset_b8 ; BATTLEANIMFRAMESET_B8
+	assert_table_length NUM_BATTLEANIMFRAMESETS
 
 .Frameset_00:
 	frame BATTLEANIMOAMSET_00,  6
--- a/data/battle_anims/oam.asm
+++ b/data/battle_anims/oam.asm
@@ -1,5 +1,6 @@
 BattleAnimOAMData:
 ; entries correspond to BATTLEANIMOAMSET_* constants
+	table_width 4, BattleAnimOAMData
 	; vtile offset, data length, data pointer
 	dbbw $00, 16, .OAMData_00 ; BATTLEANIMOAMSET_00
 	dbbw $04,  9, .OAMData_01 ; BATTLEANIMOAMSET_01
@@ -217,6 +218,7 @@
 	dbbw $00,  6, .OAMData_d5 ; BATTLEANIMOAMSET_D5
 	dbbw $00, 14, .OAMData_d6 ; BATTLEANIMOAMSET_D6
 	dbbw $00, 12, .OAMData_d7 ; BATTLEANIMOAMSET_D7
+	assert_table_length NUM_BATTLEANIMOAMSETS
 
 .OAMData_11:
 	dbsprite  -1,  -1, 4, 0, $00, $0
--- a/data/battle_anims/object_gfx.asm
+++ b/data/battle_anims/object_gfx.asm
@@ -6,6 +6,7 @@
 
 AnimObjGFX:
 ; entries correspond to ANIM_GFX_* constants
+	table_width 4, AnimObjGFX
 	anim_obj_gfx  0, AnimObj00GFX
 	anim_obj_gfx 21, AnimObjHitGFX
 	anim_obj_gfx  6, AnimObjCutGFX
@@ -48,3 +49,4 @@
 	anim_obj_gfx 24, AnimObjAeroblastGFX
 	anim_obj_gfx  1, NULL
 	anim_obj_gfx  1, NULL
+	assert_table_length NUM_ANIM_GFX + 1
--- a/data/battle_anims/objects.asm
+++ b/data/battle_anims/objects.asm
@@ -18,6 +18,7 @@
 
 BattleAnimObjects:
 ; entries correspond to ANIM_OBJ_* constants
+	table_width BATTLEANIMOBJ_LENGTH, BattleAnimObjects
 ; ANIM_OBJ_HIT_BIG_YFIX
 	battleanimobj RELATIVE_X, $ff, BATTLEANIMFRAMESET_00, BATTLEANIMFUNC_NULL, PAL_BATTLE_OB_GRAY, ANIM_GFX_HIT
 ; ANIM_OBJ_HIT_YFIX
@@ -394,3 +395,4 @@
 	battleanimobj ABSOLUTE_X, $00, BATTLEANIMFRAMESET_B7, BATTLEANIMFUNC_NULL, PAL_BATTLE_OB_ENEMY, ANIM_GFX_PLAYERHEAD
 ; ANIM_OBJ_PLAYERHEAD_2ROW
 	battleanimobj ABSOLUTE_X, $00, BATTLEANIMFRAMESET_B8, BATTLEANIMFUNC_NULL, PAL_BATTLE_OB_PLAYER, ANIM_GFX_ENEMYFEET
+	assert_table_length NUM_ANIM_OBJS
--- a/data/battle_tower/classes.asm
+++ b/data/battle_tower/classes.asm
@@ -2,6 +2,8 @@
 ; The trainer class is not used in Crystal 1.0 due to a bug.
 ; Instead, the sixth character in the trainer's name is used.
 ; See BattleTowerText in engine/events/battle_tower/trainer_text.asm.
+	table_width (NAME_LENGTH - 1) + 1, BattleTowerTrainers
+	; name, class
 	db "HANSON@@@@", FISHER
 	db "SAWYER@@@@", POKEMANIAC
 	db "MASUDA@@@@", GUITARIST
@@ -23,6 +25,7 @@
 	db "KAUFMAN@@@", SWIMMERM
 	db "LANCASTER@", SKIER
 	db "McMAHILL@@", CAMPER
+	assert_table_length BATTLETOWER_NUM_UNIQUE_MON
 ; The following can only be sampled in Crystal 1.1.
 	db "OBRIEN@@@@", GENTLEMAN
 	db "FROST@@@@@", BEAUTY
@@ -73,3 +76,4 @@
 	db "DYKSTRA@@@", SWIMMERF
 	db "EATON@@@@@", BIKER
 	db "WONG@@@@@@", FIREBREATHER
+	assert_table_length BATTLETOWER_NUM_UNIQUE_TRAINERS
--- a/data/battle_tower/parties.asm
+++ b/data/battle_tower/parties.asm
@@ -1,5 +1,6 @@
 BattleTowerMons:
 ; 10 groups (one per floor level) of 21 mons (BATTLETOWER_NUM_UNIQUE_MON).
+	table_width NICKNAMED_MON_STRUCT_LENGTH, BattleTowerMons
 
 ; BattleTowerMons group 1
 
@@ -5489,3 +5490,5 @@
 	bigdw 214 ; SAtk
 	bigdw 214 ; SDef
 	db "RANTA-N@@@@"
+
+	assert_table_length 10 * BATTLETOWER_NUM_UNIQUE_MON
--- a/data/battle_tower/unknown.asm
+++ b/data/battle_tower/unknown.asm
@@ -1,6 +1,7 @@
 BattleTowerTrainerData:: ; ???
 ; 70 tables (BATTLETOWER_NUM_UNIQUE_TRAINERS) of 18 two-argument entries (total size BATTLETOWER_TRAINERDATALENGTH).
 ; Second argument points to an entry in Unknown_170470, to which the first argument is compared.
+	table_width BATTLETOWER_TRAINERDATALENGTH, BattleTowerTrainerData
 
 ; BattleTowerTrainerData table 1:
 	db $37,  3
@@ -1401,3 +1402,5 @@
 	db $22,  7
 	db $d1,  0
 	db $32, 13
+
+	assert_table_length BATTLETOWER_NUM_UNIQUE_TRAINERS
--- a/data/collision/collision_permissions.asm
+++ b/data/collision/collision_permissions.asm
@@ -1,5 +1,6 @@
 TileCollisionTable::
 ; entries correspond to COLL_* constants
+	table_width 1, TileCollisionTable
 	db LAND_TILE         ; COLL_FLOOR
 	db LAND_TILE         ; COLL_01
 	db LAND_TILE         ; 02
@@ -256,3 +257,4 @@
 	db LAND_TILE         ; fd
 	db LAND_TILE         ; fe
 	db WALL_TILE         ; COLL_FF
+	assert_table_length $100
--- a/data/credits_strings.asm
+++ b/data/credits_strings.asm
@@ -1,5 +1,6 @@
 CreditsStringsPointers:
 ; entries correspond to constants/credits_constants.asm
+	table_width 2, CreditsStringsPointers
 	dw .SatoshiTajiri
 	dw .JunichiMasuda
 	dw .TetsuyaWatanabe
@@ -103,6 +104,7 @@
 	dw .UsCoordination
 	dw .TextTranslation
 	dw .PaadTesting
+	assert_table_length NUM_CREDITS_STRINGS
 
 .SatoshiTajiri:       db "   SATOSHI TAJIRI@"         ; "たじり さとし@"
 .JunichiMasuda:       db "   JUNICHI MASUDA@"         ; "ますだ じゅんいち@"
--- a/data/decorations/attributes.asm
+++ b/data/decorations/attributes.asm
@@ -1,5 +1,5 @@
 decoration: MACRO
-	; type, name, command, event flag, tile/sprite
+	; type, name, action, event flag, tile/sprite
 	db \1, \2, \3
 	dw \4
 	db \5
@@ -7,6 +7,7 @@
 
 DecorationAttributes:
 ; entries correspond to deco constants
+	table_width DECOATTR_STRUCT_LENGTH, DecorationAttributes
 	decoration DECO_PLANT,   0,               0,                 EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1, 0
 	decoration DECO_PLANT,   PUT_IT_AWAY,     PUT_AWAY_BED,      EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1, 0
 	decoration DECO_BED,     FEATHERY_BED,    SET_UP_BED,        EVENT_DECO_BED_1,                   $1b
@@ -60,3 +61,4 @@
 	decoration DECO_DOLL,    TENTACOOL,       SET_UP_DOLL,       EVENT_DECO_TENTACOOL_DOLL,          SPRITE_TENTACOOL
 	decoration DECO_PLANT,   GOLD_TROPHY,     SET_UP_DOLL,       EVENT_DECO_GOLD_TROPHY,             SPRITE_GOLD_TROPHY
 	decoration DECO_PLANT,   SILVER_TROPHY,   SET_UP_DOLL,       EVENT_DECO_SILVER_TROPHY,           SPRITE_SILVER_TROPHY
+	assert_table_length NUM_DECOS + NUM_DECO_CATEGORIES + 1
--- a/data/decorations/decorations.asm
+++ b/data/decorations/decorations.asm
@@ -1,5 +1,7 @@
 DecorationIDs:
 ; see constants/deco_constants.asm
+	table_width 1, DecorationIDs
+
 	db DECO_FEATHERY_BED ; 2
 	db DECO_PINK_BED ; 3
 	db DECO_POLKADOT_BED ; 4
@@ -52,4 +54,5 @@
 	db DECO_GOLD_TROPHY_DOLL ; 33
 	db DECO_SILVER_TROPHY_DOLL ; 34
 
+	assert_table_length NUM_DECOS
 	db -1 ; end
--- a/data/default_options.asm
+++ b/data/default_options.asm
@@ -14,3 +14,5 @@
 
 	db $00
 	db $00
+.End
+	assert DefaultOptions.End - DefaultOptions == wOptionsEnd - wOptions
--- a/data/events/bug_contest_flags.asm
+++ b/data/events/bug_contest_flags.asm
@@ -1,5 +1,5 @@
 BugCatchingContestantEventFlagTable:
-; there are NUM_BUG_CONTESTANTS entries
+	table_width 2, BugCatchingContestantEventFlagTable
 	dw EVENT_BUG_CATCHING_CONTESTANT_1A
 	dw EVENT_BUG_CATCHING_CONTESTANT_2A
 	dw EVENT_BUG_CATCHING_CONTESTANT_3A
@@ -10,3 +10,4 @@
 	dw EVENT_BUG_CATCHING_CONTESTANT_8A
 	dw EVENT_BUG_CATCHING_CONTESTANT_9A
 	dw EVENT_BUG_CATCHING_CONTESTANT_10A
+	assert_table_length NUM_BUG_CONTESTANTS
--- a/data/events/bug_contest_winners.asm
+++ b/data/events/bug_contest_winners.asm
@@ -1,5 +1,5 @@
 BugContestantPointers:
-; there are NUM_BUG_CONTESTANTS + 1 entries
+	table_width 2, BugContestantPointers
 	dw BugContestant_BugCatcherDon ; this reverts back to the player
 	dw BugContestant_BugCatcherDon
 	dw BugContestant_BugCatcherEd
@@ -11,6 +11,7 @@
 	dw BugContestant_BugCatcherJosh
 	dw BugContestant_YoungsterSamuel
 	dw BugContestant_SchoolboyKipp
+	assert_table_length NUM_BUG_CONTESTANTS + 1
 
 ; contestant format:
 ;   db class, id
--- a/data/events/elevator_floors.asm
+++ b/data/events/elevator_floors.asm
@@ -1,5 +1,6 @@
 ElevatorFloorNames:
 ; entries correspond to FLOOR_* constants
+	table_width 2, ElevatorFloorNames
 	dw .B4F
 	dw .B3F
 	dw .B2F
@@ -16,6 +17,7 @@
 	dw ._10F
 	dw ._11F
 	dw .ROOF
+	assert_table_length NUM_FLOORS
 
 .B4F:  db "B4F@"
 .B3F:  db "B3F@"
--- a/data/events/engine_flags.asm
+++ b/data/events/engine_flags.asm
@@ -6,6 +6,7 @@
 
 EngineFlags:
 ; entries correspond to ENGINE_* constants
+	table_width 3, EngineFlags
 
 	; pokegear
 	engine_flag wPokegearFlags, POKEGEAR_RADIO_CARD_F
@@ -197,3 +198,5 @@
 
 	engine_flag wSwarmFlags, SWARMFLAGS_DUNSPARCE_SWARM_F
 	engine_flag wSwarmFlags, SWARMFLAGS_YANMA_SWARM_F
+
+	assert_table_length NUM_ENGINE_FLAGS
--- a/data/events/happiness_changes.asm
+++ b/data/events/happiness_changes.asm
@@ -1,6 +1,7 @@
 HappinessChanges:
 ; entries correspond to HAPPINESS_* constants
-; change if happiness < 100, change if happiness < 200, change otherwise
+	table_width 3, HappinessChanges
+	; change if happiness < 100, change if happiness < 200, change otherwise
 	db  +5,  +3,  +2 ; Gained a level
 	db  +5,  +3,  +2 ; Vitamin
 	db  +1,  +1,  +0 ; X Item
@@ -20,3 +21,4 @@
 	db -15, -15, -20 ; Used Revival Herb (bitter)
 	db  +3,  +3,  +1 ; Grooming
 	db +10,  +6,  +4 ; Gained a level in the place where it was caught
+	assert_table_length NUM_HAPPINESS_CHANGES
--- a/data/events/npc_trades.asm
+++ b/data/events/npc_trades.asm
@@ -8,6 +8,7 @@
 
 NPCTrades:
 ; entries correspond to NPCTRADE_* constants
+	table_width NPCTRADE_STRUCT_LENGTH, NPCTrades
 	npctrade TRADE_DIALOGSET_COLLECTOR, ABRA,       MACHOP,     "MUSCLE@@@@@", $37, $66, GOLD_BERRY,   37460, "MIKE@@@@@@@", TRADE_GENDER_EITHER
 	npctrade TRADE_DIALOGSET_COLLECTOR, BELLSPROUT, ONIX,       "ROCKY@@@@@@", $96, $66, BITTER_BERRY, 48926, "KYLE@@@@@@@", TRADE_GENDER_EITHER
 	npctrade TRADE_DIALOGSET_HAPPY,     KRABBY,     VOLTORB,    "VOLTY@@@@@@", $98, $88, PRZCUREBERRY, 29189, "TIM@@@@@@@@", TRADE_GENDER_EITHER
@@ -15,3 +16,4 @@
 	npctrade TRADE_DIALOGSET_NEWBIE,    HAUNTER,    XATU,       "PAUL@@@@@@@", $96, $86, MYSTERYBERRY, 15616, "CHRIS@@@@@@", TRADE_GENDER_EITHER
 	npctrade TRADE_DIALOGSET_GIRL,      CHANSEY,    AERODACTYL, "AEROY@@@@@@", $96, $66, GOLD_BERRY,   26491, "KIM@@@@@@@@", TRADE_GENDER_EITHER
 	npctrade TRADE_DIALOGSET_COLLECTOR, DUGTRIO,    MAGNETON,   "MAGGIE@@@@@", $96, $66, METAL_COAT,   50082, "FOREST@@@@@", TRADE_GENDER_EITHER
+	assert_table_length NUM_NPC_TRADES
--- a/data/events/odd_eggs.asm
+++ b/data/events/odd_eggs.asm
@@ -1,3 +1,5 @@
+NUM_ODD_EGGS EQU 14
+
 prob: MACRO
 prob_total = prob_total + (\1)
 	dw prob_total * $ffff / 100
@@ -4,6 +6,7 @@
 ENDM
 
 OddEggProbabilities:
+	table_width 2, OddEggProbabilities
 prob_total = 0
 ; Pichu
 	prob 8
@@ -26,8 +29,10 @@
 ; Tyrogue
 	prob 10
 	prob 1
+	assert_table_length NUM_ODD_EGGS
 
 OddEggs:
+	table_width NICKNAMED_MON_STRUCT_LENGTH, OddEggs
 
 	db PICHU
 	db NO_ITEM
@@ -392,3 +397,5 @@
 	bigdw 9 ; SAtk
 	bigdw 9 ; SDef
 	db "EGG@@@@@@@@"
+
+	assert_table_length NUM_ODD_EGGS
--- a/data/icon_pointers.asm
+++ b/data/icon_pointers.asm
@@ -1,5 +1,6 @@
 IconPointers:
-; entries correspond to ICON_* constants
+; entries correspond to ICON_* constants (see constants/icon_constants.asm)
+	table_width 2, IconPointers
 	dw NullIcon
 	dw PoliwagIcon
 	dw JigglypuffIcon
@@ -39,3 +40,4 @@
 	dw SlowpokeIcon
 	dw SudowoodoIcon
 	dw BigmonIcon
+	assert_table_length NUM_ICONS + 1
--- a/data/items/attributes.asm
+++ b/data/items/attributes.asm
@@ -6,7 +6,8 @@
 ENDM
 
 ItemAttributes:
-; entries correspond to item ids
+; entries correspond to item ids (see constants/item_constants.asm)
+	table_width ITEMATTR_STRUCT_LENGTH, ItemAttributes
 ; MASTER_BALL
 	item_attribute 0, HELD_NONE, 0, CANT_SELECT, BALL, ITEMMENU_NOUSE, ITEMMENU_CLOSE
 ; ULTRA_BALL
@@ -387,6 +388,7 @@
 	item_attribute 50, HELD_NONE, 0, CANT_SELECT, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
 ; ITEM_BE
 	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
+	assert_table_length NUM_ITEMS
 ; TM01
 	item_attribute 3000, HELD_NONE, 0, CANT_SELECT, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
 ; TM02
@@ -491,6 +493,7 @@
 	item_attribute 3000, HELD_NONE, 0, CANT_SELECT, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
 ; TM50
 	item_attribute 2000, HELD_NONE, 0, CANT_SELECT, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
+	assert_table_length NUM_ITEMS + NUM_TMS + 2 ; count ITEM_C3 and ITEM_DC
 ; HM01
 	item_attribute 0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
 ; HM02
@@ -505,6 +508,7 @@
 	item_attribute 0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
 ; HM07
 	item_attribute 0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
+	assert_table_length NUM_ITEMS + NUM_TMS + 2 + NUM_HMS ; count ITEM_C3 and ITEM_DC
 ; ITEM_FA
 	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
 ; $fb
@@ -519,3 +523,4 @@
 	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
 ; $00
 	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
+	assert_table_length $100
--- a/data/items/buena_prizes.asm
+++ b/data/items/buena_prizes.asm
@@ -1,5 +1,5 @@
 BuenaPrizeItems:
-; there are NUM_BUENA_PRIZES entries
+	table_width 2, BuenaPrizeItems
 	db ULTRA_BALL,   2
 	db FULL_RESTORE, 2
 	db NUGGET,       3
@@ -9,3 +9,4 @@
 	db CARBOS,       5
 	db CALCIUM,      5
 	db HP_UP,        5
+	assert_table_length NUM_BUENA_PRIZES
--- a/data/items/descriptions.asm
+++ b/data/items/descriptions.asm
@@ -1,5 +1,6 @@
 ItemDescriptions:
-; entries correspond to item ids
+; entries correspond to item ids (see constants/item_constants.asm)
+	table_width 2, ItemDescriptions
 	dw MasterBallDesc
 	dw UltraBallDesc
 	dw BrightpowderDesc
@@ -190,6 +191,7 @@
 	dw MusicMailDesc
 	dw MirageMailDesc
 	dw TeruSama25Desc
+	assert_table_length NUM_ITEMS
 	dw TeruSama26Desc
 	dw TeruSama26Desc
 	dw TeruSama26Desc
@@ -255,6 +257,7 @@
 	dw TeruSama31Desc
 	dw TeruSama32Desc
 	dw TeruSama33Desc
+	assert_table_length $ff
 
 MasterBallDesc:
 	db   "The best BALL. It"
--- a/data/items/fruit_trees.asm
+++ b/data/items/fruit_trees.asm
@@ -1,5 +1,6 @@
 FruitTreeItems:
 ; entries correspond to FRUITTREE_* constants
+	table_width 1, FruitTreeItems
 	db BERRY        ; ROUTE_29
 	db BERRY        ; ROUTE_30_1
 	db BERRY        ; ROUTE_38
@@ -30,3 +31,4 @@
 	db ICE_BERRY    ; PEWTER_CITY_1
 	db MINT_BERRY   ; PEWTER_CITY_2
 	db BURNT_BERRY  ; FUCHSIA_CITY
+	assert_table_length NUM_FRUIT_TREES
--- a/data/items/marts.asm
+++ b/data/items/marts.asm
@@ -1,5 +1,6 @@
 Marts:
-; entries correspond to MART_* constants
+; entries correspond to MART_* constants (see constants/mart_constants.asm)
+	table_width 2, Marts
 	dw MartCherrygrove
 	dw MartCherrygroveDex
 	dw MartViolet
@@ -34,6 +35,7 @@
 	dw MartMtMoon
 	dw MartIndigoPlateau
 	dw MartUnderground
+	assert_table_length NUM_MARTS
 
 MartCherrygrove:
 	db 4 ; # items
--- a/data/maps/landmarks.asm
+++ b/data/maps/landmarks.asm
@@ -6,6 +6,7 @@
 
 Landmarks:
 ; entries correspond to constants/landmark_constants.asm
+	table_width 4, Landmarks
 	dbbw       0,   0, SpecialMapName
 	landmark 140, 100, NewBarkTownName
 	landmark 128, 100, Route29Name
@@ -53,6 +54,7 @@
 	landmark 112,  72, DarkCaveName
 	landmark 124,  88, Route46Name
 	landmark 148,  68, SilverCaveName
+	assert_table_length KANTO_LANDMARK
 	landmark  52, 108, PalletTownName
 	landmark  52,  92, Route1Name
 	landmark  52,  76, ViridianCityName
@@ -102,6 +104,7 @@
 	landmark  12, 100, TohjoFallsName
 	landmark  20,  68, Route28Name
 	landmark 140, 116, FastShipName
+	assert_table_length NUM_LANDMARKS
 
 NewBarkTownName:     db "NEW BARK¯TOWN@"
 CherrygroveCityName: db "CHERRYGROVE¯CITY@"
--- a/data/maps/maps.asm
+++ b/data/maps/maps.asm
@@ -16,6 +16,7 @@
 
 MapGroupPointers::
 ; pointers to the first map of each map group
+	table_width 2, MapGroupPointers
 	dw MapGroup_Olivine     ;  1
 	dw MapGroup_Mahogany    ;  2
 	dw MapGroup_Dungeons    ;  3
@@ -42,6 +43,7 @@
 	dw MapGroup_NewBark     ; 24
 	dw MapGroup_Saffron     ; 25
 	dw MapGroup_Cherrygrove ; 26
+	assert_table_length NUM_MAP_GROUPS
 
 MapGroup_Olivine:
 	map OlivinePokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_OLIVINE_CITY, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE
--- a/data/maps/outdoor_sprites.asm
+++ b/data/maps/outdoor_sprites.asm
@@ -3,6 +3,7 @@
 
 OutdoorSprites:
 ; entries correspond to map groups
+	table_width 2, OutdoorSprites
 	dw OlivineGroupSprites
 	dw MahoganyGroupSprites
 	dw DungeonsGroupSprites
@@ -29,6 +30,7 @@
 	dw NewBarkGroupSprites
 	dw SaffronGroupSprites
 	dw CherrygroveGroupSprites
+	assert_table_length NUM_MAP_GROUPS
 
 PalletGroupSprites:
 	db SPRITE_SUICUNE
--- a/data/maps/roofs.asm
+++ b/data/maps/roofs.asm
@@ -5,10 +5,12 @@
 	const ROOF_AZALEA    ; 2
 	const ROOF_OLIVINE   ; 3
 	const ROOF_GOLDENROD ; 4
+NUM_ROOFS EQU const_value
 
 MapGroupRoofs:
 ; entries correspond to map groups
 ; values are indexes for Roofs (see below)
+	table_width 1, MapGroupRoofs
 	db -1             ;  0
 	db ROOF_OLIVINE   ;  1 (Olivine)
 	db ROOF_AZALEA    ;  2 (Mahogany)
@@ -36,11 +38,14 @@
 	db ROOF_NEW_BARK  ; 24 (New Bark)
 	db -1             ; 25
 	db ROOF_NEW_BARK  ; 26 (Cherrygrove)
+	assert_table_length NUM_MAP_GROUPS + 1
 
 Roofs:
 ; entries correspond to ROOF_* constants
+	table_width ROOF_LENGTH * LEN_2BPP_TILE, Roofs
 INCBIN "gfx/tilesets/roofs/new_bark.2bpp"
 INCBIN "gfx/tilesets/roofs/violet.2bpp"
 INCBIN "gfx/tilesets/roofs/azalea.2bpp"
 INCBIN "gfx/tilesets/roofs/olivine.2bpp"
 INCBIN "gfx/tilesets/roofs/goldenrod.2bpp"
+	assert_table_length NUM_ROOFS
--- a/data/maps/sgb_roof_pal_inds.asm
+++ b/data/maps/sgb_roof_pal_inds.asm
@@ -2,6 +2,7 @@
 
 MapGroupRoofSGBPalInds:
 ; entries correspond to map groups
+	table_width 1, MapGroupRoofSGBPalInds
 	db PREDEFPAL_ROUTES
 	db PREDEFPAL_OLIVINE
 	db PREDEFPAL_MAHOGANY
@@ -29,3 +30,4 @@
 	db PREDEFPAL_NEW_BARK
 	db PREDEFPAL_SAFFRON
 	db PREDEFPAL_CHERRYGROVE
+	assert_table_length NUM_MAP_GROUPS + 1
--- a/data/maps/spawn_points.asm
+++ b/data/maps/spawn_points.asm
@@ -6,6 +6,7 @@
 
 SpawnPoints:
 ; entries correspond to SPAWN_* constants
+	table_width 4, SpawnPoints
 
 	spawn PLAYERS_HOUSE_2F,            3,  3
 	spawn VIRIDIAN_POKECENTER_1F,      5,  3
@@ -39,3 +40,5 @@
 	spawn FAST_SHIP_CABINS_SW_SSW_NW,  6,  2
 
 	spawn N_A,                        -1, -1
+
+	assert_table_length NUM_SPAWNS + 1
--- a/data/mon_menu.asm
+++ b/data/mon_menu.asm
@@ -20,6 +20,7 @@
 
 MonMenuOptions:
 ; category, item, value; actions are in PokemonActionSubmenu (see engine/pokemon/mon_menu.asm)
+	table_width 3, MonMenuOptions
 ; moves
 	db MONMENU_FIELD_MOVE, MONMENUITEM_CUT,        CUT
 	db MONMENU_FIELD_MOVE, MONMENUITEM_FLY,        FLY
@@ -43,4 +44,5 @@
 	db MONMENU_MENUOPTION, MONMENUITEM_MOVE,       MONMENUVALUE_MOVE
 	db MONMENU_MENUOPTION, MONMENUITEM_MAIL,       MONMENUVALUE_MAIL
 	db MONMENU_MENUOPTION, MONMENUITEM_ERROR,      MONMENUVALUE_ERROR
-	db -1
+	assert_table_length NUM_MONMENUITEMS
+	db -1 ; end
--- a/data/moves/animations.asm
+++ b/data/moves/animations.asm
@@ -1,5 +1,6 @@
 BattleAnimations::
 ; entries correspond to constants/move_constants.asm
+	table_width 2, BattleAnimations
 	dw BattleAnim_0
 	dw BattleAnim_Pound
 	dw BattleAnim_KarateChop
@@ -252,10 +253,12 @@
 	dw BattleAnim_RockSmash
 	dw BattleAnim_Whirlpool
 	dw BattleAnim_BeatUp
+	assert_table_length NUM_ATTACKS + 1
 	dw BattleAnim_252
 	dw BattleAnim_253
 	dw BattleAnim_254
 	dw BattleAnim_SweetScent2
+	assert_table_length $100
 ; $100
 	dw BattleAnim_ThrowPokeBall
 	dw BattleAnim_SendOutMon
@@ -279,6 +282,7 @@
 	dw BattleAnim_Wobble
 	dw BattleAnim_Shake
 	dw BattleAnim_HitConfusion
+	assert_table_length NUM_BATTLE_ANIMS + 1
 
 BattleAnim_0:
 BattleAnim_252:
--- a/data/moves/descriptions.asm
+++ b/data/moves/descriptions.asm
@@ -1,5 +1,6 @@
 MoveDescriptions::
 ; entries correspond to move ids (see constants/move_constants.asm)
+	table_width 2, MoveDescriptions
 	dw PoundDescription
 	dw KarateChopDescription
 	dw DoubleslapDescription
@@ -251,11 +252,13 @@
 	dw RockSmashDescription
 	dw WhirlpoolDescription
 	dw BeatUpDescription
+	assert_table_length NUM_ATTACKS
 	dw MoveFCDescription
 	dw MoveFDDescription
 	dw MoveFEDescription
 	dw MoveFFDescription
 	dw Move00Description
+	assert_table_length $100
 
 MoveFCDescription:
 MoveFDDescription:
--- a/data/moves/effects_pointers.asm
+++ b/data/moves/effects_pointers.asm
@@ -1,5 +1,6 @@
 MoveEffectsPointers:
 ; entries correspond to EFFECT_* constants
+	table_width 2, MoveEffectsPointers
 	dw NormalHit
 	dw DoSleep
 	dw PoisonHit
@@ -157,3 +158,4 @@
 	dw BeatUp
 	dw Fly
 	dw DefenseCurl
+	assert_table_length NUM_MOVE_EFECTS
--- a/data/moves/moves.asm
+++ b/data/moves/moves.asm
@@ -11,7 +11,8 @@
 ENDM
 
 Moves:
-; entries correspond to constants/move_constants.asm
+; entries correspond to move ids (see constants/move_constants.asm)
+	table_width MOVE_LENGTH, Moves
 	move POUND,        EFFECT_NORMAL_HIT,         40, NORMAL,       100, 35,   0
 	move KARATE_CHOP,  EFFECT_NORMAL_HIT,         50, FIGHTING,     100, 25,   0
 	move DOUBLESLAP,   EFFECT_MULTI_HIT,          15, NORMAL,        85, 10,   0
@@ -263,3 +264,4 @@
 	move ROCK_SMASH,   EFFECT_DEFENSE_DOWN_HIT,   20, FIGHTING,     100, 15,  50
 	move WHIRLPOOL,    EFFECT_TRAP_TARGET,        15, WATER,         70, 15,   0
 	move BEAT_UP,      EFFECT_BEAT_UP,            10, DARK,         100, 10,   0
+	assert_table_length NUM_ATTACKS
--- a/data/moves/tmhm_moves.asm
+++ b/data/moves/tmhm_moves.asm
@@ -3,6 +3,7 @@
 
 TMHMMoves:
 ; entries correspond to *_TMNUM constants (see constants/item_constants.asm)
+	table_width 1, TMHMMoves
 
 ; TMs
 n = 1
@@ -42,5 +43,7 @@
 PURGE MOVE_FOR_MT
 n = n + 1
 endr
+
+	assert_table_length NUM_TM_HM_TUTOR
 
 	db 0 ; end
--- a/data/party_menu_qualities.asm
+++ b/data/party_menu_qualities.asm
@@ -20,6 +20,7 @@
 
 PartyMenuQualityPointers:
 ; entries correspond to PARTYMENUACTION_* constants
+	table_width 2, PartyMenuQualityPointers
 	dw .Default  ; PARTYMENUACTION_CHOOSE_POKEMON
 	dw .Default  ; PARTYMENUACTION_HEALING_ITEM
 	dw .Default  ; PARTYMENUACTION_SWITCH
@@ -30,6 +31,7 @@
 	dw .Gender   ; PARTYMENUACTION_GIVE_MON_FEMALE
 	dw .Default  ; PARTYMENUACTION_GIVE_ITEM
 	dw .Mobile   ; PARTYMENUACTION_MOBILE
+	assert_table_length NUM_PARTYMENUACTIONS
 
 .Default:  partymenuqualities NICKNAMES, HP_BAR, HP_DIGITS, LEVEL, STATUS
 .TMHM:     partymenuqualities NICKNAMES, TMHM_COMPAT,       LEVEL, STATUS
--- a/data/phone/non_trainer_names.asm
+++ b/data/phone/non_trainer_names.asm
@@ -1,5 +1,6 @@
 NonTrainerCallerNames:
-; entries correspond to PHONECONTACT_* constants
+; entries correspond to PHONECONTACT_* constants (see constants/trainer_constants.asm)
+	table_width 2, NonTrainerCallerNames
 	dw .none
 	dw .mom
 	dw .bikeshop
@@ -6,6 +7,7 @@
 	dw .bill
 	dw .elm
 	dw .buena
+	assert_table_length NUM_NONTRAINER_PHONECONTACTS + 1
 
 .none:     db "----------@"
 .mom:      db "MOM:@"
--- a/data/phone/phone_contacts.asm
+++ b/data/phone/phone_contacts.asm
@@ -10,6 +10,7 @@
 
 PhoneContacts:
 ; entries correspond to PHONE_* constants
+	table_width PHONE_CONTACT_SIZE, PhoneContacts
 	phone TRAINER_NONE, PHONE_00,              N_A,                       0,       UnusedPhoneScript,        0,       UnusedPhoneScript
 	phone TRAINER_NONE, PHONECONTACT_MOM,      PLAYERS_HOUSE_1F,          ANYTIME, MomPhoneCalleeScript,     0,       UnusedPhoneScript
 	phone TRAINER_NONE, PHONECONTACT_BIKESHOP, OAKS_LAB,                  0,       UnusedPhoneScript,        0,       UnusedPhoneScript
@@ -48,3 +49,4 @@
 	phone HIKER,        PARRY1,                ROUTE_45,                  ANYTIME, ParryPhoneCalleeScript,   ANYTIME, ParryPhoneCallerScript
 	phone PICNICKER,    ERIN1,                 ROUTE_46,                  ANYTIME, ErinPhoneCalleeScript,    ANYTIME, ErinPhoneCallerScript
 	phone TRAINER_NONE, PHONECONTACT_BUENA,    GOLDENROD_DEPT_STORE_ROOF, ANYTIME, BuenaPhoneCalleeScript,   ANYTIME, BuenaPhoneCallerScript
+	assert_table_length NUM_PHONE_CONTACTS + 1
--- a/data/phone/special_calls.asm
+++ b/data/phone/special_calls.asm
@@ -7,6 +7,7 @@
 
 SpecialPhoneCallList:
 ; entries correspond to SPECIALCALL_* constants
+	table_width SPECIALCALL_SIZE, SpecialPhoneCallList
 	specialcall SpecialCallOnlyWhenOutside, PHONECONTACT_ELM,      ElmPhoneCallerScript
 	specialcall SpecialCallOnlyWhenOutside, PHONECONTACT_ELM,      ElmPhoneCallerScript
 	specialcall SpecialCallOnlyWhenOutside, PHONECONTACT_ELM,      ElmPhoneCallerScript
@@ -15,3 +16,4 @@
 	specialcall SpecialCallWhereverYouAre,  PHONECONTACT_BIKESHOP, BikeShopPhoneCallerScript
 	specialcall SpecialCallWhereverYouAre,  PHONECONTACT_MOM,      MomPhoneLectureScript
 	specialcall SpecialCallOnlyWhenOutside, PHONECONTACT_ELM,      ElmPhoneCallerScript
+	assert_table_length NUM_SPECIALCALLS
--- a/data/pokemon/base_stats.asm
+++ b/data/pokemon/base_stats.asm
@@ -32,6 +32,7 @@
 ENDM
 
 BaseData::
+	table_width BASE_DATA_SIZE, BaseData
 INCLUDE "data/pokemon/base_stats/bulbasaur.asm"
 INCLUDE "data/pokemon/base_stats/ivysaur.asm"
 INCLUDE "data/pokemon/base_stats/venusaur.asm"
@@ -283,5 +284,4 @@
 INCLUDE "data/pokemon/base_stats/lugia.asm"
 INCLUDE "data/pokemon/base_stats/ho_oh.asm"
 INCLUDE "data/pokemon/base_stats/celebi.asm"
-.End:
-	assert BaseData.End - BaseData == NUM_POKEMON * BASE_DATA_SIZE
+	assert_table_length NUM_POKEMON
--- a/data/pokemon/cries.asm
+++ b/data/pokemon/cries.asm
@@ -5,6 +5,7 @@
 
 PokemonCries::
 ; entries correspond to constants/pokemon_constants.asm
+	table_width MON_CRY_LENGTH, PokemonCries
 	mon_cry CRY_BULBASAUR,   128,  129 ; BULBASAUR
 	mon_cry CRY_BULBASAUR,    32,  256 ; IVYSAUR
 	mon_cry CRY_BULBASAUR,     0,  320 ; VENUSAUR
@@ -256,7 +257,9 @@
 	mon_cry CRY_TYPHLOSION,    0,  256 ; LUGIA
 	mon_cry CRY_AIPOM,         0,  384 ; HO_OH
 	mon_cry CRY_ENTEI,       330,  273 ; CELEBI
+	assert_table_length NUM_POKEMON
 	mon_cry CRY_NIDORAN_M,     0,    0 ; 252
 	mon_cry CRY_NIDORAN_M,     0,    0 ; 253
 	mon_cry CRY_NIDORAN_M,     0,    0 ; 254
 	mon_cry CRY_NIDORAN_M,     0,    0 ; 255
+	assert_table_length $ff
--- a/data/pokemon/dex_entry_pointers.asm
+++ b/data/pokemon/dex_entry_pointers.asm
@@ -1,5 +1,6 @@
 PokedexDataPointerTable:
 ; entries correspond to constants/pokemon_constants.asm
+	table_width 2, PokedexDataPointerTable
 	dw BulbasaurPokedexEntry
 	dw IvysaurPokedexEntry
 	dw VenusaurPokedexEntry
@@ -251,3 +252,4 @@
 	dw LugiaPokedexEntry
 	dw HoOhPokedexEntry
 	dw CelebiPokedexEntry
+	assert_table_length NUM_POKEMON
--- a/data/pokemon/dex_order_alpha.asm
+++ b/data/pokemon/dex_order_alpha.asm
@@ -1,6 +1,7 @@
 ; Every Pokémon sorted alphabetically by name.
 
 AlphabeticalPokedexOrder:
+	table_width 1, AlphabeticalPokedexOrder
 	db ABRA
 	db AERODACTYL
 	db AIPOM
@@ -252,3 +253,4 @@
 	db YANMA
 	db ZAPDOS
 	db ZUBAT
+	assert_table_length NUM_POKEMON
--- a/data/pokemon/dex_order_new.asm
+++ b/data/pokemon/dex_order_new.asm
@@ -1,6 +1,7 @@
 ; Every Pokémon sorted in New Pokédex Order.
 
 NewPokedexOrder:
+	table_width 1, NewPokedexOrder
 	db CHIKORITA
 	db BAYLEEF
 	db MEGANIUM
@@ -252,3 +253,4 @@
 	db MEWTWO
 	db MEW
 	db CELEBI
+	assert_table_length NUM_POKEMON
--- a/data/pokemon/egg_move_pointers.asm
+++ b/data/pokemon/egg_move_pointers.asm
@@ -1,4 +1,5 @@
 EggMovePointers::
+	table_width 2, EggMovePointers
 	dw BulbasaurEggMoves
 	dw NoEggMoves
 	dw NoEggMoves
@@ -250,3 +251,4 @@
 	dw NoEggMoves
 	dw NoEggMoves
 	dw NoEggMoves
+	assert_table_length NUM_POKEMON
--- a/data/pokemon/evos_attacks_pointers.asm
+++ b/data/pokemon/evos_attacks_pointers.asm
@@ -1,6 +1,7 @@
 ; Evolutions and attacks are grouped together since they're both checked at level-up.
 
 EvosAttacksPointers::
+	table_width 2, EvosAttacksPointers
 	dw BulbasaurEvosAttacks
 	dw IvysaurEvosAttacks
 	dw VenusaurEvosAttacks
@@ -252,3 +253,4 @@
 	dw LugiaEvosAttacks
 	dw HoOhEvosAttacks
 	dw CelebiEvosAttacks
+	assert_table_length NUM_POKEMON
--- a/data/pokemon/gen1_base_special.asm
+++ b/data/pokemon/gen1_base_special.asm
@@ -1,6 +1,7 @@
 ; The original base Special stat for each Pokémon from Red/Blue
 
 KantoMonSpecials:
+	table_width 1, KantoMonSpecials
 	db  65 ; BULBASAUR
 	db  80 ; IVYSAUR
 	db 100 ; VENUSAUR
@@ -152,3 +153,4 @@
 	db 100 ; DRAGONITE
 	db 154 ; MEWTWO
 	db 100 ; MEW
+	assert_table_length JOHTO_POKEMON - 1
--- a/data/pokemon/gen1_order.asm
+++ b/data/pokemon/gen1_order.asm
@@ -1,4 +1,5 @@
 Pokered_MonIndices:
+	table_width 1, Pokered_MonIndices
 	db RHYDON
 	db KANGASKHAN
 	db NIDORAN_M
@@ -189,7 +190,7 @@
 	db BELLSPROUT
 	db WEEPINBELL
 	db VICTREEBEL
-
+	assert_table_length 190 ; gen 1 mon indexes
 	db CHIKORITA
 	db BAYLEEF
 	db MEGANIUM
@@ -252,3 +253,4 @@
 	db QWILFISH
 	db WOBBUFFET
 	db WOBBUFFET
+	assert_table_length NUM_POKEMON + 1
--- a/data/pokemon/menu_icons.asm
+++ b/data/pokemon/menu_icons.asm
@@ -1,6 +1,7 @@
 ; party menu icons
 
 MonMenuIcons:
+	table_width 1, MonMenuIcons
 	db ICON_BULBASAUR   ; BULBASAUR
 	db ICON_BULBASAUR   ; IVYSAUR
 	db ICON_BULBASAUR   ; VENUSAUR
@@ -252,3 +253,4 @@
 	db ICON_LUGIA       ; LUGIA
 	db ICON_HO_OH       ; HO_OH
 	db ICON_HUMANSHAPE  ; CELEBI
+	assert_table_length NUM_POKEMON
--- a/data/pokemon/names.asm
+++ b/data/pokemon/names.asm
@@ -1,4 +1,5 @@
 PokemonNames::
+	table_width NAME_LENGTH - 1, PokemonNames
 	db "BULBASAUR@"
 	db "IVYSAUR@@@"
 	db "VENUSAUR@@"
@@ -250,8 +251,11 @@
 	db "LUGIA@@@@@"
 	db "HO-OH@@@@@"
 	db "CELEBI@@@@"
+	assert_table_length NUM_POKEMON
 	db "?????@@@@@"
 	db "EGG@@@@@@@"
+	assert_table_length EGG
 	db "?????@@@@@"
 	db "?????@@@@@"
 	db "?????@@@@@"
+	assert_table_length $100
--- a/data/pokemon/palettes.asm
+++ b/data/pokemon/palettes.asm
@@ -5,6 +5,9 @@
 ; only the middle two colors are included, not black or white.
 ; Shiny palettes are defined directly, not generated.
 
+	; 2 middle palettes, front and shiny, with 2 colors each
+	table_width PAL_COLOR_SIZE * 2 * 2, PokemonPalettes
+
 ; 000
 	RGB 30, 22, 17
 	RGB 16, 14, 19
@@ -515,6 +518,8 @@
 INCBIN "gfx/pokemon/celebi/front.gbcpal", middle_colors
 INCLUDE "gfx/pokemon/celebi/shiny.pal"
 
+	assert_table_length NUM_POKEMON + 1
+
 ; 252
 	RGB 30, 26, 11
 	RGB 23, 16, 00
@@ -525,6 +530,8 @@
 INCBIN "gfx/pokemon/egg/front.gbcpal", middle_colors
 INCLUDE "gfx/pokemon/egg/shiny.pal"
 
+	assert_table_length EGG + 1
+
 ; 254
 	RGB 30, 26, 11
 	RGB 23, 16, 00
@@ -538,3 +545,5 @@
 ; 255 shiny
 	RGB 23, 23, 23
 	RGB 17, 17, 17
+
+	assert_table_length $100
--- a/data/pokemon/pic_pointers.asm
+++ b/data/pokemon/pic_pointers.asm
@@ -2,6 +2,7 @@
 
 PokemonPicPointers::
 ; entries correspond to Pokémon species, two apiece
+	table_width 3 * 2, PokemonPicPointers
 	dba_pic BulbasaurFrontpic
 	dba_pic BulbasaurBackpic
 	dba_pic IvysaurFrontpic
@@ -507,7 +508,9 @@
 	dba_pic HoOhBackpic
 	dba_pic CelebiFrontpic
 	dba_pic CelebiBackpic
+	assert_table_length NUM_POKEMON
 	dbw -1, -1 ; unused
 	dbw -1, -1 ; unused
 	dba_pic EggPic
 	dbw -1, -1 ; unused
+	assert_table_length EGG
--- a/data/pokemon/unown_pic_pointers.asm
+++ b/data/pokemon/unown_pic_pointers.asm
@@ -1,5 +1,6 @@
 UnownPicPointers::
 ; entries correspond to Unown letters, two apiece
+	table_width 3 * 2, UnownPicPointers
 	dba_pic UnownAFrontpic
 	dba_pic UnownABackpic
 	dba_pic UnownBFrontpic
@@ -52,3 +53,4 @@
 	dba_pic UnownYBackpic
 	dba_pic UnownZFrontpic
 	dba_pic UnownZBackpic
+	assert_table_length NUM_UNOWN
--- a/data/pokemon/unown_words.asm
+++ b/data/pokemon/unown_words.asm
@@ -9,8 +9,9 @@
 
 UnownWords:
 ; entries correspond to UNOWN_* form constants
+	table_width 2, UnownWords
+	dw UnownWordA ; unused
 	dw UnownWordA
-	dw UnownWordA
 	dw UnownWordB
 	dw UnownWordC
 	dw UnownWordD
@@ -36,6 +37,7 @@
 	dw UnownWordX
 	dw UnownWordY
 	dw UnownWordZ
+	assert_table_length NUM_UNOWN + 1
 
 UnownWordA: unownword "ANGRY"
 UnownWordB: unownword "BEAR"
--- a/data/radio/buenas_passwords.asm
+++ b/data/radio/buenas_passwords.asm
@@ -1,5 +1,5 @@
 BuenasPasswordTable:
-; there are NUM_PASSWORD_CATEGORIES entries
+	table_width 2, BuenasPasswordTable
 	dw .JohtoStarters
 	dw .Beverages
 	dw .HealingItems
@@ -11,6 +11,7 @@
 	dw .Moves
 	dw .XItems
 	dw .RadioStations
+	assert_table_length NUM_PASSWORD_CATEGORIES
 
                ; string type, points, option 1, option 2, option 3
 .JohtoStarters: db BUENA_MON,    10, CYNDAQUIL, TOTODILE, CHIKORITA
--- a/data/radio/channel_music.asm
+++ b/data/radio/channel_music.asm
@@ -1,5 +1,6 @@
 RadioChannelSongs:
-; entries correspond to radio channel ids
+; entries correspond to radio channel ids (see constants/radio_constants.asm)
+	table_width 2, RadioChannelSongs
 	dw MUSIC_POKEMON_TALK
 	dw MUSIC_POKEMON_CENTER
 	dw MUSIC_TITLE
@@ -11,3 +12,4 @@
 	dw MUSIC_POKE_FLUTE_CHANNEL
 	dw MUSIC_RUINS_OF_ALPH_RADIO
 	dw MUSIC_LAKE_OF_RAGE_ROCKET_RADIO
+	assert_table_length NUM_RADIO_CHANNELS
--- a/data/radio/oaks_pkmn_talk_routes.asm
+++ b/data/radio/oaks_pkmn_talk_routes.asm
@@ -1,7 +1,6 @@
 ; Oak's Pokémon Talk will list wild Pokémon on these maps.
 
 OaksPKMNTalkRoutes:
-; there are NUM_OAKS_POKEMON_TALK_ROUTES entries
 	map_id ROUTE_29
 	map_id ROUTE_46
 	map_id ROUTE_30
@@ -17,3 +16,4 @@
 	map_id ROUTE_45
 	map_id ROUTE_36
 	map_id ROUTE_31
+.End
--- a/data/sprite_anims/framesets.asm
+++ b/data/sprite_anims/framesets.asm
@@ -1,5 +1,6 @@
 SpriteAnimFrameData:
-; entries correspond to SPRITE_ANIM_FRAMESET_* constants
+; entries correspond to SPRITE_ANIM_FRAMESET_* constants (see constants/sprite_anim_constants.asm)
+	table_width 2, SpriteAnimFrameData
 	dw .Frameset_00
 	dw .Frameset_PartyMon
 	dw .Frameset_PartyMonWithMail
@@ -66,6 +67,7 @@
 	dw .Frameset_IntroUnownF
 	dw .Frameset_CelebiLeft
 	dw .Frameset_CelebiRight
+	assert_table_length NUM_SPRITE_ANIM_FRAMESETS
 
 .Frameset_00:
 	frame SPRITE_ANIM_OAMSET_RED_WALK_1, 32
--- a/data/sprite_anims/oam.asm
+++ b/data/sprite_anims/oam.asm
@@ -1,5 +1,6 @@
 SpriteAnimOAMData:
-; entries correspond to SPRITE_ANIM_OAMSET_* constants
+; entries correspond to SPRITE_ANIM_OAMSET_* constants (see constants/sprite_anim_constants.asm)
+	table_width 3, SpriteAnimOAMData
 	; vtile offset, data pointer
 	dbw $00, .OAMData_RedWalk                  ; SPRITE_ANIM_OAMSET_RED_WALK_1
 	dbw $04, .OAMData_RedWalk                  ; SPRITE_ANIM_OAMSET_RED_WALK_2
@@ -141,6 +142,7 @@
 	dbw $08, .OAMData_GameFreakLogo4_11        ; SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_9
 	dbw $04, .OAMData_GameFreakLogo4_11        ; SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_10
 	dbw $00, .OAMData_GameFreakLogo4_11        ; SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_11
+	assert_table_length NUM_SPRITE_ANIM_OAMSETS
 
 .OAMData_1x1_Palette0:
 	db 1
--- a/data/sprite_anims/sequences.asm
+++ b/data/sprite_anims/sequences.asm
@@ -1,5 +1,6 @@
 SpriteAnimSeqData:
-; entries correspond to SPRITE_ANIM_INDEX_* constants
+; entries correspond to SPRITE_ANIM_INDEX_* constants (see constants/sprite_anim_constants.asm)
+	table_width 3, SpriteAnimSeqData
 	; frameset, sequence, tile
 ; SPRITE_ANIM_INDEX_PARTY_MON
 	db SPRITE_ANIM_FRAMESET_PARTY_MON,                 SPRITE_ANIM_SEQ_PARTY_MON,                 SPRITE_ANIM_DICT_DEFAULT
@@ -91,3 +92,4 @@
 	db SPRITE_ANIM_FRAMESET_INTRO_SUICUNE_AWAY,        SPRITE_ANIM_SEQ_INTRO_SUICUNE_AWAY,        SPRITE_ANIM_DICT_DEFAULT
 ; SPRITE_ANIM_INDEX_CELEBI
 	db SPRITE_ANIM_FRAMESET_CELEBI_LEFT,               SPRITE_ANIM_SEQ_NULL,                      SPRITE_ANIM_DICT_DEFAULT
+	assert_table_length NUM_SPRITE_ANIM_INDEXES
--- a/data/sprites/emotes.asm
+++ b/data/sprites/emotes.asm
@@ -7,6 +7,7 @@
 
 Emotes:
 ; entries correspond to EMOTE_* constants
+	table_width EMOTE_LENGTH, Emotes
 	emote ShockEmote,     4, $f8
 	emote QuestionEmote,  4, $f8
 	emote HappyEmote,     4, $f8
@@ -19,3 +20,4 @@
 	emote FishingRodGFX,  2, $fc
 	emote BoulderDustGFX, 2, $fe
 	emote GrassRustleGFX, 1, $fe
+	assert_table_length NUM_EMOTES
--- a/data/sprites/facings.asm
+++ b/data/sprites/facings.asm
@@ -1,5 +1,6 @@
 Facings:
-; entries correspond to FACING_* constants
+; entries correspond to FACING_* constants (see constants/map_object_constants.asm)
+	table_width 2, Facings
 	dw FacingStepDown0
 	dw FacingStepDown1
 	dw FacingStepDown2
@@ -32,10 +33,8 @@
 	dw FacingBoulderDust2
 	dw FacingGrass1
 	dw FacingGrass2
-.End
-	dw 0
-
-NUM_FACINGS EQU (Facings.End - Facings) / 2
+	assert_table_length NUM_FACINGS
+	dw 0 ; end
 
 ; Tables used as a reference to transform OAM data.
 
--- a/data/sprites/map_objects.asm
+++ b/data/sprites/map_objects.asm
@@ -1,5 +1,6 @@
 SpriteMovementData::
 ; entries correspond to SPRITEMOVEDATA_* constants
+	table_width NUM_SPRITEMOVEDATA_FIELDS, SpriteMovementData
 
 ; SPRITEMOVEDATA_00
 	db SPRITEMOVEFN_00 ; movement function
@@ -304,3 +305,5 @@
 	db 0 ; flags1
 	db 0 ; flags2
 	db 0 ; palette flags
+
+	assert_table_length NUM_SPRITEMOVEDATA + 1
--- a/data/sprites/sprite_mons.asm
+++ b/data/sprites/sprite_mons.asm
@@ -1,5 +1,6 @@
 SpriteMons:
 ; entries correspond to SPRITE_* constants past SPRITE_POKEMON
+	table_width 1, SpriteMons
 	db UNOWN
 	db GEODUDE
 	db GROWLITHE
@@ -35,3 +36,4 @@
 	db GYARADOS
 	db LUGIA
 	db HO_OH
+	assert_table_length NUM_POKEMON_SPRITES
--- a/data/sprites/sprites.asm
+++ b/data/sprites/sprites.asm
@@ -6,6 +6,7 @@
 
 OverworldSprites:
 ; entries correspond to SPRITE_* constants
+	table_width NUM_SPRITEDATA_FIELDS, OverworldSprites
 	overworld_sprite ChrisSpriteGFX, 12, WALKING_SPRITE, PAL_OW_RED
 	overworld_sprite ChrisBikeSpriteGFX, 12, WALKING_SPRITE, PAL_OW_RED
 	overworld_sprite GameboyKidSpriteGFX, 12, STANDING_SPRITE, PAL_OW_GREEN
@@ -108,3 +109,4 @@
 	overworld_sprite EnteiSpriteGFX, 4, STILL_SPRITE, PAL_OW_RED
 	overworld_sprite RaikouSpriteGFX, 4, STILL_SPRITE, PAL_OW_RED
 	overworld_sprite StandingYoungsterSpriteGFX, 12, STANDING_SPRITE, PAL_OW_BLUE
+	assert_table_length NUM_OVERWORLD_SPRITES
--- a/data/tilesets.asm
+++ b/data/tilesets.asm
@@ -11,7 +11,8 @@
 ; - The *Anim are defined in engine/tilesets/tileset_anims.asm
 
 Tilesets::
-; entries correspond to TILESET_* constants
+; entries correspond to TILESET_* constants (see constants/tileset_constants.asm)
+	table_width TILESET_LENGTH, Tilesets
 	tileset Tileset0
 	tileset TilesetJohto
 	tileset TilesetJohtoModern
@@ -49,3 +50,4 @@
 	tileset TilesetKabutoWordRoom
 	tileset TilesetOmanyteWordRoom
 	tileset TilesetAerodactylWordRoom
+	assert_table_length NUM_TILESETS + 1
--- a/data/trainers/attributes.asm
+++ b/data/trainers/attributes.asm
@@ -1,5 +1,6 @@
 TrainerClassAttributes:
 ; entries correspond to trainer classes (see constants/trainer_constants.asm)
+	table_width NUM_TRAINER_ATTRIBUTES, TrainerClassAttributes
 
 ; Falkner
 	db NO_ITEM, NO_ITEM ; items
@@ -402,3 +403,5 @@
 	db 25 ; base reward
 	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
 	dw CONTEXT_USE | SWITCH_SOMETIMES
+
+	assert_table_length NUM_TRAINER_CLASSES
--- a/data/trainers/dvs.asm
+++ b/data/trainers/dvs.asm
@@ -1,5 +1,6 @@
 TrainerClassDVs:
 ; entries correspond to trainer classes (see constants/trainer_constants.asm)
+	table_width 2, TrainerClassDVs
 	;  atk,def,spd,spc
 	dn  9, 10,  7,  7 ; FALKNER
 	dn  8,  8,  8,  8 ; WHITNEY
@@ -68,3 +69,4 @@
 	dn  9,  8,  8,  8 ; OFFICER
 	dn  7, 14, 10,  8 ; GRUNTF
 	dn  9,  8,  8,  8 ; MYSTICALMAN
+	assert_table_length NUM_TRAINER_CLASSES
--- a/data/trainers/encounter_music.asm
+++ b/data/trainers/encounter_music.asm
@@ -2,6 +2,7 @@
 
 TrainerEncounterMusic::
 ; entries correspond to trainer classes (see constants/trainer_constants.asm)
+	table_width 1, TrainerEncounterMusic
 	db MUSIC_HIKER_ENCOUNTER       ; none
 	db MUSIC_YOUNGSTER_ENCOUNTER   ; falkner
 	db MUSIC_LASS_ENCOUNTER        ; whitney
@@ -70,6 +71,7 @@
 	db MUSIC_HIKER_ENCOUNTER       ; officer
 	db MUSIC_ROCKET_ENCOUNTER      ; gruntf
 	db MUSIC_HIKER_ENCOUNTER       ; mysticalman
-	db MUSIC_HIKER_ENCOUNTER
-	db MUSIC_HIKER_ENCOUNTER
-	db MUSIC_HIKER_ENCOUNTER
+	assert_table_length NUM_TRAINER_CLASSES + 1
+	db MUSIC_HIKER_ENCOUNTER       ; unused
+	db MUSIC_HIKER_ENCOUNTER       ; unused
+	db MUSIC_HIKER_ENCOUNTER       ; unused
--- a/data/trainers/genders.asm
+++ b/data/trainers/genders.asm
@@ -2,6 +2,7 @@
 
 BTTrainerClassGenders:
 ; entries correspond to trainer classes
+	table_width 1, BTTrainerClassGenders
 	db MALE   ; FALKNER
 	db FEMALE ; WHITNEY
 	db FEMALE ; BUGSY
@@ -68,3 +69,4 @@
 	db MALE   ; BLUE
 	db MALE   ; OFFICER
 	db FEMALE ; GRUNTF
+	assert_table_length NUM_TRAINER_CLASSES - 1 ; exclude MYSTICALMAN
--- a/data/trainers/palettes.asm
+++ b/data/trainers/palettes.asm
@@ -4,6 +4,8 @@
 ; Each .gbcpal is generated from the corresponding .png, and
 ; only the middle two colors are included, not black or white.
 
+	table_width PAL_COLOR_SIZE * 2, TrainerPalettes
+
 PlayerPalette: ; Chris uses the same colors as Cal
 INCBIN "gfx/trainers/cal.gbcpal", middle_colors
 KrisPalette: ; Kris shares Falkner's palette
@@ -74,3 +76,5 @@
 INCBIN "gfx/trainers/officer.gbcpal", middle_colors
 INCBIN "gfx/trainers/grunt_f.gbcpal", middle_colors
 INCBIN "gfx/trainers/mysticalman.gbcpal", middle_colors
+
+	assert_table_length NUM_TRAINER_CLASSES + 1
--- a/data/trainers/party_pointers.asm
+++ b/data/trainers/party_pointers.asm
@@ -2,6 +2,7 @@
 
 TrainerGroups:
 ; entries correspond to trainer classes (see constants/trainer_constants.asm)
+	table_width 2, TrainerGroups
 	dw FalknerGroup
 	dw WhitneyGroup
 	dw BugsyGroup
@@ -69,3 +70,4 @@
 	dw OfficerGroup
 	dw GruntFGroup
 	dw MysticalmanGroup
+	assert_table_length NUM_TRAINER_CLASSES
--- a/data/trainers/pic_pointers.asm
+++ b/data/trainers/pic_pointers.asm
@@ -2,6 +2,7 @@
 
 TrainerPicPointers::
 ; entries correspond to trainer classes (see constants/trainer_constants.asm)
+	table_width 3, TrainerPicPointers
 	dba_pic FalknerPic
 	dba_pic WhitneyPic
 	dba_pic BugsyPic
@@ -69,3 +70,4 @@
 	dba_pic OfficerPic
 	dba_pic GruntfPic
 	dba_pic MysticalmanPic
+	assert_table_length NUM_TRAINER_CLASSES
--- a/data/trainers/sprites.asm
+++ b/data/trainers/sprites.asm
@@ -1,5 +1,6 @@
 BTTrainerClassSprites:
 ; entries correspond to trainer classes
+	table_width 1, BTTrainerClassSprites
 	db SPRITE_FALKNER
 	db SPRITE_WHITNEY
 	db SPRITE_BUGSY
@@ -66,3 +67,4 @@
 	db SPRITE_BLUE
 	db SPRITE_OFFICER
 	db SPRITE_ROCKET_GIRL
+	assert_table_length NUM_TRAINER_CLASSES - 1 ; exclude MYSTICALMAN
--- a/data/types/names.asm
+++ b/data/types/names.asm
@@ -1,5 +1,6 @@
 TypeNames:
 ; entries correspond to types (see constants/type_constants.asm)
+	table_width 2, TypeNames
 	dw Normal
 	dw Fighting
 	dw Flying
@@ -10,6 +11,7 @@
 	dw Bug
 	dw Ghost
 	dw Steel
+	assert_table_length UNUSED_TYPES
 	dw Normal
 	dw Normal
 	dw Normal
@@ -20,6 +22,7 @@
 	dw Normal
 	dw Normal
 	dw CurseType
+	assert_table_length UNUSED_TYPES_END
 	dw Fire
 	dw Water
 	dw Grass
@@ -28,6 +31,7 @@
 	dw Ice
 	dw Dragon
 	dw Dark
+	assert_table_length TYPES_END
 
 Normal:    db "NORMAL@"
 Fighting:  db "FIGHTING@"
--- a/data/types/search_strings.asm
+++ b/data/types/search_strings.asm
@@ -1,5 +1,6 @@
 PokedexTypeSearchStrings:
 ; entries correspond with PokedexTypeSearchConversionTable (see data/types/search_types.asm)
+	table_width POKEDEX_TYPE_STRING_LENGTH, PokedexTypeSearchStrings
 	db "  ----  @"
 	db " NORMAL @"
 	db "  FIRE  @"
@@ -18,3 +19,4 @@
 	db " DRAGON @"
 	db "  DARK  @"
 	db " STEEL  @"
+	assert_table_length NUM_TYPES + 1
--- a/data/types/search_types.asm
+++ b/data/types/search_types.asm
@@ -1,5 +1,6 @@
 PokedexTypeSearchConversionTable:
 ; entries correspond with PokedexTypeSearchStrings (see data/types/search_strings.asm)
+	table_width 1, PokedexTypeSearchConversionTable
 	db NORMAL
 	db FIRE
 	db WATER
@@ -17,3 +18,4 @@
 	db DRAGON
 	db DARK
 	db STEEL
+	assert_table_length NUM_TYPES
--- a/data/wild/fish.asm
+++ b/data/wild/fish.asm
@@ -7,6 +7,7 @@
 
 FishGroups:
 ; entries correspond to FISHGROUP_* constants
+	table_width FISHGROUP_DATA_LENGTH, FishGroups
 	fishgroup 50 percent + 1, .Shore_Old,            .Shore_Good,            .Shore_Super
 	fishgroup 50 percent + 1, .Ocean_Old,            .Ocean_Good,            .Ocean_Super
 	fishgroup 50 percent + 1, .Lake_Old,             .Lake_Good,             .Lake_Super
@@ -20,6 +21,7 @@
 	fishgroup 50 percent + 1, .Qwilfish_Old,         .Qwilfish_Good,         .Qwilfish_Super
 	fishgroup 50 percent + 1, .Remoraid_Old,         .Remoraid_Good,         .Remoraid_Super
 	fishgroup 50 percent + 1, .Qwilfish_NoSwarm_Old, .Qwilfish_NoSwarm_Good, .Qwilfish_NoSwarm_Super
+	assert_table_length NUM_FISHGROUPS
 
 .Shore_Old:
 	db  70 percent + 1, MAGIKARP,   10
--- a/data/wild/probabilities.asm
+++ b/data/wild/probabilities.asm
@@ -4,6 +4,7 @@
 ENDM
 
 GrassMonProbTable:
+	table_width 2, GrassMonProbTable
 	mon_prob 30,  0 ; 30% chance
 	mon_prob 60,  1 ; 30% chance
 	mon_prob 80,  2 ; 20% chance
@@ -11,8 +12,11 @@
 	mon_prob 95,  4 ;  5% chance
 	mon_prob 99,  5 ;  4% chance
 	mon_prob 100, 6 ;  1% chance
+	assert_table_length NUM_GRASSMON
 
 WaterMonProbTable:
+	table_width 2, WaterMonProbTable
 	mon_prob 60,  0 ; 60% chance
 	mon_prob 90,  1 ; 30% chance
 	mon_prob 100, 2 ; 10% chance
+	assert_table_length NUM_WATERMON
--- a/data/wild/treemons.asm
+++ b/data/wild/treemons.asm
@@ -1,5 +1,6 @@
 TreeMons:
 ; entries correspond to TREEMON_SET_* constants
+	table_width 2, TreeMons
 	dw TreeMonSet_City
 	dw TreeMonSet_Canyon
 	dw TreeMonSet_Town
@@ -8,7 +9,8 @@
 	dw TreeMonSet_Lake
 	dw TreeMonSet_Forest
 	dw TreeMonSet_Rock
-	dw TreeMonSet_City
+	assert_table_length NUM_TREEMON_SETS
+	dw TreeMonSet_City ; unused
 
 ; Two tables each (common, rare).
 ; Structure:
--- a/engine/battle/move_effects/conversion2.asm
+++ b/engine/battle/move_effects/conversion2.asm
@@ -27,7 +27,7 @@
 
 .loop
 	call BattleRandom
-	maskbits NUM_TYPES
+	maskbits TYPES_END
 	cp UNUSED_TYPES
 	jr c, .okay
 	cp UNUSED_TYPES_END
--- a/engine/battle_anims/core.asm
+++ b/engine/battle_anims/core.asm
@@ -31,7 +31,7 @@
 	ld e, a
 	ld d, 0
 	ld hl, BattleAnimObjects
-rept 6
+rept BATTLEANIMOBJ_LENGTH
 	add hl, de
 endr
 	ld e, l
--- a/engine/debug/color_picker.asm
+++ b/engine/debug/color_picker.asm
@@ -99,7 +99,7 @@
 DebugColor_InitTrainerColor:
 	ld hl, TrainerPalettes
 	ld de, wDebugOriginalColors
-	ld c, NUM_TRAINER_CLASSES
+	ld c, NUM_TRAINER_CLASSES + 1
 .loop
 	push bc
 	push hl
@@ -284,7 +284,7 @@
 	ld a, NUM_POKEMON ; CELEBI
 	ret
 .trainer
-	ld a, NUM_TRAINER_CLASSES - 1 ; MYSTICALMAN
+	ld a, NUM_TRAINER_CLASSES ; MYSTICALMAN
 	ret
 
 .Jumptable:
--- a/engine/gfx/color.asm
+++ b/engine/gfx/color.asm
@@ -1305,7 +1305,9 @@
 INCLUDE "gfx/overworld/npc_sprites.pal"
 
 RoofPals:
+	table_width PAL_COLOR_SIZE * 2 * 2, RoofPals
 INCLUDE "gfx/tilesets/roofs.pal"
+	assert_table_length NUM_MAP_GROUPS + 1
 
 DiplomaPalettes:
 INCLUDE "gfx/diploma/diploma.pal"
--- a/engine/gfx/load_pics.asm
+++ b/engine/gfx/load_pics.asm
@@ -311,7 +311,7 @@
 	ld a, [wTrainerClass]
 	and a
 	ret z
-	cp NUM_TRAINER_CLASSES
+	cp NUM_TRAINER_CLASSES + 1
 	ret nc
 	call WaitBGMap
 	xor a
--- a/engine/gfx/sprite_anims.asm
+++ b/engine/gfx/sprite_anims.asm
@@ -12,7 +12,8 @@
 	jp hl
 
 .Jumptable:
-; entries correspond to SPRITE_ANIM_SEQ_* constants
+; entries correspond to SPRITE_ANIM_SEQ_* constants (see constants/sprite_anim_constants.asm)
+	table_width 2, DoAnimFrame.Jumptable
 	dw AnimSeq_Null
 	dw AnimSeq_PartyMon
 	dw AnimSeq_PartyMonSwitch
@@ -48,6 +49,7 @@
 	dw AnimSeq_IntroUnown
 	dw AnimSeq_IntroUnownF
 	dw AnimSeq_IntroSuicuneAway
+	assert_table_length NUM_SPRITE_ANIM_SEQS
 
 AnimSeq_Null:
 	ret
--- a/engine/items/item_effects.asm
+++ b/engine/items/item_effects.asm
@@ -12,7 +12,8 @@
 	ret
 
 ItemEffects:
-; entries correspond to item ids
+; entries correspond to item ids (see constants/item_constants.asm)
+	table_width 2, ItemEffects
 	dw PokeBallEffect      ; MASTER_BALL
 	dw PokeBallEffect      ; ULTRA_BALL
 	dw NoEffect            ; BRIGHTPOWDER
@@ -192,6 +193,21 @@
 	dw PokeBallEffect      ; PARK_BALL
 	dw NoEffect            ; RAINBOW_WING
 	dw NoEffect            ; ITEM_B3
+	assert_table_length ITEM_B3
+; The items past ITEM_B3 do not have effect entries:
+;	BRICK_PIECE
+;	SURF_MAIL
+;	LITEBLUEMAIL
+;	PORTRAITMAIL
+;	LOVELY_MAIL
+;	EON_MAIL
+;	MORPH_MAIL
+;	BLUESKY_MAIL
+;	MUSIC_MAIL
+;	MIRAGE_MAIL
+;	ITEM_BE
+; They all have the ITEMMENU_NOUSE attribute so they can't be used anyway.
+; NoEffect would be appropriate, with the table then being NUM_ITEMS long.
 
 PokeBallEffect:
 	ld a, [wBattleMode]
--- a/engine/overworld/decorations.asm
+++ b/engine/overworld/decorations.asm
@@ -23,7 +23,7 @@
 	ld [wCurDecorationCategory], a
 	jr c, .exit_menu
 	ld a, [wMenuSelection]
-	ld hl, .pointers
+	ld hl, .category_pointers
 	call MenuJumptable
 	jr nc, .top_loop
 
@@ -46,26 +46,28 @@
 	db 0 ; items
 	dw wNumOwnedDecoCategories
 	dw PlaceNthMenuStrings
-	dw .pointers
+	dw .category_pointers
 
-.pointers
-	dw DecoBedMenu, .bed
-	dw DecoCarpetMenu, .carpet
-	dw DecoPlantMenu, .plant
-	dw DecoPosterMenu, .poster
-	dw DecoConsoleMenu, .game
+.category_pointers:
+	table_width 2 + 2, _PlayerDecorationMenu.category_pointers
+	dw DecoBedMenu,      .bed
+	dw DecoCarpetMenu,   .carpet
+	dw DecoPlantMenu,    .plant
+	dw DecoPosterMenu,   .poster
+	dw DecoConsoleMenu,  .game
 	dw DecoOrnamentMenu, .ornament
-	dw DecoBigDollMenu, .big_doll
-	dw DecoExitMenu, .exit
+	dw DecoBigDollMenu,  .big_doll
+	dw DecoExitMenu,     .exit
+	assert_table_length NUM_DECO_CATEGORIES + 1
 
-.bed      db "BED@"
-.carpet   db "CARPET@"
-.plant    db "PLANT@"
-.poster   db "POSTER@"
-.game     db "GAME CONSOLE@"
-.ornament db "ORNAMENT@"
-.big_doll db "BIG DOLL@"
-.exit     db "EXIT@"
+.bed:      db "BED@"
+.carpet:   db "CARPET@"
+.plant:    db "PLANT@"
+.poster:   db "POSTER@"
+.game:     db "GAME CONSOLE@"
+.ornament: db "ORNAMENT@"
+.big_doll: db "BIG DOLL@"
+.exit:     db "EXIT@"
 
 .FindCategoriesWithOwnedDecos:
 	xor a
@@ -99,7 +101,7 @@
 	ret
 
 .FindOwnedDecos:
-	ld hl, .dw
+	ld hl, .owned_pointers
 .loop
 	ld a, [hli]
 	ld e, a
@@ -121,14 +123,16 @@
 .done
 	ret
 
-.dw
-	dwb FindOwnedBeds, 0 ; bed
-	dwb FindOwnedCarpets, 1 ; carpet
-	dwb FindOwnedPlants, 2 ; plant
-	dwb FindOwnedPosters, 3 ; poster
-	dwb FindOwnedConsoles, 4 ; game console
+.owned_pointers:
+	table_width 3, _PlayerDecorationMenu.owned_pointers
+	dwb FindOwnedBeds,      0 ; bed
+	dwb FindOwnedCarpets,   1 ; carpet
+	dwb FindOwnedPlants,    2 ; plant
+	dwb FindOwnedPosters,   3 ; poster
+	dwb FindOwnedConsoles,  4 ; game console
 	dwb FindOwnedOrnaments, 5 ; ornament
-	dwb FindOwnedBigDolls, 6 ; big doll
+	dwb FindOwnedBigDolls,  6 ; big doll
+	assert_table_length NUM_DECO_CATEGORIES
 	dw 0 ; end
 
 Deco_FillTempWithMinusOne:
@@ -200,7 +204,7 @@
 	ld c, BEDS
 	jp FindOwnedDecosInCategory
 
-.beds
+.beds:
 	db DECO_FEATHERY_BED ; 2
 	db DECO_PINK_BED ; 3
 	db DECO_POLKADOT_BED ; 4
@@ -218,7 +222,7 @@
 	ld c, CARPETS
 	jp FindOwnedDecosInCategory
 
-.carpets
+.carpets:
 	db DECO_RED_CARPET ; 7
 	db DECO_BLUE_CARPET ; 8
 	db DECO_YELLOW_CARPET ; 9
@@ -236,7 +240,7 @@
 	ld c, PLANTS
 	jp FindOwnedDecosInCategory
 
-.plants
+.plants:
 	db DECO_MAGNAPLANT ; c
 	db DECO_TROPICPLANT ; d
 	db DECO_JUMBOPLANT ; e
@@ -253,7 +257,7 @@
 	ld c, POSTERS
 	jp FindOwnedDecosInCategory
 
-.posters
+.posters:
 	db DECO_TOWN_MAP ; 10
 	db DECO_PIKACHU_POSTER ; 11
 	db DECO_CLEFAIRY_POSTER ; 12
@@ -271,7 +275,7 @@
 	ld c, CONSOLES
 	jp FindOwnedDecosInCategory
 
-.consoles
+.consoles:
 	db DECO_FAMICOM ; 15
 	db DECO_SNES ; 16
 	db DECO_N64 ; 17
@@ -289,7 +293,7 @@
 	ld c, DOLLS
 	jp FindOwnedDecosInCategory
 
-.ornaments
+.ornaments:
 	db DECO_PIKACHU_DOLL ; 1e
 	db DECO_SURF_PIKACHU_DOLL ; 1f
 	db DECO_CLEFAIRY_DOLL ; 20
@@ -326,7 +330,7 @@
 	ld c, BIG_DOLLS
 	jp FindOwnedDecosInCategory
 
-.big_dolls
+.big_dolls:
 	db DECO_BIG_SNORLAX_DOLL ; 1a
 	db DECO_BIG_ONIX_DOLL ; 1b
 	db DECO_BIG_LAPRAS_DOLL ; 1c
@@ -419,7 +423,7 @@
 
 GetDecorationData:
 	ld hl, DecorationAttributes
-	ld bc, 6
+	ld bc, DECOATTR_STRUCT_LENGTH
 	call AddNTimes
 	ret
 
@@ -443,7 +447,7 @@
 DoDecorationAction2:
 	ld a, [wMenuSelection]
 	call GetDecorationData
-	ld de, 2 ; function 2
+	ld de, DECOATTR_ACTION
 	add hl, de
 	ld a, [hl]
 	ld hl, .DecoActions
@@ -451,6 +455,7 @@
 	ret
 
 .DecoActions:
+	table_width 2, DoDecorationAction2.DecoActions
 	dw DecoAction_nothing
 	dw DecoAction_setupbed
 	dw DecoAction_putawaybed
@@ -466,10 +471,11 @@
 	dw DecoAction_putawaybigdoll
 	dw DecoAction_setupornament
 	dw DecoAction_putawayornament
+	assert_table_length NUM_DECO_ACTIONS + 1
 
 GetDecorationFlag:
 	call GetDecorationData
-	ld de, 3 ; event flag
+	ld de, DECOATTR_EVENT_FLAG
 	add hl, de
 	ld a, [hli]
 	ld d, [hl]
@@ -486,7 +492,7 @@
 GetDecorationSprite:
 	ld a, c
 	call GetDecorationData
-	ld de, 5 ; sprite
+	ld de, DECOATTR_SPRITE
 	add hl, de
 	ld a, [hl]
 	ld c, a
@@ -497,8 +503,8 @@
 INCLUDE "data/decorations/names.asm"
 
 GetDecoName:
-	ld a, [hli]
-	ld e, [hl]
+	ld a, [hli] ; DECOATTR_TYPE
+	ld e, [hl] ; DECOATTR_NAME
 	ld bc, wStringBuffer2
 	push bc
 	ld hl, .NameFunctions
@@ -507,6 +513,7 @@
 	ret
 
 .NameFunctions:
+	table_width 2, GetDecoName.NameFunctions
 	dw .invalid
 	dw .plant
 	dw .bed
@@ -514,37 +521,38 @@
 	dw .poster
 	dw .doll
 	dw .bigdoll
+	assert_table_length NUM_DECO_TYPES + 1
 
-.invalid
+.invalid:
 	ret
 
-.plant
+.plant:
 	ld a, e
 	jr .getdeconame
 
-.bed
+.bed:
 	call .plant
 	ld a, _BED
 	jr .getdeconame
 
-.carpet
+.carpet:
 	call .plant
 	ld a, _CARPET
 	jr .getdeconame
 
-.poster
+.poster:
 	ld a, e
 	call .getpokename
 	ld a, _POSTER
 	jr .getdeconame
 
-.doll
+.doll:
 	ld a, e
 	call .getpokename
 	ld a, _DOLL
 	jr .getdeconame
 
-.bigdoll
+.bigdoll:
 	push de
 	ld a, BIG_
 	call .getdeconame
@@ -552,7 +560,7 @@
 	ld a, e
 	jr .getpokename
 
-.unused ; unreferenced
+.unused: ; unreferenced
 	push de
 	call .getdeconame
 	pop de
@@ -559,7 +567,7 @@
 	ld a, e
 	jr .getdeconame
 
-.getpokename
+.getpokename:
 	push bc
 	ld [wNamedObjectIndex], a
 	call GetPokemonName
@@ -566,11 +574,11 @@
 	pop bc
 	jr .copy
 
-.getdeconame
+.getdeconame:
 	call ._getdeconame
 	jr .copy
 
-._getdeconame
+._getdeconame:
 	push bc
 	ld hl, DecorationNames
 	call GetNthString
@@ -579,7 +587,7 @@
 	pop bc
 	ret
 
-.copy
+.copy:
 	ld h, b
 	ld l, c
 	call CopyName2
@@ -968,11 +976,13 @@
 
 .JumpTable:
 ; entries correspond to DECODESC_* constants
+	table_width 2, DescribeDecoration.JumpTable
 	dw DecorationDesc_Poster
 	dw DecorationDesc_LeftOrnament
 	dw DecorationDesc_RightOrnament
 	dw DecorationDesc_GiantOrnament
 	dw DecorationDesc_Console
+	assert_table_length NUM_DECODESCS
 
 DecorationDesc_Poster:
 	ld a, [wDecoPoster]
--- a/engine/overworld/map_object_action.asm
+++ b/engine/overworld/map_object_action.asm
@@ -1,6 +1,7 @@
 ObjectActionPairPointers:
-; entries correspond to OBJECT_ACTION_* constants
+; entries correspond to OBJECT_ACTION_* constants (see constants/map_object_constants.asm)
 ; normal action, frozen action
+	table_width 2 + 2, ObjectActionPairPointers
 	dw SetFacingStanding,              SetFacingStanding
 	dw SetFacingStandAction,           SetFacingCurrent
 	dw SetFacingStepAction,            SetFacingCurrent
@@ -18,6 +19,7 @@
 	dw SetFacingBoulderDust,           SetFacingStanding
 	dw SetFacingGrassShake,            SetFacingStanding
 	dw SetFacingSkyfall,               SetFacingCurrent
+	assert_table_length NUM_OBJECT_ACTIONS
 
 SetFacingStanding:
 	ld hl, OBJECT_FACING_STEP
--- a/engine/overworld/map_objects.asm
+++ b/engine/overworld/map_objects.asm
@@ -523,7 +523,8 @@
 	ret
 
 .Pointers:
-; entries correspond to SPRITEMOVEFN_* constants
+; entries correspond to SPRITEMOVEFN_* constants (see constants/map_object_constants.asm)
+	table_width 2, StepFunction_FromMovement.Pointers
 	dw MovementFunction_Null                 ; 00
 	dw MovementFunction_RandomWalkY          ; 01
 	dw MovementFunction_RandomWalkX          ; 02
@@ -552,6 +553,7 @@
 	dw MovementFunction_SpinCounterclockwise ; 19
 	dw MovementFunction_BoulderDust          ; 1a
 	dw MovementFunction_ShakingGrass         ; 1b
+	assert_table_length NUM_SPRITEMOVEFN
 
 MovementFunction_Null:
 	ret
@@ -1085,7 +1087,8 @@
 	ret
 
 StepTypesJumptable:
-; entries correspond to STEP_TYPE_* constants
+; entries correspond to STEP_TYPE_* constants (see constants/map_object_constants.asm)
+	table_width 2, StepTypesJumptable
 	dw StepFunction_Reset           ; 00
 	dw StepFunction_FromMovement    ; 01
 	dw StepFunction_NPCWalk         ; 02
@@ -1112,6 +1115,7 @@
 	dw StepFunction_17              ; 17
 	dw StepFunction_Delete          ; 18
 	dw StepFunction_SkyfallTop      ; 19
+	assert_table_length NUM_STEP_TYPES
 
 WaitStep_InPlace:
 	ld hl, OBJECT_STEP_DURATION
--- a/engine/overworld/overworld.asm
+++ b/engine/overworld/overworld.asm
@@ -636,7 +636,7 @@
 LoadEmote::
 ; Get the address of the pointer to emote c.
 	ld a, c
-	ld bc, 6 ; sizeof(emote)
+	ld bc, EMOTE_LENGTH
 	ld hl, Emotes
 	call AddNTimes
 ; Load the emote address into de
--- a/engine/overworld/player_movement.asm
+++ b/engine/overworld/player_movement.asm
@@ -1,5 +1,4 @@
 DoPlayerMovement::
-
 	call .GetDPad
 	ld a, movement_step_sleep
 	ld [wMovementAnimation], a
@@ -12,7 +11,6 @@
 	ret
 
 .GetDPad:
-
 	ldh a, [hJoyDown]
 	ld [wCurInput], a
 
@@ -469,7 +467,8 @@
 	ret
 
 .Steps:
-; entries correspond to STEP_* constants
+; entries correspond to STEP_* constants (see constants/map_object_constants.asm)
+	table_width 2, DoPlayerMovement.Steps
 	dw .SlowStep
 	dw .NormalStep
 	dw .FastStep
@@ -478,6 +477,7 @@
 	dw .TurningStep
 	dw .BackJumpStep
 	dw .FinishFacing
+	assert_table_length NUM_STEPS
 
 .SlowStep:
 	slow_step DOWN
--- a/engine/phone/phone.asm
+++ b/engine/phone/phone.asm
@@ -247,7 +247,7 @@
 	ld c, a
 	ld b, 0
 	ld hl, SpecialPhoneCallList
-	ld a, 6
+	ld a, SPECIALCALL_SIZE
 	call AddNTimes
 	ld a, [hli]
 	ld h, [hl]
@@ -291,7 +291,7 @@
 	ld c, a
 	ld b, 0
 	ld hl, SpecialPhoneCallList
-	ld a, 6
+	ld a, SPECIALCALL_SIZE
 	call AddNTimes
 	ret
 
--- a/engine/pokedex/pokedex.asm
+++ b/engine/pokedex/pokedex.asm
@@ -1805,7 +1805,7 @@
 	jr .done
 
 .wrap_around
-	ld [hl], NUM_TYPES - 1
+	ld [hl], NUM_TYPES
 
 .done
 	scf
@@ -1818,7 +1818,7 @@
 
 	ld hl, wDexSearchMonType1
 	ld a, [hl]
-	cp NUM_TYPES - 1
+	cp NUM_TYPES
 	jr nc, .type1_wrap_around
 	inc [hl]
 	jr .done
@@ -1829,7 +1829,7 @@
 .type2
 	ld hl, wDexSearchMonType2
 	ld a, [hl]
-	cp NUM_TYPES - 1
+	cp NUM_TYPES
 	jr nc, .type2_wrap_around
 	inc [hl]
 	jr .done
@@ -1862,7 +1862,7 @@
 	ld e, a
 	ld d, 0
 	ld hl, PokedexTypeSearchStrings
-rept 9
+rept POKEDEX_TYPE_STRING_LENGTH
 	add hl, de
 endr
 	ld e, l
--- a/engine/pokegear/radio.asm
+++ b/engine/pokegear/radio.asm
@@ -20,6 +20,7 @@
 
 RadioJumptable:
 ; entries correspond to constants/radio_constants.asm
+	table_width 2, RadioJumptable
 	dw OaksPKMNTalk1     ; $00
 	dw PokedexShow1      ; $01
 	dw BenMonMusic1      ; $02
@@ -31,6 +32,7 @@
 	dw PokeFluteRadio    ; $08
 	dw UnownRadio        ; $09
 	dw EvolutionRadio    ; $0a
+	assert_table_length NUM_RADIO_CHANNELS
 ; OaksPKMNTalk
 	dw OaksPKMNTalk2     ; $0b
 	dw OaksPKMNTalk3     ; $0c
@@ -116,6 +118,7 @@
 	dw PokedexShow6      ; $55
 	dw PokedexShow7      ; $56
 	dw PokedexShow8      ; $57
+	assert_table_length NUM_RADIO_SEGMENTS
 
 PrintRadioLine:
 	ld [wNextRadioLine], a
@@ -193,10 +196,9 @@
 ; Choose a random route, and a random Pokemon from that route.
 .sample
 	call Random
-	and %11111 ; maskbits NUM_OAKS_POKEMON_TALK_ROUTES would be more efficient
-	cp NUM_OAKS_POKEMON_TALK_ROUTES
+	and %11111
+	cp (OaksPKMNTalkRoutes.End - OaksPKMNTalkRoutes) / 2
 	jr nc, .sample
-	; We now have a number between 0 and NUM_OAKS_POKEMON_TALK_ROUTES - 1.
 	ld hl, OaksPKMNTalkRoutes
 	ld c, a
 	ld b, 0
@@ -347,7 +349,7 @@
 	jp NextRadioLine
 
 .Adverbs:
-; there are NUM_OAKS_POKEMON_TALK_ADVERBS entries
+	table_width 2, OaksPKMNTalk8.Adverbs
 	dw .OPT_SweetAdorablyText
 	dw .OPT_WigglySlicklyText
 	dw .OPT_AptlyNamedText
@@ -364,6 +366,7 @@
 	dw .OPT_ProvocativelyText
 	dw .OPT_FlippedOutText
 	dw .OPT_HeartMeltinglyText
+	assert_table_length NUM_OAKS_POKEMON_TALK_ADVERBS
 
 .OPT_SweetAdorablyText:
 	text_far _OPT_SweetAdorablyText
@@ -455,7 +458,7 @@
 	jp NextRadioLine
 
 .Adjectives:
-; there are NUM_OAKS_POKEMON_TALK_ADJECTIVES entries
+	table_width 2, OaksPKMNTalk9.Adjectives
 	dw .OPT_CuteText
 	dw .OPT_WeirdText
 	dw .OPT_PleasantText
@@ -472,6 +475,7 @@
 	dw .OPT_GuardedText
 	dw .OPT_LovelyText
 	dw .OPT_SpeedyText
+	assert_table_length NUM_OAKS_POKEMON_TALK_ADJECTIVES
 
 .OPT_CuteText:
 	text_far _OPT_CuteText
@@ -1090,7 +1094,7 @@
 	call Random
 	maskbits NUM_TRAINER_CLASSES
 	inc a
-	cp NUM_TRAINER_CLASSES - 1 ; omit MYSTICALMAN
+	cp NUM_TRAINER_CLASSES
 	jr nc, PeoplePlaces4
 	push af
 	ld hl, PnP_HiddenPeople
@@ -1154,7 +1158,7 @@
 	jp NextRadioLine
 
 .Adjectives:
-; there are NUM_PNP_PEOPLE_ADJECTIVES entries
+	table_width 2, PeoplePlaces5.Adjectives
 	dw PnP_CuteText
 	dw PnP_LazyText
 	dw PnP_HappyText
@@ -1171,6 +1175,7 @@
 	dw PnP_WeirdText
 	dw PnP_RightForMeText
 	dw PnP_OddText
+	assert_table_length NUM_PNP_PEOPLE_ADJECTIVES
 
 PnP_CuteText:
 	text_far _PnP_CuteText
@@ -1289,7 +1294,7 @@
 	jp PrintRadioLine
 
 .Adjectives:
-; there are NUM_PNP_PLACES_ADJECTIVES entries
+	table_width 2, PeoplePlaces7.Adjectives
 	dw PnP_CuteText
 	dw PnP_LazyText
 	dw PnP_HappyText
@@ -1306,6 +1311,7 @@
 	dw PnP_WeirdText
 	dw PnP_RightForMeText
 	dw PnP_OddText
+	assert_table_length NUM_PNP_PLACES_ADJECTIVES
 
 RocketRadio1:
 	call StartRadioStation
--- a/engine/tilesets/mapgroup_roofs.asm
+++ b/engine/tilesets/mapgroup_roofs.asm
@@ -8,10 +8,10 @@
 	cp -1
 	ret z
 	ld hl, Roofs
-	ld bc, 9 tiles
+	ld bc, ROOF_LENGTH tiles
 	call AddNTimes
 	ld de, vTiles2 tile $0a
-	ld bc, 9 tiles
+	ld bc, ROOF_LENGTH tiles
 	call CopyBytes
 	ret
 
--- a/gfx/footprints.asm
+++ b/gfx/footprints.asm
@@ -9,6 +9,8 @@
 
 ; Entries correspond to Pokémon species, two apiece, 8 tops then 8 bottoms
 
+	table_width LEN_1BPP_TILE * 4, Footprints
+
 ; 001-008 top halves
 INCBIN "gfx/footprints/bulbasaur.1bpp",  footprint_top
 INCBIN "gfx/footprints/ivysaur.1bpp",    footprint_top
@@ -585,3 +587,5 @@
 INCBIN "gfx/footprints/254.1bpp",        footprint_bottom
 INCBIN "gfx/footprints/255.1bpp",        footprint_bottom
 INCBIN "gfx/footprints/256.1bpp",        footprint_bottom
+
+	assert_table_length $100
--- a/gfx/pokemon/anim_pointers.asm
+++ b/gfx/pokemon/anim_pointers.asm
@@ -1,4 +1,5 @@
 AnimationPointers:
+	table_width 2, AnimationPointers
 	dw BulbasaurAnimation
 	dw IvysaurAnimation
 	dw VenusaurAnimation
@@ -250,3 +251,4 @@
 	dw LugiaAnimation
 	dw HoOhAnimation
 	dw CelebiAnimation
+	assert_table_length NUM_POKEMON
--- a/gfx/pokemon/bitmask_pointers.asm
+++ b/gfx/pokemon/bitmask_pointers.asm
@@ -1,4 +1,5 @@
 BitmasksPointers:
+	table_width 2, BitmasksPointers
 	dw BulbasaurBitmasks
 	dw IvysaurBitmasks
 	dw VenusaurBitmasks
@@ -250,3 +251,4 @@
 	dw LugiaBitmasks
 	dw HoOhBitmasks
 	dw CelebiBitmasks
+	assert_table_length NUM_POKEMON
--- a/gfx/pokemon/frame_pointers.asm
+++ b/gfx/pokemon/frame_pointers.asm
@@ -1,4 +1,5 @@
 FramesPointers:
+	table_width 2, FramesPointers
 	dw BulbasaurFrames
 	dw IvysaurFrames
 	dw VenusaurFrames
@@ -250,3 +251,4 @@
 	dw LugiaFrames
 	dw HoOhFrames
 	dw CelebiFrames
+	assert_table_length NUM_POKEMON
--- a/gfx/pokemon/idle_pointers.asm
+++ b/gfx/pokemon/idle_pointers.asm
@@ -1,4 +1,5 @@
 AnimationIdlePointers:
+	table_width 2, AnimationIdlePointers
 	dw BulbasaurAnimationIdle
 	dw IvysaurAnimationIdle
 	dw VenusaurAnimationIdle
@@ -250,3 +251,4 @@
 	dw LugiaAnimationIdle
 	dw HoOhAnimationIdle
 	dw CelebiAnimationIdle
+	assert_table_length NUM_POKEMON
--- a/gfx/pokemon/unown_anim_pointers.asm
+++ b/gfx/pokemon/unown_anim_pointers.asm
@@ -1,4 +1,5 @@
 UnownAnimationPointers:
+	table_width 2, UnownAnimationPointers
 	dw UnownAAnimation
 	dw UnownBAnimation
 	dw UnownCAnimation
@@ -25,3 +26,4 @@
 	dw UnownXAnimation
 	dw UnownYAnimation
 	dw UnownZAnimation
+	assert_table_length NUM_UNOWN
--- a/gfx/pokemon/unown_bitmask_pointers.asm
+++ b/gfx/pokemon/unown_bitmask_pointers.asm
@@ -1,4 +1,5 @@
 UnownBitmasksPointers:
+	table_width 2, UnownBitmasksPointers
 	dw UnownABitmasks
 	dw UnownBBitmasks
 	dw UnownCBitmasks
@@ -25,3 +26,4 @@
 	dw UnownXBitmasks
 	dw UnownYBitmasks
 	dw UnownZBitmasks
+	assert_table_length NUM_UNOWN
--- a/gfx/pokemon/unown_frame_pointers.asm
+++ b/gfx/pokemon/unown_frame_pointers.asm
@@ -1,4 +1,5 @@
 UnownFramesPointers:
+	table_width 2, UnownFramesPointers
 	dw UnownAFrames
 	dw UnownBFrames
 	dw UnownCFrames
@@ -25,3 +26,4 @@
 	dw UnownXFrames
 	dw UnownYFrames
 	dw UnownZFrames
+	assert_table_length NUM_UNOWN
--- a/gfx/pokemon/unown_idle_pointers.asm
+++ b/gfx/pokemon/unown_idle_pointers.asm
@@ -1,4 +1,5 @@
 UnownAnimationIdlePointers:
+	table_width 2, UnownAnimationIdlePointers
 	dw UnownAAnimationIdle
 	dw UnownBAnimationIdle
 	dw UnownCAnimationIdle
@@ -25,3 +26,4 @@
 	dw UnownXAnimationIdle
 	dw UnownYAnimationIdle
 	dw UnownZAnimationIdle
+	assert_table_length NUM_UNOWN
--- a/home/audio.asm
+++ b/home/audio.asm
@@ -143,7 +143,7 @@
 	ld [MBC3RomBank], a
 
 	ld hl, PokemonCries
-rept 6 ; sizeof(mon_cry)
+rept MON_CRY_LENGTH
 	add hl, de
 endr
 
--- a/home/battle_vars.asm
+++ b/home/battle_vars.asm
@@ -49,6 +49,7 @@
 
 BattleVarPairs:
 ; entries correspond to BATTLE_VARS_* constants
+	table_width 2, BattleVarPairs
 	dw .Substatus1
 	dw .Substatus2
 	dw .Substatus3
@@ -70,6 +71,7 @@
 	dw .LastCounterOpp
 	dw .LastMove
 	dw .LastMoveOpp
+	assert_table_length NUM_BATTLE_VARS
 
 ;                   player                 enemy
 .Substatus1:     db PLAYER_SUBSTATUS_1,    ENEMY_SUBSTATUS_1
@@ -96,6 +98,7 @@
 
 BattleVarLocations:
 ; entries correspond to PLAYER_* and ENEMY_* constants
+	table_width 2 + 2, BattleVarLocations
 	dw wPlayerSubStatus1,          wEnemySubStatus1
 	dw wPlayerSubStatus2,          wEnemySubStatus2
 	dw wPlayerSubStatus3,          wEnemySubStatus3
@@ -109,3 +112,4 @@
 	dw wCurPlayerMove,             wCurEnemyMove
 	dw wLastPlayerCounterMove,     wLastEnemyCounterMove
 	dw wLastPlayerMove,            wLastEnemyMove
+	assert_table_length NUM_BATTLE_VAR_LOCATION_PAIRS
--- a/home/map.asm
+++ b/home/map.asm
@@ -2285,12 +2285,12 @@
 	push bc
 
 	ld hl, Tilesets
-	ld bc, wTilesetEnd - wTileset
+	ld bc, TILESET_LENGTH
 	ld a, [wMapTileset]
 	call AddNTimes
 
 	ld de, wTilesetBank
-	ld bc, wTilesetEnd - wTileset
+	ld bc, TILESET_LENGTH
 
 	ld a, BANK(Tilesets)
 	call FarCopyBytes
--- a/home/pokemon.asm
+++ b/home/pokemon.asm
@@ -166,7 +166,7 @@
 	rst Bankswitch
 
 	ld hl, PokemonCries
-rept 6 ; sizeof(mon_cry)
+rept MON_CRY_LENGTH
 	add hl, bc
 endr
 
--- a/macros.asm
+++ b/macros.asm
@@ -1,3 +1,4 @@
+INCLUDE "macros/asserts.asm"
 INCLUDE "macros/const.asm"
 INCLUDE "macros/predef.asm"
 INCLUDE "macros/rst.asm"
--- /dev/null
+++ b/macros/asserts.asm
@@ -1,0 +1,20 @@
+; 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
--- a/macros/scripts/battle_commands.asm
+++ b/macros/scripts/battle_commands.asm
@@ -180,6 +180,7 @@
 	command supereffectivelooptext  ; ad
 	command startloop               ; ae
 	command curl                    ; af
+NUM_EFFECT_COMMANDS EQU const_value - 1
 
 	const_def -1, -1
 	command endmove                 ; ff
--- a/wram.asm
+++ b/wram.asm
@@ -2254,6 +2254,7 @@
 	ds 2 ; unused
 wTilesetPalettes:: dw ; bank 3f
 wTilesetEnd::
+	assert wTilesetEnd - wTileset == TILESET_LENGTH
 
 wEvolvableFlags:: flag_array PARTY_LENGTH
 
@@ -2489,6 +2490,7 @@
 wBaseEggGroups:: db
 wBaseTMHM:: flag_array NUM_TM_HM_TUTOR
 wCurBaseDataEnd::
+	assert wCurBaseDataEnd - wCurBaseData == BASE_DATA_SIZE
 
 wCurDamage:: dw