Split the Special Stat - pret/pokered GitHub Wiki

Introduction

This tutorial details how to replace the SPECIAL stat with special attack and special defense as later generations of Pokémon games do. This tutorial assumes you are modifying a vanilla (unmodified) copy of pokered, but the included explanations should be enough to help regardless of your starting point.

DISCLAIMER: The modifications below are extensive, and though they have been tested, unintended consequences may arise. If you discover any bugs, or any issues with the tutorial itself, feel free to fix them here.

Contents

Updating Pokémon Data Structures

Constants and Names

This tutorial follows the Gen 2 naming convention of SPCL.ATK and SPCL.DEF, and the ordering convention of keeping SPEED before the special stats. Doing so minimizes the changes we will need to make to stat calculation/display later.

constants/battle_constants.asm

Even if you do not intend to add a new vitamin, adding the new constant here is required. This increases the value of NUM_STATS and allows the battle engine to reference this stat later.

...
; VitaminStats indexes (see data/battle/stat_names.asm)
	const_def 1
	const STAT_HEALTH
	const STAT_ATTACK
	const STAT_DEFENSE
	const STAT_SPEED
-	const STAT_SPECIAL
+	const STAT_SPCLATK
+	const STAT_SPCLDEF
DEF NUM_STATS EQU const_value - 1

; StatModTextStrings indexes (see data/battle/stat_mod_names.asm)
	const_def
	const MOD_ATTACK
	const MOD_DEFENSE
	const MOD_SPEED
-	const MOD_SPECIAL
+	const MOD_SPCLATK
+	const MOD_SPCLDEF
	const MOD_ACCURACY
	const MOD_EVASION
	const_skip 2
DEF NUM_STAT_MODS EQU const_value
...

constants/pokemon_data_constants.asm

These changes parallel the later changes made to memory addresses.

; base data struct members (see data/pokemon/base_stats/*.asm)
rsreset
DEF BASE_DEX_NO      rb
DEF BASE_STATS       rb NUM_STATS
rsset BASE_STATS
DEF BASE_HP          rb
DEF BASE_ATK         rb
DEF BASE_DEF         rb
DEF BASE_SPD         rb
-DEF BASE_SPC         rb
+DEF BASE_SAT         rb
+DEF BASE_SDF         rb
...
DEF MON_EXP        rb 3
DEF MON_HP_EXP     rw
DEF MON_ATK_EXP    rw
DEF MON_DEF_EXP    rw
DEF MON_SPD_EXP    rw
-DEF MON_SPC_EXP    rw
+DEF MON_SAT_EXP    rw
+DEF MON_SDF_EXP    rw
DEF MON_DVS        rw
DEF MON_PP         rb NUM_MOVES
DEF BOXMON_STRUCT_LENGTH EQU _RS
DEF MON_LEVEL      rb
DEF MON_STATS      rw NUM_STATS
rsset MON_STATS
DEF MON_MAXHP      rw
DEF MON_ATK        rw
DEF MON_DEF        rw
DEF MON_SPD        rw
-DEF MON_SPC        rw
+DEF MON_SAT        rw
+DEF MON_SDF        rw
DEF PARTYMON_STRUCT_LENGTH EQU _RS

DEF PARTY_LENGTH EQU 6

-DEF MONS_PER_BOX EQU 20
+DEF MONS_PER_BOX EQU 18 ; Adding special defense means boxed mons will take up more space. Decreased this value from 20 so that the Stack can still have the same size (see ram/wram.asm).
DEF NUM_BOXES    EQU 12

DEF HOF_MON           EQU $10
...

data/battle/stat_mod_names.asm

; Stats that move effects can raise or lower
; The relevant move effect IDs correspond to the stats

StatModTextStrings:
	list_start
	li "ATTACK"
	li "DEFENSE"
	li "SPEED"
-	li "SPECIAL"
-	assert_list_length SPECIAL_DOWN_SIDE_EFFECT - ATTACK_DOWN_SIDE_EFFECT + 1
+	li "SPCL.ATK"
+	li "SPCL.DEF"
+	assert_list_length SPCLDEF_DOWN_SIDE_EFFECT - ATTACK_DOWN_SIDE_EFFECT + 1
	li "ACCURACY"
...

SPCLDEF_DOWN_SIDE_EFFECT will be added in a later step. See Adding and Updating Move Effects.

data/battle/stat_names.asm

; Stats that vitamins can raise or lower

VitaminStats:
	list_start
	li "HEALTH"
	li "ATTACK"
	li "DEFENSE"
	li "SPEED"
-   li "SPECIAL"
+	li "SPCL.ATK"
+	li "SPCL.DEF"
	assert_list_length NUM_STATS

Alternatively, if you don't intend to add another vitamin:

; Stats that vitamins can raise or lower

VitaminStats:
	list_start
	li "HEALTH"
	li "ATTACK"
	li "DEFENSE"
	li "SPEED"
-   li "SPECIAL"
-	assert_list_length NUM_STATS
+	li "SPCL.ATK"
+	assert_list_length NUM_STATS - 1 ; Zinc not added until Gen 3

RAM Addresses

The difference introduced between box_struct and battle_struct will be accounted for later:

macros/ram.asm

; Used in wram.asm

MACRO flag_array
	ds ((\1) + 7) / 8
ENDM

-DEF BOX_STRUCT_LENGTH EQU 25 + NUM_MOVES * 2
+DEF BOX_STRUCT_LENGTH EQU 27 + NUM_MOVES * 2

+; The distance from HPExp to DVs used to be 11 bytes, but is now 13
MACRO box_struct
\1Species::    db
\1HP::         dw
\1BoxLevel::   db
\1Status::     db
\1Type::
\1Type1::      db
\1Type2::      db
\1CatchRate::  db
\1Moves::      ds NUM_MOVES
\1OTID::       dw
\1Exp::        ds 3
\1HPExp::      dw
\1AttackExp::  dw
\1DefenseExp:: dw
\1SpeedExp::   dw
-\1SpecialExp:: dw
+\1SpclAtkExp:: dw
+\1SpclDefExp:: dw
\1DVs::        dw
\1PP::         ds NUM_MOVES
ENDM

MACRO party_struct
	box_struct \1
\1Level::      db
\1Stats::
\1MaxHP::      dw
\1Attack::     dw
\1Defense::    dw
\1Speed::      dw
-\1Special::    dw
+\1SpclAtk::    dw
+\1SpclDef::    dw
ENDM

+; The distance from HP to DVs is still 11 bytes
MACRO battle_struct
\1Species::    db
\1HP::         dw
\1PartyPos::
\1BoxLevel::   db
\1Status::     db
\1Type::
\1Type1::      db
\1Type2::      db
\1CatchRate::  db
\1Moves::      ds NUM_MOVES
\1DVs::        dw
\1Level::      db
\1Stats::
\1MaxHP::      dw
\1Attack::     dw
\1Defense::    dw
\1Speed::      dw
-\1Special::    dw
+\1SpclAtk::    dw
+\1SpclDef::    dw
\1PP::         ds NUM_MOVES
ENDM
...

ram/wram.asm

...
-; This union spans 39 bytes.
+; This union spans 45 bytes.
UNION
wInGameTradeGiveMonSpecies:: db
wInGameTradeTextPointerTablePointer:: dw
wInGameTradeTextPointerTableIndex:: db
wInGameTradeGiveMonName:: ds NAME_LENGTH
wInGameTradeReceiveMonName:: ds NAME_LENGTH
wInGameTradeMonNick:: ds NAME_LENGTH
wInGameTradeReceiveMonSpecies:: db

NEXTU
wPlayerMonUnmodifiedLevel:: db
wPlayerMonUnmodifiedMaxHP:: dw
wPlayerMonUnmodifiedAttack:: dw
wPlayerMonUnmodifiedDefense:: dw
wPlayerMonUnmodifiedSpeed:: dw
-wPlayerMonUnmodifiedSpecial:: dw
+wPlayerMonUnmodifiedSpclAtk:: dw
+wPlayerMonUnmodifiedSpclDef:: dw

; stat modifiers for the player's current pokemon
; value can range from 1 - 13 ($1 to $D)
; 7 is normal
wPlayerMonStatMods::
wPlayerMonAttackMod:: db
wPlayerMonDefenseMod:: db
wPlayerMonSpeedMod:: db
-wPlayerMonSpecialMod:: db
+wPlayerMonSpclAtkMod:: db
+wPlayerMonSpclDefMod:: db
wPlayerMonAccuracyMod:: db
wPlayerMonEvasionMod:: db
	ds 2
wPlayerMonStatModsEnd::

	ds 1

wEnemyMonUnmodifiedLevel:: db
wEnemyMonUnmodifiedMaxHP:: dw
wEnemyMonUnmodifiedAttack:: dw
wEnemyMonUnmodifiedDefense:: dw
wEnemyMonUnmodifiedSpeed:: dw
-wEnemyMonUnmodifiedSpecial:: dw
+wEnemyMonUnmodifiedSpclAtk:: dw
+wEnemyMonUnmodifiedSpclDef:: dw

; stat modifiers for the enemy's current pokemon
; value can range from 1 - 13 ($1 to $D)
; 7 is normal
wEnemyMonStatMods::
wEnemyMonAttackMod:: db
wEnemyMonDefenseMod:: db
wEnemyMonSpeedMod:: db
-wEnemyMonSpecialMod:: db
+wEnemyMonSpclAtkMod:: db
+wEnemyMonSpclDefMod:: db
wEnemyMonAccuracyMod:: db
wEnemyMonEvasionMod:: db
...
ENDU
...
wMonHeader::
; In the ROM base stats data structure, this is the dex number, but it is
; overwritten with the internal index number after the header is copied to WRAM.
wMonHIndex:: db
wMonHBaseStats::
wMonHBaseHP:: db
wMonHBaseAttack:: db
wMonHBaseDefense:: db
wMonHBaseSpeed:: db
-wMonHBaseSpecial:: db
+wMonHBaseSpclAtk:: db
+wMonHBaseSpclDef:: db
...

Pokémon Data

The base stats for every single Pokémon will need to be updated with a value for special defense. This will take a while, so go grab a drink or a snack and get comfortable while you work. Here is an example:

data/pokemon/base_stats/abra.asm

	db DEX_ABRA ; pokedex id

-	db  25,  20,  15,  90, 105
-	;   hp  atk  def  spd  spc
+	db  25,  20,  15,  90, 105,  55
+	;   hp  atk  def  spd  sat  sdf
...

List of stats

Below is a list of the stats for the original 151 Pokémon as they were in the Gen 2 games, arranged alphabetically to make it easier to follow as you modify each file in the data/pokemon/base_stats folder. The comment for each line also lists instructions for how to update base stats to their modern (as of Gen 9) values. If you intend to use modern stats, now is a good time to make that change.

	db:  25,  20,  15,  90, 105,  55 ; ABRA
	db:  80, 105,  65, 130,  60,  75 ; AERODACTYL
	db:  55,  50,  45, 120, 135,  85 ; ALAKAZAM (+10 sdf for modern stats)
	db:  60,  85,  69,  80,  65,  79 ; ARBOK
	db:  90, 110,  80,  95, 100,  80 ; ARCANINE
	db:  90,  85, 100,  85,  95, 125 ; ARTICUNO
	db:  65,  80,  40,  75,  45,  80 ; BEEDRILL (+10 atk for modern stats)
	db:  50,  75,  35,  40,  70,  30 ; BELLSPROUT
	db:  79,  83, 100,  78,  85, 105 ; BLASTOISE
	db:  45,  49,  49,  45,  65,  65 ; BULBASAUR
	db:  60,  45,  50,  70,  80,  80 ; BUTTERFREE (+10 sat for modern stats)
	db:  45,  30,  35,  45,  20,  20 ; CATERPIE
	db: 250,   5,   5,  50,  35, 105 ; CHANSEY
	db:  78,  84,  78, 100, 109,  85 ; CHARIZARD
	db:  39,  52,  43,  65,  60,  50 ; CHARMANDER
	db:  58,  64,  58,  80,  80,  65 ; CHARMELEON
	db:  95,  70,  73,  60,  85,  90 ; CLEFABLE (+10 sat for modern stats)
	db:  70,  45,  48,  35,  60,  65 ; CLEFAIRY
	db:  50,  95, 180,  70,  85,  45 ; CLOYSTER
	db:  50,  50,  95,  35,  40,  50 ; CUBONE
	db:  90,  70,  80,  70,  70,  95 ; DEWGONG
	db:  10,  55,  25,  95,  35,  45 ; DIGLETT
	db:  48,  48,  48,  48,  48,  48 ; DITTO
	db:  60, 110,  70, 100,  60,  60 ; DODRIO (+10 spd for modern stats)
	db:  35,  85,  45,  75,  35,  35 ; DODUO
	db:  61,  84,  65,  70,  70,  70 ; DRAGONAIR
	db:  91, 134,  95,  80, 100, 100 ; DRAGONITE
	db:  41,  64,  45,  50,  50,  50 ; DRATINI
	db:  60,  48,  45,  42,  43,  90 ; DROWZEE
	db:  35,  80,  50, 120,  50,  70 ; DUGTRIO (+20 atk for modern stats)
	db:  55,  55,  50,  55,  45,  65 ; EEVEE
	db:  35,  60,  44,  55,  40,  54 ; EKANS
	db:  65,  83,  57, 105,  95,  85 ; ELECTABUZZ
	db:  60,  50,  70, 140,  80,  80 ; ELECTRODE (+10 spd for modern stats)
	db:  60,  40,  80,  40,  60,  45 ; EXEGGCUTE
	db:  95,  95,  85,  55, 125,  65 ; EXEGGUTOR (+10 sdf for modern stats)
	db:  52,  65,  55,  60,  58,  62 ; FARFETCH'D (+25 atk for modern stats)
	db:  65,  90,  65, 100,  61,  61 ; FEAROW
	db:  65, 130,  60,  65,  95, 110 ; FLAREON
	db:  30,  35,  30,  80, 100,  35 ; GASTLY
	db:  60,  65,  60, 110, 130,  75 ; GENGAR
	db:  40,  80, 100,  20,  30,  30 ; GEODUDE
	db:  60,  65,  70,  40,  85,  75 ; GLOOM
	db:  75,  80,  70,  90,  65,  75 ; GOLBAT
	db:  45,  67,  60,  63,  35,  50 ; GOLDEEN
	db:  80,  82,  78,  85,  95,  80 ; GOLDUCK
	db:  80, 110, 130,  45,  55,  65 ; GOLEM (+10 atk for modern stats)
	db:  55,  95, 115,  35,  45,  45 ; GRAVELER
	db:  80,  80,  50,  25,  40,  50 ; GRIMER
	db:  55,  70,  45,  60,  70,  50 ; GROWLITHE
	db:  95, 125,  79,  81,  60, 100 ; GYARADOS
	db:  45,  50,  45,  95, 115,  55 ; HAUNTER
	db:  50, 105,  79,  76,  35, 110 ; HITMONCHAN
	db:  50, 120,  53,  87,  35, 110 ; HITMONLEE
	db:  30,  40,  70,  60,  70,  25 ; HORSEA
	db:  85,  73,  70,  67,  73, 115 ; HYPNO
	db:  60,  62,  63,  60,  80,  80 ; IVYSAUR
	db: 115,  45,  20,  20,  45,  25 ; JIGGLYPUFF
	db:  65,  65,  60, 130, 110,  95 ; JOLTEON
	db:  65,  50,  35,  95, 115,  95 ; JYNX
	db:  30,  80,  90,  55,  55,  45 ; KABUTO
	db:  60, 115, 105,  80,  65,  70 ; KABUTOPS
	db:  40,  35,  30, 105, 120,  70 ; KADABRA
	db:  45,  25,  50,  35,  25,  25 ; KAKUNA
	db: 105,  95,  80,  90,  40,  80 ; KANGASKHAN
	db:  55, 130, 115,  75,  50,  50 ; KINGLER
	db:  40,  65,  95,  35,  60,  45 ; KOFFING
	db:  30, 105,  90,  50,  25,  25 ; KRABBY
	db: 130,  85,  80,  60,  85,  95 ; LAPRAS
	db:  90,  55,  75,  30,  60,  75 ; LICKITUNG
	db:  90, 130,  80,  55,  65,  85 ; MACHAMP
	db:  80, 100,  70,  45,  50,  60 ; MACHOKE
	db:  70,  80,  50,  35,  35,  35 ; MACHOP
	db:  20,  10,  55,  80,  15,  20 ; MAGIKARP
	db:  65,  95,  57,  93, 100,  85 ; MAGMAR
	db:  25,  35,  70,  45,  95,  55 ; MAGNEMITE
	db:  50,  60,  95,  70, 120,  70 ; MAGNETON
	db:  40,  80,  35,  70,  35,  45 ; MANKEY
	db:  60,  80, 110,  45,  50,  80 ; MAROWAK
	db:  40,  45,  35,  90,  40,  40 ; MEOWTH
	db:  50,  20,  55,  30,  25,  25 ; METAPOD
	db: 100, 100, 100, 100, 100, 100 ; MEW
	db: 106, 110,  90, 130, 154,  90 ; MEWTWO
	db:  90, 100,  90,  90, 125,  85 ; MOLTRES
	db:  40,  45,  65,  90, 100, 120 ; MR. MIME
	db: 105, 105,  75,  50,  65, 100 ; MUK
	db:  81,  92,  77,  85,  85,  75 ; NIDOKING (+10 atk for modern stats)
	db:  90,  82,  87,  76,  75,  85 ; NIDOQUEEN (+10 atk for modern stats)
	db:  55,  47,  52,  41,  40,  40 ; NIDORAN_F
	db:  46,  57,  40,  50,  40,  40 ; NIDORAN_M
	db:  70,  62,  67,  56,  55,  55 ; NIDORINA
	db:  61,  72,  57,  65,  55,  55 ; NIDORINO
	db:  73,  76,  75, 100,  81, 100 ; NINETALES
	db:  45,  50,  55,  30,  75,  65 ; ODDISH
	db:  35,  40, 100,  35,  90,  55 ; OMANYTE
	db:  70,  60, 125,  55, 115,  70 ; OMASTAR
	db:  35,  45, 160,  70,  30,  45 ; ONIX
	db:  35,  70,  55,  25,  45,  55 ; PARAS
	db:  60,  95,  80,  30,  60,  80 ; PARASECT
	db:  65,  70,  60, 115,  65,  65 ; PERSIAN
	db:  83,  80,  75,  91,  70,  70 ; PIDGEOT (+10 spd for modern stats)
	db:  63,  60,  55,  71,  50,  50 ; PIDGEOTTO
	db:  40,  45,  40,  56,  35,  35 ; PIDGEY
	db:  35,  55,  30,  90,  50,  40 ; PIKACHU (+10 def and sdf for modern stats)
	db:  65, 125, 100,  85,  55,  70 ; PINSIR
	db:  40,  50,  40,  90,  40,  40 ; POLIWAG
	db:  65,  65,  65,  90,  50,  50 ; POLIWHIRL
	db:  90,  85,  95,  70,  70,  90 ; POLIWRATH (+10 atk for modern stats)
	db:  50,  85,  55,  90,  65,  65 ; PONYTA
	db:  65,  60,  70,  40,  85,  75 ; PORYGON
	db:  65, 105,  60,  95,  60,  70 ; PRIMEAPE
	db:  50,  52,  48,  55,  65,  50 ; PSYDUCK
	db:  60,  90,  55, 100,  90,  80 ; RAICHU (+10 spd for modern stats)
	db:  65, 100,  70, 105,  80,  80 ; RAPIDASH
	db:  55,  81,  60,  97,  50,  70 ; RATICATE
	db:  30,  56,  35,  72,  25,  35 ; RATTATA
	db: 105, 130, 120,  40,  45,  45 ; RHYDON
	db:  80,  85,  95,  25,  30,  30 ; RHYHORN
	db:  50,  75,  85,  40,  20,  30 ; SANDSHREW
	db:  75, 100, 110,  65,  45,  55 ; SANDSLASH
	db:  70, 110,  80, 105,  55,  80 ; SCYTHER
	db:  55,  65,  95,  85,  95,  45 ; SEADRA
	db:  80,  92,  65,  68,  65,  80 ; SEAKING
	db:  65,  45,  55,  45,  45,  70 ; SEEL
	db:  30,  65, 100,  40,  45,  25 ; SHELLDER
	db:  95,  75, 110,  30, 100,  80 ; SLOWBRO
	db:  90,  65,  65,  15,  40,  40 ; SLOWPOKE
	db: 160, 110,  65,  30,  65, 110 ; SNORLAX
	db:  40,  60,  30,  70,  31,  31 ; SPEAROW
	db:  44,  48,  65,  43,  50,  64 ; SQUIRTLE
	db:  60,  75,  85, 115, 100,  85 ; STARMIE
	db:  30,  45,  55,  85,  70,  55 ; STARYU
	db:  65,  55, 115,  60, 100,  40 ; TANGELA
	db:  75, 100,  95, 110,  40,  70 ; TAUROS
	db:  40,  40,  35,  70,  50, 100 ; TENTACOOL
	db:  80,  70,  65, 100,  80, 120 ; TENTACRUEL
	db: 130,  65,  60,  65, 110,  95 ; VAPOREON
	db:  70,  65,  60,  90,  90,  75 ; VENOMOTH
	db:  60,  55,  50,  45,  40,  55 ; VENONAT
	db:  80,  82,  83,  80, 100, 100 ; VENUSAUR
	db:  80, 105,  65,  70, 100,  60 ; VICTREEBEL (+10 sdf for modern stats)
	db:  75,  80,  85,  50, 100,  90 ; VILEPLUME (+10 sat for modern stats)
	db:  40,  30,  50, 100,  55,  55 ; VOLTORB
	db:  38,  41,  40,  65,  50,  65 ; VULPIX
	db:  59,  63,  80,  58,  65,  80 ; WARTORTLE
	db:  40,  35,  30,  50,  20,  20 ; WEEDLE
	db:  65,  90,  50,  55,  85,  45 ; WEEPINBELL
	db:  65,  90, 120,  60,  85,  70 ; WEEZING
	db: 140,  70,  45,  45,  75,  50 ; WIGGLYTUFF (+10 sat for modern stats)
	db:  90,  90,  85, 100, 125,  90 ; ZAPDOS
	db:  40,  45,  35,  55,  30,  40 ; ZUBAT

Engine Changes

Adding Special Defense Calculation

Stat calculation requires miniminal changes since NUM_STATS has been increased and the values for special defense immediately follow those of special attack in memory. Besides updating comments, the only necessary change is specifying the value to use for special attack DV. This tutorial follows the Gen 2 strategy of reusing the special attack DV. Adding a new DV for special defense (and for HP) is beyond the scope of this tutorial.

home/move_mon.asm

...
-; calculates all 5 stats of current mon and writes them to [de]
+; calculates all 6 stats of current mon and writes them to [de]
CalcStats::
	ld c, $0
.statsLoop
	inc c
	call CalcStat
	ldh a, [hMultiplicand+1]
	ld [de], a
	inc de
	ldh a, [hMultiplicand+2]
	ld [de], a
	inc de
	ld a, c
	cp NUM_STATS
	jr nz, .statsLoop
	ret

; calculates stat c of current mon
-; c: stat to calc (HP=1,Atk=2,Def=3,Spd=4,Spc=5)
+; c: stat to calc (HP=1,Atk=2,Def=3,Spd=4,Sat=5,Sdf=6)
...
	cp $5
	jr z, .getSpecialIV
+	cp $6
+	jr z, .getSpecialIV ; Like Gen 2, special attack and special defense share an "IV" (DV)
.getHpIV
...

Updating Damage Calculation

engine/battle/core.asm

GetDamageVarsForPlayerAttack:
...
.specialAttack
-	ld hl, wEnemyMonSpecial
+	ld hl, wEnemyMonSpclDef
	ld a, [hli]
	ld b, a
-	ld c, [hl] ; bc = enemy special
+	ld c, [hl] ; bc = enemy special defense
	ld a, [wEnemyBattleStatus3]
	bit HAS_LIGHT_SCREEN_UP, a ; check for Light Screen
	jr z, .specialAttackCritCheck
-; if the enemy has used Light Screen, double the enemy's special
+; if the enemy has used Light Screen, double the enemy's special defense
	sla c
	rl b
; reflect and light screen boosts do not cap the stat at MAX_STAT_VALUE, so weird things will happen during stats scaling
-; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special has used Light Screen
+; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special Defense has used Light Screen
.specialAttackCritCheck
-	ld hl, wBattleMonSpecial
+	ld hl, wBattleMonSpclAtk
	ld a, [wCriticalHitOrOHKO]
	and a ; check for critical hit
	jr z, .scaleStats
-; in the case of a critical hit, reset the player's and enemy's specials to their base values
-	ld c, STAT_SPECIAL
+; in the case of a critical hit, reset the player's special attack and enemy's special defense to their base values
+	ld c, STAT_SPCLDEF
	call GetEnemyMonStat
	ldh a, [hProduct + 2]
	ld b, a
	ldh a, [hProduct + 3]
	ld c, a
	push bc
-	ld hl, wPartyMon1Special
+	ld hl, wPartyMon1SpclAtk
...
GetDamageVarsForEnemyAttack:
...
.specialAttack
-	ld hl, wBattleMonSpecial
+	ld hl, wBattleMonSpclDef
	ld a, [hli]
	ld b, a
	ld c, [hl]
	ld a, [wPlayerBattleStatus3]
	bit HAS_LIGHT_SCREEN_UP, a ; check for Light Screen
	jr z, .specialAttackCritCheck
-; if the player has used Light Screen, double the player's special
+; if the player has used Light Screen, double the player's special defense
	sla c
	rl b
; reflect and light screen boosts do not cap the stat at MAX_STAT_VALUE, so weird things will happen during stats scaling
-; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special has used Light Screen
+; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special Defense has used Light Screen
.specialAttackCritCheck
-	ld hl, wEnemyMonSpecial
+	ld hl, wEnemyMonSpclAtk
	ld a, [wCriticalHitOrOHKO]
	and a ; check for critical hit
	jr z, .scaleStats
-; in the case of a critical hit, reset the player's and enemy's specials to their base values
-	ld hl, wPartyMon1Special
+; in the case of a critical hit, reset the player's special defense and enemy's special attack to their base values
+	ld hl, wPartyMon1SpclDef
	ld a, [wPlayerMonNumber]
	ld bc, wPartyMon2 - wPartyMon1
	call AddNTimes
	ld a, [hli]
	ld b, a
	ld c, [hl]
	push bc
-	ld c, STAT_SPECIAL
+	ld c, STAT_SPCLATK
...

Adjusting for Memory Changes

A few lines contain hard-coded values (and comments) that assume RAM addresses never change. Failure to update these values accordingly will mangle the stats calculated for enemy Pokémon, and for gift Pokémon sent to boxes instead of the party.

engine/battle/core.asm

; get stat c of enemy mon
; c: stat to get (STAT_* constant)
GetEnemyMonStat:
...
.notLinkBattle
...
-	ld hl, wLoadedMonSpeedExp - $b ; this base address makes CalcStat look in [wLoadedMonSpeedExp] for DVs
+	ld hl, wLoadedMonSpeedExp - $d ; this base address makes CalcStat look in [wLoadedMonSpeedExp] for DVs
	call CalcStat
...
LoadEnemyMonData:
...
.storeDVs
	ld hl, wEnemyMonDVs
	ld [hli], a
	ld [hl], b
	ld de, wEnemyMonLevel
	ld a, [wCurEnemyLevel]
	ld [de], a
	inc de
	ld b, $0
-	ld hl, wEnemyMonHP
-	push hl
+	ld hl, wEnemyMonHP - 2 ; CalcStats will now add 13 bytes to this value instead of 11 to find DVs, so we adjust by subtracting 2 ...
	call CalcStats
-	pop hl
+	ld hl, wEnemyMonHP     ; ... and now we restore the expected value
	ld a, [wIsInBattle]
...
-; calculate modified stat for stat c (0 = attack, 1 = defense, 2 = speed, 3 = special)
+; calculate modified stat for stat c (0 = attack, 1 = defense, 2 = speed, 3 = spcl.atk, 4 = spcl.def)
CalculateModifiedStat:
...

engine/pokemon/add_mon.asm

.writeEVsLoop              ; set all EVs to 0
...
	ld hl, wEnemyMonMaxHP
-	ld bc, $a
+	ld bc, NUM_STATS * 2
	call CopyData          ; copy stats of cur enemy mon
...
.findMonDataDest
	ld a, [wMoveMonType]
	dec a
	ld hl, wPartyMons
-	ld bc, wPartyMon2 - wPartyMon1 ; $2c
+	ld bc, wPartyMon2 - wPartyMon1 ; $31
	ld a, [wPartyCount]
	jr nz, .addMonOffset
	; if it's PARTY_TO_BOX
	ld hl, wBoxMons
-	ld bc, wBoxMon2 - wBoxMon1 ; $21
+	ld bc, wBoxMon2 - wBoxMon1 ; $23
	ld a, [wBoxCount]
.addMonOffset
	dec a
	call AddNTimes
.findMonDataSrc
	push hl
	ld e, l
	ld d, h
	ld a, [wMoveMonType]
	and a
	ld hl, wBoxMons
-	ld bc, wBoxMon2 - wBoxMon1 ; $21
+	ld bc, wBoxMon2 - wBoxMon1 ; $23
	jr z, .addMonOffset2
	cp DAYCARE_TO_PARTY
	ld hl, wDayCareMon
	jr z, .copyMonData
	ld hl, wPartyMons
-	ld bc, wPartyMon2 - wPartyMon1 ; $2c
+	ld bc, wPartyMon2 - wPartyMon1 ; $31
...
-	ld bc, -18
+	ld bc, -20 ; Moving from end of box_struct, this positions hl 2 bytes before HPExp
	add hl, bc
	ld b, $1
	call CalcStats
.done
	and a
	ret

Adding Special Defense Badge Boost

This section is optional. Each stat except HP is associated with one of the badges. Acquiring that badge will apply a 12.5% increase (a "badge boost") in its related stat to every Pokémon the player uses, except during Link Battles. The game will function just fine without adding a badge boost for special defense, but it seems odd to leave one stat out.

engine/battle/core.asm

The easiest way to add a special defense badge boost is to append a check for the badge of your choice to the end of the loop in ApplyBadgeStatBoosts. This tutorial assumes the Volcano Badge will continue to boost both special attack and defense as it did before splitting the SPECIAL stat.

...
ApplyBadgeStatBoosts:
	ld a, [wLinkState]
	cp LINK_STATE_BATTLING
	ret z ; return if link battle
	ld a, [wObtainedBadges]
	ld b, a
	ld hl, wBattleMonAttack
	ld c, $4
; the boost is applied for badges whose bit position is even
; the order of boosts matches the order they are laid out in RAM
; Boulder (bit 0) - attack
; Thunder (bit 2) - defense
; Soul (bit 4) - speed
-; Volcano (bit 6) - special
+; Volcano (bit 6) - special attack
.loop
	srl b
	call c, .applyBoostToStat
	inc hl
	inc hl
	srl b
	dec c
	jr nz, .loop
-	ret
+; Special case added for special defense
+	ld a, [wObtainedBadges]
+	bit BIT_VOLCANOBADGE, a ; checks same badge as special attack, but can be any of them
+	ret z                   ; return immediately if that badge is not obtained
+; fall-through intended

; multiply stat at hl by 1.125
; cap stat at MAX_STAT_VALUE
.applyBoostToStat
...

Alternatively, the loop could be replaced with a check for each bit corresponding to a badge boost. That is beyond the scope of this tutorial.

Modifying Status Screens

There are two cases in which a player can view a Pokémon's special defense: in battle upon level-up, and in a status screen. Both cases use the same function. For the first case, the text box can be increased in height and the text adjusted to fit. For the second case, the text box can no longer fit on screen. This tutorial removes the border and prints the text only, resulting in just enough room.

engine/pokemon/status_screen.asm

...
PrintStatsBox:
	ld a, d
	and a ; a is 0 from the status screen
	jr nz, .DifferentBox
-	hlcoord 0, 8
-	ld b, 8
-	ld c, 8
-	call TextBoxBorder ; Draws the box
-	hlcoord 1, 8 ; Start printing stats from here
+; Don't draw a border; status screen needs every line it can get here
+	hlcoord 1, 8 ; Start printing stats from here
	ld bc, $19 ; Number offset
	jr .PrintStats
.DifferentBox
-	hlcoord 9, 2
-	ld b, 8
+	hlcoord 9, 0
+	ld b, 10
	ld c, 9
	call TextBoxBorder
-	hlcoord 11, 3
+	hlcoord 11, 1
	ld bc, $18
.PrintStats
...
-	ld de, wLoadedMonSpecial
+	ld de, wLoadedMonSpclAtk
+	call PrintStat
+	ld de, wLoadedMonSpclDef
	jp PrintNumber
...
StatsText:
	db   "ATTACK"
	next "DEFENSE"
	next "SPEED"
-	next "SPECIAL@"
+	next "SPCL.ATK"
+	next "SPCL.DEF@"
...

Adding and Updating Move Effects

Certain moves interacted with the SPECIAL stat, so they must now be updated. Doing so will require adding new move effects for stat modifications to special defense, adjusting some hard-coded values, and updating lots of comments.

constants/move_effect_constants.asm

New constants for special defense should follow those for special attack in order to make implementing those effects easier. As a result, the numbering in the comments will no longer match reality unless they are updated as well. Although tedious, this is important to prevent confusion in the future. The only thing worse than uncommented code is incorrectly commented code. Not all these changes are detailed in the diff below, to keep things short.

...
-	const SPECIAL_UP1_EFFECT         ; $0D
+	const SPCLATK_UP1_EFFECT         ; $0D
+	const SPCLDEF_UP1_EFFECT         ; $0E
...
-	const SPECIAL_DOWN1_EFFECT       ; $15
+	const SPCLATK_DOWN1_EFFECT       ; $16
+	const SPCLDEF_DOWN1_EFFECT       ; $17
...
-	const EFFECT_1E                  ; $1E unused
+	const EFFECT_20                  ; $20 unused
...
-	const SPECIAL_UP2_EFFECT         ; $35
+	const SPCLATK_UP2_EFFECT         ; $37
+	const SPCLDEF_UP2_EFFECT         ; $38
...
-	const SPECIAL_DOWN2_EFFECT       ; $3D
+	const SPCLATK_DOWN2_EFFECT       ; $40
+	const SPCLDEF_DOWN2_EFFECT       ; $41
...
-	const SPECIAL_DOWN_SIDE_EFFECT   ; $47
+	const SPCLATK_DOWN_SIDE_EFFECT   ; $4B
+	const SPCLDEF_DOWN_SIDE_EFFECT   ; $4C
...
-	const DISABLE_EFFECT             ; $56
+	const DISABLE_EFFECT             ; $5B
DEF NUM_MOVE_EFFECTS EQU const_value - 1

data/battle/always_happen_effects.asm

Updating the name of EFFECT_1E to EFFECT_20 is technically optional, but is recommended. It must also be changed here:

AlwaysHappenSideEffects:
; Attacks that aren't finished after they faint the opponent.
	db DRAIN_HP_EFFECT
	db EXPLODE_EFFECT
	db DREAM_EATER_EFFECT
	db PAY_DAY_EFFECT
	db TWO_TO_FIVE_ATTACKS_EFFECT
-	db EFFECT_1E
+	db EFFECT_20
...

data/battle/special_effects.asm

...and here as well:

SpecialEffects:
; Effects from arrays 2, 4, and 5B, minus Twineedle and Rage.
; Includes all effects that do not need to be called at the end of
; ExecutePlayerMove (or ExecuteEnemyMove), because they have already been handled
	db DRAIN_HP_EFFECT
	db EXPLODE_EFFECT
	db DREAM_EATER_EFFECT
	db PAY_DAY_EFFECT
	db SWIFT_EFFECT
	db TWO_TO_FIVE_ATTACKS_EFFECT
-	db EFFECT_1E
+	db EFFECT_20
...

data/battle/residual_effects_2.asm

Back to replacing SPECIAL with SPCLATK and SPCLDEF in move effect names.

ResidualEffects2:
; non-side effects not included in ResidualEffects1
; stat-affecting moves, sleep-inflicting moves, and Bide
; e.g., Meditate, Bide, Hypnosis
	db EFFECT_01
	db ATTACK_UP1_EFFECT
	db DEFENSE_UP1_EFFECT
	db SPEED_UP1_EFFECT
-	db SPECIAL_UP1_EFFECT
+	db SPCLATK_UP1_EFFECT
+	db SPCLDEF_UP1_EFFECT
	db ACCURACY_UP1_EFFECT
	db EVASION_UP1_EFFECT
	db ATTACK_DOWN1_EFFECT
	db DEFENSE_DOWN1_EFFECT
	db SPEED_DOWN1_EFFECT
-	db SPECIAL_DOWN1_EFFECT
+	db SPCLATK_DOWN1_EFFECT
+	db SPCLDEF_DOWN1_EFFECT
	db ACCURACY_DOWN1_EFFECT
	db EVASION_DOWN1_EFFECT
	db BIDE_EFFECT
	db SLEEP_EFFECT
	db ATTACK_UP2_EFFECT
	db DEFENSE_UP2_EFFECT
	db SPEED_UP2_EFFECT
-	db SPECIAL_UP2_EFFECT
+	db SPCLATK_UP2_EFFECT
+	db SPCLDEF_UP2_EFFECT
	db ACCURACY_UP2_EFFECT
	db EVASION_UP2_EFFECT
	db ATTACK_DOWN2_EFFECT
	db DEFENSE_DOWN2_EFFECT
	db SPEED_DOWN2_EFFECT
-	db SPECIAL_DOWN2_EFFECT
+	db SPCLATK_DOWN2_EFFECT
+	db SPCLDEF_DOWN2_EFFECT
	db ACCURACY_DOWN2_EFFECT
	db EVASION_DOWN2_EFFECT
	db -1 ; end

data/moves/effects_pointers.asm

Associate the new move effects with the correct function.

...
-	dw StatModifierUpEffect      ; SPECIAL_UP1_EFFECT
+	dw StatModifierUpEffect      ; SPCLATK_UP1_EFFECT
+	dw StatModifierUpEffect      ; SPCLDEF_UP1_EFFECT
...
-	dw StatModifierDownEffect    ; SPECIAL_DOWN1_EFFECT
+	dw StatModifierDownEffect    ; SPCLATK_DOWN1_EFFECT
+	dw StatModifierDownEffect    ; SPCLDEF_DOWN1_EFFECT
...
-	dw TwoToFiveAttacksEffect    ; EFFECT_1E
+	dw TwoToFiveAttacksEffect    ; EFFECT_20
...
-	dw StatModifierUpEffect      ; SPECIAL_UP2_EFFECT
+	dw StatModifierUpEffect      ; SPCALTK_UP2_EFFECT
+	dw StatModifierUpEffect      ; SPCLDEF_UP2_EFFECT
...
-	dw StatModifierDownEffect    ; SPECIAL_DOWN2_EFFECT
+	dw StatModifierDownEffect    ; SPCLATK_DOWN2_EFFECT
+	dw StatModifierDownEffect    ; SPCLDEF_DOWN2_EFFECT
...
-	dw StatModifierDownEffect    ; SPECIAL_DOWN_SIDE_EFFECT
+	dw StatModifierDownEffect    ; SPCLATK_DOWN_SIDE_EFFECT
+	dw StatModifierDownEffect    ; SPCLDEF_DOWN_SIDE_EFFECT
...

data/moves/moves.asm

Update the moves Growth, Psychic, and Amnesia with the appropriate effects. Growth is a special case: its effect changed multiple times after Gen 1. To replicate its original effect, it should raise both special attack and special defense. To update it to its modern effect (Gen 5 to Gen 9), it should raise both attack and special attack. Increasing two different stats would require adding a new effect, which is outside the scope of this tutorial, so instead the change here raises just special attack by one stage (matching its effect from Gen 2 to Gen 4).

...
-	move GROWTH,       SPECIAL_UP1_EFFECT,           0, NORMAL,       100, 40
+	move GROWTH,       SPCLATK_UP1_EFFECT,           0, NORMAL,       100, 40
...
-	move PSYCHIC_M,    SPECIAL_DOWN_SIDE_EFFECT,    90, PSYCHIC_TYPE, 100, 10
+	move PSYCHIC_M,    SPCLDEF_DOWN_SIDE_EFFECT,    90, PSYCHIC_TYPE, 100, 10
...
-	move AMNESIA,      SPECIAL_UP2_EFFECT,           0, PSYCHIC_TYPE, 100, 20
+	move AMNESIA,      SPCLDEF_UP2_EFFECT,           0, PSYCHIC_TYPE, 100, 20
...

engine/battle/core.asm

Another place to update the name of EFFECT_1E if it was changed.

; Multi-hit attacks may or may not have 0 bp.
	cp TWO_TO_FIVE_ATTACKS_EFFECT
	jr z, .skipbp
-	cp EFFECT_1E
+	cp EFFECT_20
	jr z, .skipbp

engine/battle/effects.asm

There are two places where hard-coded values will prevent the SPCLDEF effects from working correctly, since they assume only four base stats can be modified.

StatModifierUpEffect:
...
.incrementStatMod
...
.ok
	ld [hl], b
	ld a, c
-	cp $4
+	cp NUM_STATS
	jr nc, UpdateStatDone ; jump if mod affected is evasion/accuracy
...
StatModifierDownEffect:
...
.statModifierDownEffect
...
-	sub ATTACK_DOWN_SIDE_EFFECT ; map each stat to 0-3
+	sub ATTACK_DOWN_SIDE_EFFECT ; map each stat to 0-4
...
.ok
	ld [hl], b ; save modified mod
	ld a, c
-	cp $4
+	cp NUM_STATS
	jr nc, UpdateLoweredStatDone ; jump for evasion/accuracy

engine/battle/move_effects/transform.asm

Finally, the move effect for Transform also needs an update so that the user's special defense can be saved for later, and the enemy's special defense can be copied.

...
.next
; DVs
	ld a, [hli]
	ld [de], a
	inc de
	ld a, [hli]
	ld [de], a
	inc de
-; Attack, Defense, Speed, and Special stats
+; Attack, Defense, Speed, Spcl.Atk, and Spcl.Def stats
	inc hl
	inc hl
	inc hl
	inc de
	inc de
	inc de
-	ld bc, $8
+	ld bc, $a
	call CopyData
...
.gotStatsOrModsToCopy
-	ld bc, $8
+	ld bc, $a
	jp CopyData
...

engine/battle/trainer_ai.asm

AIUseXSpecial is unused in vanilla (unmodified) pokered, but SPECIAL_UP1_EFFECT must be replaced regardless.

...
AIUseXSpecial:
-	ld b, SPECIAL_UP1_EFFECT
+	ld b, SPCLATK_UP1_EFFECT
	ld a, X_SPECIAL
	; fallthrough

AIIncreaseStat:
...

Alternatively, if you wish to update its name and add an X Sp. Def option (more on that later), make the following changes:

...
-AIUseXSpecial:
-	ld b, SPECIAL_UP1_EFFECT
-	ld a, X_SPECIAL
+AIUseXSpAtk:
+	ld b, SPCLATK_UP1_EFFECT
+	ld a, X_SP_ATK
+	jr AIIncreaseStat
+
+AIUseXSpDef:
+	ld b, SPCLDEF_UP1_EFFECT
+	ld a, X_SP_DEF
	; fallthrough

AIIncreaseStat:
...

Adding a New Vitamin

This section is optional. Despite special defense being added in Gen 2, a vitamin to increase that stat (Zinc) was not added until Gen 3.

constants/item_constants.asm

Insert ZINC below CALCIUM. This allows that code that applies vitamins to Stat EXP to work without modification. Again, adjusting the values in the comments is a hassle, but will likely be important for reference later. Less re-numbering is needed if you remove unused ITEM_2C.

...
	const HP_UP         ; $23
	const PROTEIN       ; $24
	const IRON          ; $25
	const CARBOS        ; $26
	const CALCIUM       ; $27
-	const RARE_CANDY    ; $28
-	const DOME_FOSSIL   ; $29
-	const HELIX_FOSSIL  ; $2A
-	const SECRET_KEY    ; $2B
-	const ITEM_2C       ; $2C ; unused
+	const ZINC          ; $28
+	const RARE_CANDY    ; $29
+	const DOME_FOSSIL   ; $2A
+	const HELIX_FOSSIL  ; $2B
+	const SECRET_KEY    ; $2C
	const BIKE_VOUCHER  ; $2D
...

data/items/key_items.asm

...
	dbit FALSE ; HP_UP
	dbit FALSE ; PROTEIN
	dbit FALSE ; IRON
	dbit FALSE ; CARBOS
	dbit FALSE ; CALCIUM
+   dbit FALSE ; ZINC
	dbit FALSE ; RARE_CANDY
	dbit TRUE  ; DOME_FOSSIL
	dbit TRUE  ; HELIX_FOSSIL
	dbit TRUE  ; SECRET_KEY
-	dbit TRUE  ; ITEM_2C
	dbit TRUE  ; BIKE_VOUCHER
...

data/items/marts.asm

Add ZINC to the Celadon Vitamin mart, the only place where the other vitamins are sold. Adding ZINC to item balls and hidden items elsewhere is recommended, but beyond the scope of this tutorial.

...
CeladonMart5FClerk2Text::
-	script_mart HP_UP, PROTEIN, IRON, CARBOS, CALCIUM
+	script_mart HP_UP, PROTEIN, IRON, CARBOS, CALCIUM, ZINC
...

data/items/names.asm

...
	li "HP UP"
	li "PROTEIN"
	li "IRON"
	li "CARBOS"
	li "CALCIUM"
+	li "ZINC"
	li "RARE CANDY"
	li "DOME FOSSIL"
	li "HELIX FOSSIL"
	li "SECRET KEY"
-	li "?????" ; ITEM_2C
...

data/items/prices.asm

...
	bcd3 9800  ; HP_UP
	bcd3 9800  ; PROTEIN
	bcd3 9800  ; IRON
	bcd3 9800  ; CARBOS
	bcd3 9800  ; CALCIUM
+	bcd3 9800  ; ZINC
	bcd3 4800  ; RARE_CANDY
	bcd3 0     ; DOME_FOSSIL
	bcd3 0     ; HELIX_FOSSIL
	bcd3 0     ; SECRET_KEY
-	bcd3 0     ; ITEM_2C
	bcd3 0     ; BIKE_VOUCHER
...

data/items/use_party.asm

...
	db HP_UP
	db PROTEIN
	db IRON
	db CARBOS
	db CALCIUM
+	db ZINC
	db RARE_CANDY
...

engine/items/item_effects.asm

...
	dw ItemUseVitamin    ; HP_UP
	dw ItemUseVitamin    ; PROTEIN
	dw ItemUseVitamin    ; IRON
	dw ItemUseVitamin    ; CARBOS
	dw ItemUseVitamin    ; CALCIUM
+	dw ItemUseVitamin    ; ZINC
	dw ItemUseVitamin    ; RARE_CANDY
	dw UnusableItem      ; DOME_FOSSIL
	dw UnusableItem      ; HELIX_FOSSIL
	dw UnusableItem      ; SECRET_KEY
-	dw UnusableItem      ; ITEM_2C
	dw UnusableItem      ; BIKE_VOUCHER
...

Renaming X Special and Adding X Sp. Def

This section is optional. Despite special defense being added in Gen 2, a battle item to boost that stat (X Sp. Def) was not added until Gen 4. The item that boosts special attack kept the name X Special all the way until Gen 6.

constants/item_constants.asm

Insert X_SP_DEF below X_SPECIAL, which can be renamed X_SP_ATK if desired. Adjust the values in the comments for later reference.

	const X_ATTACK      ; $41
	const X_DEFEND      ; $42
	const X_SPEED       ; $43
-	const X_SPECIAL     ; $44
-	const COIN_CASE     ; $45
-	const OAKS_PARCEL   ; $46
-	const ITEMFINDER    ; $47
-	const SILPH_SCOPE   ; $48
-	const POKE_FLUTE    ; $49
-	const LIFT_KEY      ; $4A
-	const EXP_ALL       ; $4B
-	const OLD_ROD       ; $4C
-	const GOOD_ROD      ; $4D
-	const SUPER_ROD     ; $4E
-	const PP_UP         ; $4F
-	const ETHER         ; $50
-	const MAX_ETHER     ; $51
-	const ELIXER        ; $52
-	const MAX_ELIXER    ; $53
+	const X_SP_ATK      ; $44
+	const X_SP_DEF      ; $45
+	const COIN_CASE     ; $46
+	const OAKS_PARCEL   ; $47
+	const ITEMFINDER    ; $48
+	const SILPH_SCOPE   ; $49
+	const POKE_FLUTE    ; $4A
+	const LIFT_KEY      ; $4B
+	const EXP_ALL       ; $4C
+	const OLD_ROD       ; $4D
+	const GOOD_ROD      ; $4E
+	const SUPER_ROD     ; $4F
+	const PP_UP         ; $50
+	const ETHER         ; $51
+	const MAX_ETHER     ; $52
+	const ELIXER        ; $53
+	const MAX_ELIXER    ; $54
DEF NUM_ITEMS EQU const_value - 1

; elevator floors use item IDs (see scripts/CeladonMartElevator.asm and scripts/SilphCoElevator.asm)
-	const FLOOR_B2F     ; $54
-	const FLOOR_B1F     ; $55
-	const FLOOR_1F      ; $56
-	const FLOOR_2F      ; $57
-	const FLOOR_3F      ; $58
-	const FLOOR_4F      ; $59
-	const FLOOR_5F      ; $5A
-	const FLOOR_6F      ; $5B
-	const FLOOR_7F      ; $5C
-	const FLOOR_8F      ; $5D
-	const FLOOR_9F      ; $5E
-	const FLOOR_10F     ; $5F
-	const FLOOR_11F     ; $60
-	const FLOOR_B4F     ; $61
+	const FLOOR_B2F     ; $55
+	const FLOOR_B1F     ; $56
+	const FLOOR_1F      ; $57
+	const FLOOR_2F      ; $58
+	const FLOOR_3F      ; $59
+	const FLOOR_4F      ; $5A
+	const FLOOR_5F      ; $5B
+	const FLOOR_6F      ; $5C
+	const FLOOR_7F      ; $5D
+	const FLOOR_8F      ; $5E
+	const FLOOR_9F      ; $5F
+	const FLOOR_10F     ; $60
+	const FLOOR_11F     ; $61
+	const FLOOR_B4F     ; $62
DEF NUM_FLOORS EQU const_value - 1 - NUM_ITEMS
...

constants/move_constants.asm

The only change here is to a comment:

...
-	const XSTATITEM_ANIM ; use X Attack/Defense/Speed/Special
+	const XSTATITEM_ANIM ; use X Attack/Defense/Speed/Sp Atk/Sp Def
...

data/events/hidden_objects.asm

If renaming X_SPECIAL, also update this reference in the hidden item data.

...
UndergroundPathNsHiddenObjects:
	hidden_object  3,  4, FULL_RESTORE, HiddenItems
-	hidden_object  4, 34, X_SPECIAL, HiddenItems
+	hidden_object  4, 34, X_SP_ATK, HiddenItems
	db -1 ; end
...

data/items/key_items.asm

...
	dbit FALSE ; X_ATTACK
	dbit FALSE ; X_DEFEND
	dbit FALSE ; X_SPEED
-	dbit FALSE ; X_SPECIAL
+	dbit FALSE ; X_SP_ATK
+	dbit FALSE ; X_SP_DEF
	dbit TRUE  ; COIN_CASE
...

data/items/marts.asm

Add X_SP_DEF to this Celadon mart, the only place where the other battle items are sold. Adding X_SP_DEF to item balls and hidden items elsewhere is recommended, but beyond the scope of this tutorial. Also rename X_SPECIAL.

...
CeladonMart5FClerk1Text::
-	script_mart X_ACCURACY, GUARD_SPEC, DIRE_HIT, X_ATTACK, X_DEFEND, X_SPEED, X_SPECIAL
+	script_mart X_ACCURACY, GUARD_SPEC, DIRE_HIT, X_ATTACK, X_DEFEND, X_SPEED, X_SP_ATK, X_SP_DEF
...

data/items/names.asm

Using the Gen 6 names here. Alternatively, these can match the status screen names: X SPCL.ATK and X SPCL.DEF.

...
	li "X ATTACK"
	li "X DEFEND"
	li "X SPEED"
-	li "X SPECIAL"
+	li "X SP. ATK"
+	li "X SP. DEF"
	li "COIN CASE"
...

data/items/prices.asm

...
	bcd3 500   ; X_ATTACK
	bcd3 550   ; X_DEFEND
	bcd3 350   ; X_SPEED
-	bcd3 350   ; X_SPECIAL
+	bcd3 350   ; X_SP_ATK
+	bcd3 350   ; X_SP_DEF
	bcd3 0     ; COIN_CASE
...

data/items/use_party.asm

...
	db X_ATTACK
	db X_DEFEND
	db X_SPEED
-	db X_SPECIAL
+	db X_SP_ATK
+	db X_SP_DEF
	db PP_UP
...

engine/items/item_effects.asm

...
	dw ItemUseXStat      ; X_ATTACK
	dw ItemUseXStat      ; X_DEFEND
	dw ItemUseXStat      ; X_SPEED
-	dw ItemUseXStat      ; X_SPECIAL
+	dw ItemUseXStat      ; X_SP_ATK
+	dw ItemUseXStat      ; X_SP_DEF
	dw ItemUseCoinCase   ; COIN_CASE
...

NOTE: Don't forget to make the necessary changes to engine/battle/trainer_ai.asm already detailed above.

Updating NPC Text

Several NPCs and a book mention the SPECIAL stat or an associated item. They should be updated to reflect the changes made in previous sections.

text/CeladonMart5F.asm

If you added the ZINC item, make the following change:

_CeladonMart5FGentlemanText::
	text "#MON ability"
	line "enhancers can be"
	cont "bought only here."

	para "Use CALCIUM to"
-	line "increase SPECIAL"
-	cont "abilities."
+	line "increase SPCL.ATK"
+	cont "ability."
+
+	para "Use ZINC to"
+	line "increase SPCL.DEF"
+	cont "ability."

	para "Use CARBOS to"
	line "increase SPEED."
	done
...

Otherwise,

_CeladonMart5FGentlemanText::
	text "#MON ability"
	line "enhancers can be"
	cont "bought only here."

	para "Use CALCIUM to"
-	line "increase SPECIAL"
-	cont "abilities."
+	line "increase SPCL.ATK"
+	cont "ability."

	para "Use CARBOS to"
	line "increase SPEED."
	done
...

text/CeruleanBadgeHouse.asm

If you added the special defense badge boost and associated it with the Volcano Badge, either leave this dialogue unchanged or make the following change:

...
_CeruleanBadgeHouseVolcanoBadgeText::
	text "Your #MON's"
-	line "SPECIAL abilities"
+	line "SPCL.ATK and"
+	cont "SPCL.DEF"
	cont "increase a bit."
	prompt
...

text/CinnabarGym.asm

If you added the special defense badge boost and associated it with the Volcano Badge, either leave this dialogue unchanged or make the following change:

...
_CinnabarGymBlaineVolcanoBadgeInfoText::
	text "Hah!"

	para "The VOLCANOBADGE"
	line "heightens the"
-	cont "SPECIAL abilities"
+	cont "SPCL.ATK and"
+	cont "SPCL.DEF"
	cont "of your #MON!"
...

Otherwise,

...
_CinnabarGymBlaineVolcanoBadgeInfoText::
	text "Hah!"

	para "The VOLCANOBADGE"
	line "heightens the"
-	cont "SPECIAL abilities"
+	cont "SPCL.ATK ability"
	cont "of your #MON!"
...

text/LavenderMart.asm

Modify this text if renaming X_SPECIAL and/or adding X_SP_DEF:

_LavenderMartBaldingGuyText::
	text "I'm searching for"
	line "items that raise"
	cont "the abilities of"
	cont "#MON during a"
	cont "single battle."

	para "X ATTACK, X"
	line "DEFEND, X SPEED"
-	cont "and X SPECIAL are"
+	cont "X SP. ATK and X"
+	cont "SP. DEF are"
	cont "what I'm after."
...

text/MrPsychicsHouse.asm

...
_MrPsychicsHouseMrPsychicTM29ExplanationText::
	text "TM29 is PSYCHIC!"

	para "It can lower the"
-	line "target's SPECIAL"
-	cont "abilities."
+	line "target's SPCL.DEF"
+	cont "ability."
	done
...