From a50a9b25cb125c51c2a3058cdb0f44e5b55d4ed0 Mon Sep 17 00:00:00 2001 From: Jeff Padgham Date: Sat, 10 Oct 2026 22:28:21 -0700 Subject: [PATCH 1/8] battle: match func_800B0170 --- src/battle/battle.c | 18 ++++++++++++++++-- src/battle/battle.h | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/battle/battle.c b/src/battle/battle.c index ec5559ad..43478d0a 100644 --- a/src/battle/battle.c +++ b/src/battle/battle.c @@ -3745,14 +3745,28 @@ void func_800AF9C8(void) { } } -extern s32 D_800F499C; +extern volatile s32 D_800F499C; extern s32 D_800F49F8[][10]; static s32 func_800AFE98(s32 arg0) { return D_800F49F8[D_800F499C][arg0] >> 0xC; } INCLUDE_ASM("asm/us/battle/nonmatchings/battle", func_800AFECC); -INCLUDE_ASM("asm/us/battle/nonmatchings/battle", func_800B0170); +extern /*?*/ s32 D_800F49A0[2]; +extern /*?*/ s32 D_800F49A8[][10]; + +void func_800B0170(void) { + s32* unitSpeed = D_800F49A8[D_800F499C]; + s32* speedMultiplier = D_800F49F8[D_800F499C]; + s32 i; + + D_800F49A0[D_800F499C] += g_BattleSceneContext.battleSpeed; + + for (i = 0; i < NUM_BATTLE_ACTOR; i++) { + unitSpeed[i] += g_BattleWork.turn[i].unitSpeed; + speedMultiplier[i] += g_BattleWork.turn[i].atbSpeedMultiplier; + } +} INCLUDE_ASM("asm/us/battle/nonmatchings/battle", func_800B0234); diff --git a/src/battle/battle.h b/src/battle/battle.h index 3d706bad..15675828 100644 --- a/src/battle/battle.h +++ b/src/battle/battle.h @@ -555,7 +555,7 @@ typedef struct { } BattleSpriteDesc; // size:0x10 typedef struct { - /* 0x00 */ s16 unitSpeed; + /* 0x00 */ u16 unitSpeed; /* 0x02 */ s16 atbIncrement; /* 0x04 */ u16 atbGauge; // ATB fill gauge, saturates/compares at 0xFFFF -- unsigned /* 0x06 */ s16 unk6; From b4dd3f2f1a08005694912a87bccfd036869008c9 Mon Sep 17 00:00:00 2001 From: Jeff Padgham Date: Sun, 11 Oct 2026 10:40:36 -0700 Subject: [PATCH 2/8] battle: match func_800AFECC --- src/battle/battle.c | 105 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/src/battle/battle.c b/src/battle/battle.c index 43478d0a..2b5c448f 100644 --- a/src/battle/battle.c +++ b/src/battle/battle.c @@ -3746,25 +3746,116 @@ void func_800AF9C8(void) { } extern volatile s32 D_800F499C; +extern s32 D_800F49A0[2]; +extern s32 D_800F49A8[][10]; extern s32 D_800F49F8[][10]; +extern s32 D_800F4A48[10]; +extern s32 D_800F4A70[10]; +extern s32 D_800F4A98[10]; static s32 func_800AFE98(s32 arg0) { return D_800F49F8[D_800F499C][arg0] >> 0xC; } -INCLUDE_ASM("asm/us/battle/nonmatchings/battle", func_800AFECC); +void func_800AFECC(void) { + s32* unitSpeeds; + s32* speedMultipliers; + s32 bufferIndex = D_800F499C; + s32 ticks; + s32 i; + s32 k; + + speedMultipliers = D_800F49F8[bufferIndex]; + unitSpeeds = D_800F49A8[bufferIndex]; + + D_800F499C ^= 1; + ticks = D_800F49A0[bufferIndex] >> 13; + D_800F49A0[bufferIndex] &= 0x1FFF; + + for (i = 0; i < NUM_BATTLE_ACTOR; i++) { + s32 unk6 = g_BattleWork.turn[i].unk6; + s32 unitSpeed = unitSpeeds[i]; + s32 speedMulti = speedMultipliers[i]; + s32 unitTicks; + s32 atbTicks; + s32 hpChange; + s32 deltaHP; + s32 sum1; + s32 sum2; + + sum1 = D_800F4A48[i] + unitSpeed; + D_800F4A48[i] = sum1 & 0x1FFF; + + sum2 = D_800F4A70[i] + speedMulti; + D_800F4A70[i] = sum2 & 0xFFF; + + hpChange = D_800F4A98[i] + unitSpeed * unk6; + D_800F4A98[i] = hpChange & 0x7FFF; + + unitSpeeds[i] = 0; + speedMultipliers[i] = 0; + + unitTicks = sum1 >> 13; + atbTicks = sum2 >> 12; + deltaHP = hpChange >> 15; + + if (!(g_BattleState.combatant[i].status & STATUS_DEATH)) { + s32 maxHP = g_BattleState.combatant[i].maxHP; + s32 newHP = g_BattleState.combatant[i].curHP + deltaHP; + if (newHP < 0) { + g_BattleState.combatant[i].status |= STATUS_DEATH; + func_800A6278(i, i, 1); + BattleExecFormationAIScripts(); + newHP = 0; + } else if (maxHP < newHP) { + newHP = maxHP; + } + g_BattleState.combatant[i].curHP = newHP; + } + + for (k = 0; k < NUM_STATUS_TIMERS; k++) { + s32 timer = g_BattleWork.turn[i].statusTimers[k]; + s32 delta; + + if (timer) { + switch (k) { + case TIMER_STOP: + delta = ticks; + break; + + case TIMER_D_SENTENCE: + case TIMER_SLOW_NUMB: + delta = atbTicks; + break; + + default: + delta = unitTicks; + break; + } + + timer -= delta; + + if (timer < 0) { + timer = 0; + } + if (timer == 0) { + BattleQueueEvent(2, i, k, k); + } -extern /*?*/ s32 D_800F49A0[2]; -extern /*?*/ s32 D_800F49A8[][10]; + g_BattleWork.turn[i].statusTimers[k] = timer; + } + } + } +} void func_800B0170(void) { - s32* unitSpeed = D_800F49A8[D_800F499C]; - s32* speedMultiplier = D_800F49F8[D_800F499C]; + s32* unitSpeeds = D_800F49A8[D_800F499C]; + s32* speedMultipliers = D_800F49F8[D_800F499C]; s32 i; D_800F49A0[D_800F499C] += g_BattleSceneContext.battleSpeed; for (i = 0; i < NUM_BATTLE_ACTOR; i++) { - unitSpeed[i] += g_BattleWork.turn[i].unitSpeed; - speedMultiplier[i] += g_BattleWork.turn[i].atbSpeedMultiplier; + unitSpeeds[i] += g_BattleWork.turn[i].unitSpeed; + speedMultipliers[i] += g_BattleWork.turn[i].atbSpeedMultiplier; } } From 4e8a1dd9d8b7442c778f10e11c45ccb10b737bfa Mon Sep 17 00:00:00 2001 From: Jeff Padgham Date: Sun, 11 Oct 2026 12:21:58 -0700 Subject: [PATCH 3/8] battle: match BattleRecalcUnitSpeed --- src/battle/battle.c | 50 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/battle/battle.c b/src/battle/battle.c index 2b5c448f..82ef141d 100644 --- a/src/battle/battle.c +++ b/src/battle/battle.c @@ -3291,8 +3291,54 @@ void BattleCalcAffinityFlags(void) { } } -void BattleRecalcUnitSpeed(int index); -INCLUDE_ASM("asm/us/battle/nonmatchings/battle", BattleRecalcUnitSpeed); +void BattleRecalcUnitSpeed(s32 index) { + // Status alterations in this function are local only; the unit status is never written back + s32 status = g_BattleState.combatant[index].status; + s32 unitSpeed = 0; + s16 atbSpeedMultiplier = 0; + s32 atbIncrement; + + if (status & STATUS_IMPRISONED) { + switch (g_BattleSceneContext.imprisonedType) { + case 0: + case 3: + status |= STATUS_STOP; + break; + case 1: + status |= STATUS_PARALYSIS; + break; + } + } + + if (!(status & (STATUS_STOP | STATUS_DEATH))) { + unitSpeed = g_BattleSceneContext.battleSpeed; + atbSpeedMultiplier = 0x22; // Slow (0.5x) + + if (!(status & STATUS_SLOW)) { + unitSpeed *= 2; + atbSpeedMultiplier = 0x44; // Normal (1.0x) + if (status & STATUS_HASTE) { + unitSpeed *= 2; + atbSpeedMultiplier = 0x88; // Haste (2.0x) + } + } + } + + if (index < NUM_PARTY) { + s32 totalDex = BattleApplyStatMult(index, g_BattleState.combatant[index].dexterity, STAT_MULT_DEXTERITY) + 50; + atbIncrement = unitSpeed * totalDex / g_BattleSceneContext.avgPartyDexterity; + } else { + atbIncrement = (unitSpeed * g_BattleState.combatant[index].dexterity) / g_BattleSceneContext.avgPartyDexterity; + } + + if (status & (STATUS_PARALYSIS | STATUS_PETRIFY | STATUS_SLEEP)) { + atbIncrement = 0; + } + + g_BattleWork.turn[index].unitSpeed = unitSpeed; + g_BattleWork.turn[index].atbIncrement = atbIncrement; + g_BattleWork.turn[index].atbSpeedMultiplier = atbSpeedMultiplier; +} const u8 g_StatusTimerInitValues[] = { 0x1E, 0x14, 0x3C, 0x1E, 0x7F, 0x7F, 0x0A, 0x64, 0x7F, 0x7F, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00}; From eb924803daa4de045d0464cf65e02be31dd85414 Mon Sep 17 00:00:00 2001 From: Jeff Padgham Date: Sun, 11 Oct 2026 14:14:22 -0700 Subject: [PATCH 4/8] battle: match func_800B0234 --- src/battle/battle.c | 39 ++++++++++++++++++++++++++++++++++++++- src/battle/battle.h | 5 +++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/battle/battle.c b/src/battle/battle.c index 82ef141d..eb945d39 100644 --- a/src/battle/battle.c +++ b/src/battle/battle.c @@ -3905,7 +3905,44 @@ void func_800B0170(void) { } } -INCLUDE_ASM("asm/us/battle/nonmatchings/battle", func_800B0234); +void func_800B0234(void) { + s32* unitSpeeds = D_800F49A8[D_800F499C]; + s32 i; + + for (i = 0; i < NUM_PARTY; i++) { + s32 unk6 = g_BattleWork.turn[i].unk6; + s32 unitSpeed = unitSpeeds[i]; + s32 prevMP = g_BattleWork.turn[i].prevMP; + s32 newHP = g_BattleWork.turn[i].prevHP; + s32 barrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_BARRIER]; + s32 mbarrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_M_BARRIER]; + + newHP += (unk6 * unitSpeed) >> 15; + + if (newHP < 0) { + newHP = 0; + } else if (g_BattleState.combatant[i].maxHP < newHP) { + newHP = g_BattleState.combatant[i].maxHP; + } + + barrierTimer -= unitSpeed >> 13; + mbarrierTimer -= unitSpeed >> 13; + if (barrierTimer < 0) { + barrierTimer = 0; + } + if (mbarrierTimer < 0) { + mbarrierTimer = 0; + } + + if (!(g_BattleState.setupFlags & SETUP_UNK2)) { + g_ActiveCharacters[i].hp = newHP; + g_ActiveCharacters[i].mp = prevMP; + } + + g_ActiveCharacters[i].counterActionIndex = barrierTimer * 2; + g_ActiveCharacters[i].counterChance = mbarrierTimer * 2; + } +} void BattleRollPhysicalHit(void) { BattleUnit* attacker = &g_BattleState.combatant[g_CurrentAction->actorId]; diff --git a/src/battle/battle.h b/src/battle/battle.h index 15675828..e432fe50 100644 --- a/src/battle/battle.h +++ b/src/battle/battle.h @@ -8,8 +8,9 @@ // https://github.com/petfriendamy/ff7-scarlet/blob/main/src/SceneEditor/BattleFlags.cs#L4 typedef enum { - SETUP_CANNOT_ESCAPE = 4, - SETUP_NO_VICTORY_POSE = 8, + SETUP_UNK2 = 0x2, + SETUP_CANNOT_ESCAPE = 0x4, + SETUP_NO_VICTORY_POSE = 0x8, SETUP_NO_PREEMPTIVE_STRIKE = 0x10, } BattleSetupFlags; From 41d488f9de99a3e457dd95651f320b66c06910a3 Mon Sep 17 00:00:00 2001 From: Jeff Padgham Date: Sun, 11 Oct 2026 15:47:00 -0700 Subject: [PATCH 5/8] battle: cleanup & renaming global battle timer symbols --- config/symbols.battle.txt | 7 +++ src/battle/battle.c | 118 +++++++++++++++++++------------------- src/battle/battle.h | 4 +- 3 files changed, 69 insertions(+), 60 deletions(-) diff --git a/config/symbols.battle.txt b/config/symbols.battle.txt index fa6854cf..b44da46e 100644 --- a/config/symbols.battle.txt +++ b/config/symbols.battle.txt @@ -499,6 +499,13 @@ g_BattleCallbackEvent = 0x800F4308; g_BattlePartyEventReadIdx = 0x800F4908; g_BattlePartyEventWriteIdx = 0x800F4914; g_BattleHitFormulaOffs = 0x800F495C; +g_BattleTimerBufferIndex = 0x800F499C; +g_BattleGlobalTimerAccum = 0x800F49A0; +g_BattleVarTimerAccum = 0x800F49A8; +g_BattleConstTimerAccum = 0x800F49F8; +g_BattleVarTimerFrac = 0x800F4A48; +g_BattleConstTimerFrac = 0x800F4A70; +g_BattleHpChangeFrac = 0x800F4A98; g_BattleScriptActionPriority = 0x800F4AC8; g_BattleModelClutRect = 0x800F4B24; // size:0x8 g_BattleWork = 0x800F5BB8; // size:0x38C diff --git a/src/battle/battle.c b/src/battle/battle.c index eb945d39..6d48ecdf 100644 --- a/src/battle/battle.c +++ b/src/battle/battle.c @@ -3294,8 +3294,8 @@ void BattleCalcAffinityFlags(void) { void BattleRecalcUnitSpeed(s32 index) { // Status alterations in this function are local only; the unit status is never written back s32 status = g_BattleState.combatant[index].status; - s32 unitSpeed = 0; - s16 atbSpeedMultiplier = 0; + s32 varSpeed = 0; + s16 constSpeed = 0; s32 atbIncrement; if (status & STATUS_IMPRISONED) { @@ -3311,33 +3311,33 @@ void BattleRecalcUnitSpeed(s32 index) { } if (!(status & (STATUS_STOP | STATUS_DEATH))) { - unitSpeed = g_BattleSceneContext.battleSpeed; - atbSpeedMultiplier = 0x22; // Slow (0.5x) + varSpeed = g_BattleSceneContext.battleSpeed; + constSpeed = 0x22; // Slow (0.5x) if (!(status & STATUS_SLOW)) { - unitSpeed *= 2; - atbSpeedMultiplier = 0x44; // Normal (1.0x) + varSpeed *= 2; + constSpeed = 0x44; // Normal (1.0x) if (status & STATUS_HASTE) { - unitSpeed *= 2; - atbSpeedMultiplier = 0x88; // Haste (2.0x) + varSpeed *= 2; + constSpeed = 0x88; // Haste (2.0x) } } } if (index < NUM_PARTY) { s32 totalDex = BattleApplyStatMult(index, g_BattleState.combatant[index].dexterity, STAT_MULT_DEXTERITY) + 50; - atbIncrement = unitSpeed * totalDex / g_BattleSceneContext.avgPartyDexterity; + atbIncrement = varSpeed * totalDex / g_BattleSceneContext.avgPartyDexterity; } else { - atbIncrement = (unitSpeed * g_BattleState.combatant[index].dexterity) / g_BattleSceneContext.avgPartyDexterity; + atbIncrement = (varSpeed * g_BattleState.combatant[index].dexterity) / g_BattleSceneContext.avgPartyDexterity; } if (status & (STATUS_PARALYSIS | STATUS_PETRIFY | STATUS_SLEEP)) { atbIncrement = 0; } - g_BattleWork.turn[index].unitSpeed = unitSpeed; + g_BattleWork.turn[index].varSpeed = varSpeed; g_BattleWork.turn[index].atbIncrement = atbIncrement; - g_BattleWork.turn[index].atbSpeedMultiplier = atbSpeedMultiplier; + g_BattleWork.turn[index].constSpeed = constSpeed; } const u8 g_StatusTimerInitValues[] = { @@ -3791,56 +3791,57 @@ void func_800AF9C8(void) { } } -extern volatile s32 D_800F499C; -extern s32 D_800F49A0[2]; -extern s32 D_800F49A8[][10]; -extern s32 D_800F49F8[][10]; -extern s32 D_800F4A48[10]; -extern s32 D_800F4A70[10]; -extern s32 D_800F4A98[10]; +extern volatile s32 g_BattleTimerBufferIndex; +extern s32 g_BattleGlobalTimerAccum[2]; +extern s32 g_BattleVarTimerAccum[2][NUM_BATTLE_ACTOR]; +extern s32 g_BattleConstTimerAccum[2][NUM_BATTLE_ACTOR]; +extern s32 g_BattleVarTimerFrac[NUM_BATTLE_ACTOR]; +extern s32 g_BattleConstTimerFrac[NUM_BATTLE_ACTOR]; +extern s32 g_BattleHpChangeFrac[NUM_BATTLE_ACTOR]; -static s32 func_800AFE98(s32 arg0) { return D_800F49F8[D_800F499C][arg0] >> 0xC; } +static s32 func_800AFE98(s32 arg0) { return g_BattleConstTimerAccum[g_BattleTimerBufferIndex][arg0] >> 12; } +// Called during BATTLE_Main (main consumer thread) void func_800AFECC(void) { - s32* unitSpeeds; - s32* speedMultipliers; - s32 bufferIndex = D_800F499C; - s32 ticks; + s32* varTimerAccum; + s32* constTimerAccum; + s32 bufferIndex = g_BattleTimerBufferIndex; + s32 globalUnits; s32 i; s32 k; - speedMultipliers = D_800F49F8[bufferIndex]; - unitSpeeds = D_800F49A8[bufferIndex]; + constTimerAccum = g_BattleConstTimerAccum[bufferIndex]; + varTimerAccum = g_BattleVarTimerAccum[bufferIndex]; - D_800F499C ^= 1; - ticks = D_800F49A0[bufferIndex] >> 13; - D_800F49A0[bufferIndex] &= 0x1FFF; + g_BattleTimerBufferIndex ^= 1; + globalUnits = g_BattleGlobalTimerAccum[bufferIndex] >> 13; + g_BattleGlobalTimerAccum[bufferIndex] &= 0x1FFF; for (i = 0; i < NUM_BATTLE_ACTOR; i++) { s32 unk6 = g_BattleWork.turn[i].unk6; - s32 unitSpeed = unitSpeeds[i]; - s32 speedMulti = speedMultipliers[i]; - s32 unitTicks; - s32 atbTicks; + s32 varTimerInc = varTimerAccum[i]; + s32 constTimerInc = constTimerAccum[i]; + s32 varUnits; + s32 constUnits; s32 hpChange; s32 deltaHP; - s32 sum1; - s32 sum2; + s32 varTimer; + s32 constTimer; - sum1 = D_800F4A48[i] + unitSpeed; - D_800F4A48[i] = sum1 & 0x1FFF; + varTimer = g_BattleVarTimerFrac[i] + varTimerInc; + g_BattleVarTimerFrac[i] = varTimer & 0x1FFF; - sum2 = D_800F4A70[i] + speedMulti; - D_800F4A70[i] = sum2 & 0xFFF; + constTimer = g_BattleConstTimerFrac[i] + constTimerInc; + g_BattleConstTimerFrac[i] = constTimer & 0xFFF; - hpChange = D_800F4A98[i] + unitSpeed * unk6; - D_800F4A98[i] = hpChange & 0x7FFF; + hpChange = g_BattleHpChangeFrac[i] + varTimerInc * unk6; + g_BattleHpChangeFrac[i] = hpChange & 0x7FFF; - unitSpeeds[i] = 0; - speedMultipliers[i] = 0; + varTimerAccum[i] = 0; + constTimerAccum[i] = 0; - unitTicks = sum1 >> 13; - atbTicks = sum2 >> 12; + varUnits = varTimer >> 13; + constUnits = constTimer >> 12; deltaHP = hpChange >> 15; if (!(g_BattleState.combatant[i].status & STATUS_DEATH)) { @@ -3864,16 +3865,16 @@ void func_800AFECC(void) { if (timer) { switch (k) { case TIMER_STOP: - delta = ticks; + delta = globalUnits; break; case TIMER_D_SENTENCE: case TIMER_SLOW_NUMB: - delta = atbTicks; + delta = constUnits; break; default: - delta = unitTicks; + delta = varUnits; break; } @@ -3892,32 +3893,33 @@ void func_800AFECC(void) { } } +// Called during VSync (the producer thread) void func_800B0170(void) { - s32* unitSpeeds = D_800F49A8[D_800F499C]; - s32* speedMultipliers = D_800F49F8[D_800F499C]; + s32* varTimerAccum = g_BattleVarTimerAccum[g_BattleTimerBufferIndex]; + s32* constTimerAccum = g_BattleConstTimerAccum[g_BattleTimerBufferIndex]; s32 i; - D_800F49A0[D_800F499C] += g_BattleSceneContext.battleSpeed; + g_BattleGlobalTimerAccum[g_BattleTimerBufferIndex] += g_BattleSceneContext.battleSpeed; for (i = 0; i < NUM_BATTLE_ACTOR; i++) { - unitSpeeds[i] += g_BattleWork.turn[i].unitSpeed; - speedMultipliers[i] += g_BattleWork.turn[i].atbSpeedMultiplier; + varTimerAccum[i] += g_BattleWork.turn[i].varSpeed; + constTimerAccum[i] += g_BattleWork.turn[i].constSpeed; } } void func_800B0234(void) { - s32* unitSpeeds = D_800F49A8[D_800F499C]; + s32* varTimerAccum = g_BattleVarTimerAccum[g_BattleTimerBufferIndex]; s32 i; for (i = 0; i < NUM_PARTY; i++) { s32 unk6 = g_BattleWork.turn[i].unk6; - s32 unitSpeed = unitSpeeds[i]; + s32 varTimerInc = varTimerAccum[i]; s32 prevMP = g_BattleWork.turn[i].prevMP; s32 newHP = g_BattleWork.turn[i].prevHP; s32 barrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_BARRIER]; s32 mbarrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_M_BARRIER]; - newHP += (unk6 * unitSpeed) >> 15; + newHP += (unk6 * varTimerInc) >> 15; if (newHP < 0) { newHP = 0; @@ -3925,8 +3927,8 @@ void func_800B0234(void) { newHP = g_BattleState.combatant[i].maxHP; } - barrierTimer -= unitSpeed >> 13; - mbarrierTimer -= unitSpeed >> 13; + barrierTimer -= varTimerInc >> 13; + mbarrierTimer -= varTimerInc >> 13; if (barrierTimer < 0) { barrierTimer = 0; } diff --git a/src/battle/battle.h b/src/battle/battle.h index e432fe50..df848638 100644 --- a/src/battle/battle.h +++ b/src/battle/battle.h @@ -556,12 +556,12 @@ typedef struct { } BattleSpriteDesc; // size:0x10 typedef struct { - /* 0x00 */ u16 unitSpeed; + /* 0x00 */ u16 varSpeed; /* 0x02 */ s16 atbIncrement; /* 0x04 */ u16 atbGauge; // ATB fill gauge, saturates/compares at 0xFFFF -- unsigned /* 0x06 */ s16 unk6; /* 0x08 */ s16 limitSpeedFlag; - /* 0x0A */ u16 atbSpeedMultiplier; + /* 0x0A */ u16 constSpeed; /* 0x0C */ u8 senseTargetMask; /* 0x0D */ u8 accessoryEffectId; /* 0x0E */ u8 hasLimitBreak; From eb22114319f302320a6354ffa02cc7e6e6420aec Mon Sep 17 00:00:00 2001 From: Jeff Padgham Date: Sun, 11 Oct 2026 16:32:01 -0700 Subject: [PATCH 6/8] battle: adding macros to make battle timer/fractions more readable --- src/battle/battle.c | 52 ++++++++++++++++--------------------- src/battle/battle_private.h | 19 ++++++++++++++ 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/battle/battle.c b/src/battle/battle.c index 6d48ecdf..ee629b06 100644 --- a/src/battle/battle.c +++ b/src/battle/battle.c @@ -3312,14 +3312,14 @@ void BattleRecalcUnitSpeed(s32 index) { if (!(status & (STATUS_STOP | STATUS_DEATH))) { varSpeed = g_BattleSceneContext.battleSpeed; - constSpeed = 0x22; // Slow (0.5x) + constSpeed = (1 << BATTLE_CONST_TIMER_FRAC_BITS) / 120; // Slow (0.5x) if (!(status & STATUS_SLOW)) { varSpeed *= 2; - constSpeed = 0x44; // Normal (1.0x) + constSpeed = (1 << BATTLE_CONST_TIMER_FRAC_BITS) / 60; // Normal (1.0x) if (status & STATUS_HASTE) { varSpeed *= 2; - constSpeed = 0x88; // Haste (2.0x) + constSpeed = (1 << BATTLE_CONST_TIMER_FRAC_BITS) / 30; // Haste (2.0x) } } } @@ -3791,22 +3791,16 @@ void func_800AF9C8(void) { } } -extern volatile s32 g_BattleTimerBufferIndex; -extern s32 g_BattleGlobalTimerAccum[2]; -extern s32 g_BattleVarTimerAccum[2][NUM_BATTLE_ACTOR]; -extern s32 g_BattleConstTimerAccum[2][NUM_BATTLE_ACTOR]; -extern s32 g_BattleVarTimerFrac[NUM_BATTLE_ACTOR]; -extern s32 g_BattleConstTimerFrac[NUM_BATTLE_ACTOR]; -extern s32 g_BattleHpChangeFrac[NUM_BATTLE_ACTOR]; - -static s32 func_800AFE98(s32 arg0) { return g_BattleConstTimerAccum[g_BattleTimerBufferIndex][arg0] >> 12; } +static s32 func_800AFE98(s32 arg0) { + return BATTLE_TICKS(g_BattleConstTimerAccum[g_BattleTimerBufferIndex][arg0], BATTLE_CONST_TIMER_FRAC_BITS); +} // Called during BATTLE_Main (main consumer thread) void func_800AFECC(void) { s32* varTimerAccum; s32* constTimerAccum; s32 bufferIndex = g_BattleTimerBufferIndex; - s32 globalUnits; + s32 globalTicks; s32 i; s32 k; @@ -3814,35 +3808,35 @@ void func_800AFECC(void) { varTimerAccum = g_BattleVarTimerAccum[bufferIndex]; g_BattleTimerBufferIndex ^= 1; - globalUnits = g_BattleGlobalTimerAccum[bufferIndex] >> 13; - g_BattleGlobalTimerAccum[bufferIndex] &= 0x1FFF; + globalTicks = BATTLE_TICKS(g_BattleGlobalTimerAccum[bufferIndex], BATTLE_TIMER_FRAC_BITS); + g_BattleGlobalTimerAccum[bufferIndex] = BATTLE_FRAC(g_BattleGlobalTimerAccum[bufferIndex], BATTLE_TIMER_FRAC_BITS); for (i = 0; i < NUM_BATTLE_ACTOR; i++) { s32 unk6 = g_BattleWork.turn[i].unk6; s32 varTimerInc = varTimerAccum[i]; s32 constTimerInc = constTimerAccum[i]; - s32 varUnits; - s32 constUnits; + s32 varTicks; + s32 constTicks; s32 hpChange; s32 deltaHP; s32 varTimer; s32 constTimer; varTimer = g_BattleVarTimerFrac[i] + varTimerInc; - g_BattleVarTimerFrac[i] = varTimer & 0x1FFF; + g_BattleVarTimerFrac[i] = BATTLE_FRAC(varTimer, BATTLE_TIMER_FRAC_BITS); constTimer = g_BattleConstTimerFrac[i] + constTimerInc; - g_BattleConstTimerFrac[i] = constTimer & 0xFFF; + g_BattleConstTimerFrac[i] = BATTLE_FRAC(constTimer, BATTLE_CONST_TIMER_FRAC_BITS); hpChange = g_BattleHpChangeFrac[i] + varTimerInc * unk6; - g_BattleHpChangeFrac[i] = hpChange & 0x7FFF; + g_BattleHpChangeFrac[i] = BATTLE_FRAC(hpChange, BATTLE_HP_CHANGE_FRAC_BITS); varTimerAccum[i] = 0; constTimerAccum[i] = 0; - varUnits = varTimer >> 13; - constUnits = constTimer >> 12; - deltaHP = hpChange >> 15; + varTicks = BATTLE_TICKS(varTimer, BATTLE_TIMER_FRAC_BITS); + constTicks = BATTLE_TICKS(constTimer, BATTLE_CONST_TIMER_FRAC_BITS); + deltaHP = BATTLE_TICKS(hpChange, BATTLE_HP_CHANGE_FRAC_BITS); if (!(g_BattleState.combatant[i].status & STATUS_DEATH)) { s32 maxHP = g_BattleState.combatant[i].maxHP; @@ -3865,16 +3859,16 @@ void func_800AFECC(void) { if (timer) { switch (k) { case TIMER_STOP: - delta = globalUnits; + delta = globalTicks; break; case TIMER_D_SENTENCE: case TIMER_SLOW_NUMB: - delta = constUnits; + delta = constTicks; break; default: - delta = varUnits; + delta = varTicks; break; } @@ -3919,7 +3913,7 @@ void func_800B0234(void) { s32 barrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_BARRIER]; s32 mbarrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_M_BARRIER]; - newHP += (unk6 * varTimerInc) >> 15; + newHP += BATTLE_TICKS(unk6 * varTimerInc, BATTLE_HP_CHANGE_FRAC_BITS); if (newHP < 0) { newHP = 0; @@ -3927,8 +3921,8 @@ void func_800B0234(void) { newHP = g_BattleState.combatant[i].maxHP; } - barrierTimer -= varTimerInc >> 13; - mbarrierTimer -= varTimerInc >> 13; + barrierTimer -= BATTLE_TICKS(varTimerInc, BATTLE_TIMER_FRAC_BITS); + mbarrierTimer -= BATTLE_TICKS(varTimerInc, BATTLE_TIMER_FRAC_BITS); if (barrierTimer < 0) { barrierTimer = 0; } diff --git a/src/battle/battle_private.h b/src/battle/battle_private.h index 6b589808..0a5cf142 100644 --- a/src/battle/battle_private.h +++ b/src/battle/battle_private.h @@ -6,6 +6,25 @@ #define HIT_OPCODE_DELIM 0x08 #define BATTLE_EVENT_QUEUE_SIZE 128 +// Battle timers +// The timer accumulators are fixed-point values: the low fracBits bits are a fraction +// carried to the next update, the rest is whole ticks. Status timers count down one per +// tick. The HP change uses the same split, giving whole HP. +// See the Battle Mechanics FAQ for more details +#define BATTLE_TIMER_FRAC_BITS 13 // global and var timers: 8192 per tick +#define BATTLE_CONST_TIMER_FRAC_BITS 12 // const timer: 4096 per tick +#define BATTLE_HP_CHANGE_FRAC_BITS 15 +#define BATTLE_TICKS(value, fracBits) ((value) >> (fracBits)) +#define BATTLE_FRAC(value, fracBits) ((value) & ((1 << (fracBits)) - 1)) + +extern volatile s32 g_BattleTimerBufferIndex; +extern s32 g_BattleGlobalTimerAccum[2]; +extern s32 g_BattleVarTimerAccum[2][NUM_BATTLE_ACTOR]; +extern s32 g_BattleConstTimerAccum[2][NUM_BATTLE_ACTOR]; +extern s32 g_BattleVarTimerFrac[NUM_BATTLE_ACTOR]; +extern s32 g_BattleConstTimerFrac[NUM_BATTLE_ACTOR]; +extern s32 g_BattleHpChangeFrac[NUM_BATTLE_ACTOR]; + enum QueueMethod { QUEUE_LOAD_IMAGE, QUEUE_STORE_IMAGE, From 82d8f92c4649fb571ed0cdc6ce9336dea599a5ef Mon Sep 17 00:00:00 2001 From: Jeff Padgham Date: Sun, 11 Oct 2026 17:14:46 -0700 Subject: [PATCH 7/8] battle: doc and cleanup/rename pass --- config/symbols.battle.txt | 7 ++++-- src/battle/batini.c | 4 ++-- src/battle/battle.c | 46 +++++++++++++++++++------------------ src/battle/battle.h | 6 ++--- src/battle/battle_private.h | 15 +++++++++++- 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/config/symbols.battle.txt b/config/symbols.battle.txt index b44da46e..6116657a 100644 --- a/config/symbols.battle.txt +++ b/config/symbols.battle.txt @@ -133,17 +133,20 @@ BattleApplyKillCountBonus = 0x800AE318; BattleCalcMateriaSlotScore = 0x800AE378; BattleGetAffinityMasks = 0x800AE42C; BattleCalcAffinityFlags = 0x800AE82C; -BattleRecalcUnitSpeed = 0x800AE954; +BattleUnitCalcSpeed = 0x800AE954; BattleUnitInitStatusTimer = 0x800AEB20; BattlePostAddDeath = 0x800AEC10; BattlePostRemoveDeath = 0x800AEF68; BattleRestoreBattleActionIfCan = 0x800AF0C4; func_battle_800AF1A8 = 0x800AF1A8; BattleTryApplyHitEffect = 0x800AF1D4; -BattleApplyRegenPoisonTick = 0x800AF3AC; +BattleUnitSetHpChangeRate = 0x800AF3AC; BattleClearActorSlotReferences = 0x800AF594; BattleStatusBitToTimerIndex = 0x800AF834; BattleGetStatusProtectionMask = 0x800AF874; +BattleUnitGetPendingConstTicks = 0x800AFE98; +BattleProcessTimers = 0x800AFECC; +BattleAdvanceTimers = 0x800B0170; BattleRollPhysicalHit = 0x800B0378; BattleRollMagicalHit = 0x800B062C; BattleRollCriticalHit = 0x800B079C; diff --git a/src/battle/batini.c b/src/battle/batini.c index 00cb2282..ba5cdcbb 100644 --- a/src/battle/batini.c +++ b/src/battle/batini.c @@ -80,7 +80,7 @@ void BatInitMain(s32 sceneID) { pUnit = pCombatantBase; g_BattleSceneContext.atbWaitMode = (Savemap.config & 0xC0) >> 6; for (i = 0; i < NUM_BATTLE_ACTOR; i++) { - BattleRecalcUnitSpeed(i); + BattleUnitCalcSpeed(i); if ((s8)pUnit[i].actorId != -1) { *(u16*)((u8*)pCombatantBase - 0x32) |= 1 << i; } @@ -183,7 +183,7 @@ static void BattleInitSetup(s32 sceneID) { BattleInitEnemyUnits(); g_BattleState.presentMask = 0; for (i = 0; i < NUM_BATTLE_ACTOR; i++) { - BattleRecalcUnitSpeed(i); + BattleUnitCalcSpeed(i); unit = &g_BattleState.combatant[i]; if (unit->actorId != -1) { g_BattleState.presentMask |= 1 << i; diff --git a/src/battle/battle.c b/src/battle/battle.c index ee629b06..10b3c8e3 100644 --- a/src/battle/battle.c +++ b/src/battle/battle.c @@ -1293,7 +1293,7 @@ void BattleExecFormationAIScripts(void) { } // Seems to be a KO handler when a unit is killed on the battlefield -// arg0 is the killer, arg1 is the victim, arg2: 1 from func_800AFECC, 0 from BattleCmdScriptDispatch +// arg0 is the killer, arg1 is the victim, arg2: 1 from BattleProcessTimers, 0 from BattleCmdScriptDispatch void func_800A6278(s32 arg0, s32 arg1, s32 arg2) { s32 var_s3; u8 nextAssignedOrder; @@ -1997,7 +1997,7 @@ void BattleApplyVincentLimitTransform(void) { g_BattleState.combatant[unitId].maxHP = g_BattleWork.party[unitId].capHP; } g_BattleState.combatant[unitId].curHP = g_BattleState.combatant[unitId].maxHP; - BattleRecalcUnitSpeed(unitId); + BattleUnitCalcSpeed(unitId); BattleQueueEvent(2, unitId, 0x18, 1); } @@ -3291,7 +3291,7 @@ void BattleCalcAffinityFlags(void) { } } -void BattleRecalcUnitSpeed(s32 index) { +void BattleUnitCalcSpeed(s32 index) { // Status alterations in this function are local only; the unit status is never written back s32 status = g_BattleState.combatant[index].status; s32 varSpeed = 0; @@ -3312,14 +3312,14 @@ void BattleRecalcUnitSpeed(s32 index) { if (!(status & (STATUS_STOP | STATUS_DEATH))) { varSpeed = g_BattleSceneContext.battleSpeed; - constSpeed = (1 << BATTLE_CONST_TIMER_FRAC_BITS) / 120; // Slow (0.5x) + constSpeed = BATTLE_CONST_SPEED_NORMAL / 2; // Slow if (!(status & STATUS_SLOW)) { varSpeed *= 2; - constSpeed = (1 << BATTLE_CONST_TIMER_FRAC_BITS) / 60; // Normal (1.0x) + constSpeed = BATTLE_CONST_SPEED_NORMAL; // Normal if (status & STATUS_HASTE) { varSpeed *= 2; - constSpeed = (1 << BATTLE_CONST_TIMER_FRAC_BITS) / 30; // Haste (2.0x) + constSpeed = BATTLE_CONST_SPEED_NORMAL * 2; // Haste } } } @@ -3388,7 +3388,7 @@ void func_800AEB80(s32 arg0, s32 statusBit, s32 arg2) { } } -void func_800AEBF0(s32 index, s32 arg1, s32 arg2) { BattleRecalcUnitSpeed(index); } +void func_800AEBF0(s32 index, s32 arg1, s32 arg2) { BattleUnitCalcSpeed(index); } #ifndef PLATFORM_PSYZ // Original call in BattlePostAddDeath had no prototype in scope, but signature is correct according to other callers @@ -3422,7 +3422,7 @@ void BattlePostAddDeath(s32 arg0, s32 arg1, s32 arg2) { g_BattleState.combatant[arg0].curHP = 0; func_800AEBF0(arg0, arg1, arg2); - g_BattleWork.turn[arg0].unk6 = 0; + g_BattleWork.turn[arg0].hpChangeRate = 0; g_BattleSceneContext.subActionSlots[arg0].priority = 0xFF; BattleReqReturnReservedItems(arg0); @@ -3534,7 +3534,7 @@ void func_800AF320(s32 arg0, s32 arg1, s32 arg2) { void func_800AF380(s32 arg0) { BattleQueueEvent(2, arg0, 0x15, 0xF); } -void BattleApplyRegenPoisonTick(s32 arg0, s32 arg1, s32 arg2) { +void BattleUnitSetHpChangeRate(s32 arg0, s32 arg1, s32 arg2) { s32 amount; s32 status; s32 step; @@ -3553,7 +3553,7 @@ void BattleApplyRegenPoisonTick(s32 arg0, s32 arg1, s32 arg2) { if (status & STATUS_DUAL_DRAIN) { amount -= step; } - g_BattleWork.turn[arg0].unk6 = amount; + g_BattleWork.turn[arg0].hpChangeRate = amount; if (arg2 != 0) { BattleUnitInitStatusTimer(arg0, arg1, arg2); } else { @@ -3566,7 +3566,7 @@ void func_800AF470(s32 arg0) { g_BattleWork.turn[arg0].unk28 = 3; } void func_800AF494(s32 arg0, s32 arg1, s32 arg2) { switch (g_BattleSceneContext.imprisonedType) { case 1: - BattleApplyRegenPoisonTick(arg0, arg1, arg2); + BattleUnitSetHpChangeRate(arg0, arg1, arg2); /* fallthrough */ case 0: case 3: @@ -3791,12 +3791,12 @@ void func_800AF9C8(void) { } } -static s32 func_800AFE98(s32 arg0) { +static s32 BattleUnitGetPendingConstTicks(s32 arg0) { return BATTLE_TICKS(g_BattleConstTimerAccum[g_BattleTimerBufferIndex][arg0], BATTLE_CONST_TIMER_FRAC_BITS); } -// Called during BATTLE_Main (main consumer thread) -void func_800AFECC(void) { +// Called from BATTLE_Main; consumes the set filled by BattleAdvanceTimers +void BattleProcessTimers(void) { s32* varTimerAccum; s32* constTimerAccum; s32 bufferIndex = g_BattleTimerBufferIndex; @@ -3812,7 +3812,7 @@ void func_800AFECC(void) { g_BattleGlobalTimerAccum[bufferIndex] = BATTLE_FRAC(g_BattleGlobalTimerAccum[bufferIndex], BATTLE_TIMER_FRAC_BITS); for (i = 0; i < NUM_BATTLE_ACTOR; i++) { - s32 unk6 = g_BattleWork.turn[i].unk6; + s32 hpChangeRate = g_BattleWork.turn[i].hpChangeRate; s32 varTimerInc = varTimerAccum[i]; s32 constTimerInc = constTimerAccum[i]; s32 varTicks; @@ -3828,7 +3828,7 @@ void func_800AFECC(void) { constTimer = g_BattleConstTimerFrac[i] + constTimerInc; g_BattleConstTimerFrac[i] = BATTLE_FRAC(constTimer, BATTLE_CONST_TIMER_FRAC_BITS); - hpChange = g_BattleHpChangeFrac[i] + varTimerInc * unk6; + hpChange = g_BattleHpChangeFrac[i] + varTimerInc * hpChangeRate; g_BattleHpChangeFrac[i] = BATTLE_FRAC(hpChange, BATTLE_HP_CHANGE_FRAC_BITS); varTimerAccum[i] = 0; @@ -3887,8 +3887,8 @@ void func_800AFECC(void) { } } -// Called during VSync (the producer thread) -void func_800B0170(void) { +// Called on every VSync from the interrupt callback +void BattleAdvanceTimers(void) { s32* varTimerAccum = g_BattleVarTimerAccum[g_BattleTimerBufferIndex]; s32* constTimerAccum = g_BattleConstTimerAccum[g_BattleTimerBufferIndex]; s32 i; @@ -3901,19 +3901,21 @@ void func_800B0170(void) { } } +// Copies party HP/MP and Barrier/M-Barrier timers, adjusted +// by the pending var ticks, into g_ActiveCharacters void func_800B0234(void) { s32* varTimerAccum = g_BattleVarTimerAccum[g_BattleTimerBufferIndex]; s32 i; for (i = 0; i < NUM_PARTY; i++) { - s32 unk6 = g_BattleWork.turn[i].unk6; + s32 hpChangeRate = g_BattleWork.turn[i].hpChangeRate; s32 varTimerInc = varTimerAccum[i]; s32 prevMP = g_BattleWork.turn[i].prevMP; s32 newHP = g_BattleWork.turn[i].prevHP; s32 barrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_BARRIER]; s32 mbarrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_M_BARRIER]; - newHP += BATTLE_TICKS(unk6 * varTimerInc, BATTLE_HP_CHANGE_FRAC_BITS); + newHP += BATTLE_TICKS(hpChangeRate * varTimerInc, BATTLE_HP_CHANGE_FRAC_BITS); if (newHP < 0) { newHP = 0; @@ -4778,7 +4780,7 @@ void func_800B2CFC(s32 arg0, s32 arg1) { g_BattleState.combatant[arg0].status = g_BattleState.combatant[arg1].status; g_BattleState.combatant[arg0].prevStatus = g_BattleState.combatant[arg1].prevStatus; - g_BattleWork.turn[arg0].unk6 = g_BattleWork.turn[arg1].unk6; + g_BattleWork.turn[arg0].hpChangeRate = g_BattleWork.turn[arg1].hpChangeRate; g_BattleWork.turn[arg0].unk28 = g_BattleWork.turn[arg1].unk28; g_BattleWork.turn[arg0].turnFlags = g_BattleWork.turn[arg1].turnFlags; @@ -4790,7 +4792,7 @@ void func_800B2CFC(s32 arg0, s32 arg1) { g_BattleWork.turn[arg0].statMults[i] = g_BattleWork.turn[arg1].statMults[i]; } - BattleRecalcUnitSpeed(arg0); + BattleUnitCalcSpeed(arg0); BattleInitUnitAction(arg0); } diff --git a/src/battle/battle.h b/src/battle/battle.h index df848638..1e2a9c91 100644 --- a/src/battle/battle.h +++ b/src/battle/battle.h @@ -558,8 +558,8 @@ typedef struct { typedef struct { /* 0x00 */ u16 varSpeed; /* 0x02 */ s16 atbIncrement; - /* 0x04 */ u16 atbGauge; // ATB fill gauge, saturates/compares at 0xFFFF -- unsigned - /* 0x06 */ s16 unk6; + /* 0x04 */ u16 atbGauge; // ATB fill gauge, saturates/compares at 0xFFFF -- unsigned + /* 0x06 */ s16 hpChangeRate; // HP per 4 var ticks: +maxHP/32 Regen, -maxHP/32 Dual Drain /* 0x08 */ s16 limitSpeedFlag; /* 0x0A */ u16 constSpeed; /* 0x0C */ u8 senseTargetMask; @@ -793,7 +793,7 @@ s32 BattleEntityGetStereoPan(s32 arg0); void func_800D5774(u32 targetIndex); void BattleRunFrame(void); void BattleQueueEvent(s32 arg0, s32 arg1, s32 arg2, s32 arg3); -void BattleRecalcUnitSpeed(s32 index); +void BattleUnitCalcSpeed(s32 index); void BattleUpdateUnitMasks(void); void BattleCheckAllLucky7s(void); void BattleActionQueueReset(void); diff --git a/src/battle/battle_private.h b/src/battle/battle_private.h index 0a5cf142..3a39e54b 100644 --- a/src/battle/battle_private.h +++ b/src/battle/battle_private.h @@ -10,17 +10,30 @@ // The timer accumulators are fixed-point values: the low fracBits bits are a fraction // carried to the next update, the rest is whole ticks. Status timers count down one per // tick. The HP change uses the same split, giving whole HP. -// See the Battle Mechanics FAQ for more details +// See the Battle Mechanics FAQ for more details. #define BATTLE_TIMER_FRAC_BITS 13 // global and var timers: 8192 per tick #define BATTLE_CONST_TIMER_FRAC_BITS 12 // const timer: 4096 per tick +#define BATTLE_CONST_TICK (1 << BATTLE_CONST_TIMER_FRAC_BITS) +#define BATTLE_CONST_SPEED_NORMAL (BATTLE_CONST_TICK / 60) // 1 tick per 60 updates #define BATTLE_HP_CHANGE_FRAC_BITS 15 #define BATTLE_TICKS(value, fracBits) ((value) >> (fracBits)) #define BATTLE_FRAC(value, fracBits) ((value) & ((1 << (fracBits)) - 1)) +// Timer accumulators are double buffered: BattleAdvanceTimers adds into the current set +// from the VSync interrupt, while BattleProcessTimers (BATTLE_Main) flips the index and +// consumes the other set, so an interrupt arriving mid-consume never loses an update. +// volatile: shared with the interrupt handler, so it must be re-read from memory. +// when consumed by the main loop. extern volatile s32 g_BattleTimerBufferIndex; + +// Added to on every VSync by BattleAdvanceTimers. +// Var timers scale with the Battle Speed setting and Haste/Slow, const timers only +// with Haste/Slow, and the global timer only counts down Stop. extern s32 g_BattleGlobalTimerAccum[2]; extern s32 g_BattleVarTimerAccum[2][NUM_BATTLE_ACTOR]; extern s32 g_BattleConstTimerAccum[2][NUM_BATTLE_ACTOR]; + +// Fractions carried over between BattleProcessTimers calls extern s32 g_BattleVarTimerFrac[NUM_BATTLE_ACTOR]; extern s32 g_BattleConstTimerFrac[NUM_BATTLE_ACTOR]; extern s32 g_BattleHpChangeFrac[NUM_BATTLE_ACTOR]; From c7b90ae89fa5a5b80015893977423713ae92748a Mon Sep 17 00:00:00 2001 From: Jeff Padgham Date: Sun, 11 Oct 2026 18:06:25 -0700 Subject: [PATCH 8/8] naming is hard (tweaking macro names to read more naturally) --- src/battle/battle.c | 24 ++++++++++++------------ src/battle/battle_private.h | 16 ++++++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/battle/battle.c b/src/battle/battle.c index 10b3c8e3..7f3a7643 100644 --- a/src/battle/battle.c +++ b/src/battle/battle.c @@ -3792,7 +3792,7 @@ void func_800AF9C8(void) { } static s32 BattleUnitGetPendingConstTicks(s32 arg0) { - return BATTLE_TICKS(g_BattleConstTimerAccum[g_BattleTimerBufferIndex][arg0], BATTLE_CONST_TIMER_FRAC_BITS); + return BATTLE_CONST_TICKS(g_BattleConstTimerAccum[g_BattleTimerBufferIndex][arg0]); } // Called from BATTLE_Main; consumes the set filled by BattleAdvanceTimers @@ -3808,8 +3808,8 @@ void BattleProcessTimers(void) { varTimerAccum = g_BattleVarTimerAccum[bufferIndex]; g_BattleTimerBufferIndex ^= 1; - globalTicks = BATTLE_TICKS(g_BattleGlobalTimerAccum[bufferIndex], BATTLE_TIMER_FRAC_BITS); - g_BattleGlobalTimerAccum[bufferIndex] = BATTLE_FRAC(g_BattleGlobalTimerAccum[bufferIndex], BATTLE_TIMER_FRAC_BITS); + globalTicks = BATTLE_TICKS(g_BattleGlobalTimerAccum[bufferIndex]); + g_BattleGlobalTimerAccum[bufferIndex] = BATTLE_TIMER_FRAC(g_BattleGlobalTimerAccum[bufferIndex]); for (i = 0; i < NUM_BATTLE_ACTOR; i++) { s32 hpChangeRate = g_BattleWork.turn[i].hpChangeRate; @@ -3823,20 +3823,20 @@ void BattleProcessTimers(void) { s32 constTimer; varTimer = g_BattleVarTimerFrac[i] + varTimerInc; - g_BattleVarTimerFrac[i] = BATTLE_FRAC(varTimer, BATTLE_TIMER_FRAC_BITS); + g_BattleVarTimerFrac[i] = BATTLE_TIMER_FRAC(varTimer); constTimer = g_BattleConstTimerFrac[i] + constTimerInc; - g_BattleConstTimerFrac[i] = BATTLE_FRAC(constTimer, BATTLE_CONST_TIMER_FRAC_BITS); + g_BattleConstTimerFrac[i] = BATTLE_CONST_FRAC(constTimer); hpChange = g_BattleHpChangeFrac[i] + varTimerInc * hpChangeRate; - g_BattleHpChangeFrac[i] = BATTLE_FRAC(hpChange, BATTLE_HP_CHANGE_FRAC_BITS); + g_BattleHpChangeFrac[i] = BATTLE_HP_FRAC(hpChange); varTimerAccum[i] = 0; constTimerAccum[i] = 0; - varTicks = BATTLE_TICKS(varTimer, BATTLE_TIMER_FRAC_BITS); - constTicks = BATTLE_TICKS(constTimer, BATTLE_CONST_TIMER_FRAC_BITS); - deltaHP = BATTLE_TICKS(hpChange, BATTLE_HP_CHANGE_FRAC_BITS); + varTicks = BATTLE_TICKS(varTimer); + constTicks = BATTLE_CONST_TICKS(constTimer); + deltaHP = BATTLE_HP_DELTA(hpChange); if (!(g_BattleState.combatant[i].status & STATUS_DEATH)) { s32 maxHP = g_BattleState.combatant[i].maxHP; @@ -3915,7 +3915,7 @@ void func_800B0234(void) { s32 barrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_BARRIER]; s32 mbarrierTimer = g_BattleWork.turn[i].statusTimers[TIMER_M_BARRIER]; - newHP += BATTLE_TICKS(hpChangeRate * varTimerInc, BATTLE_HP_CHANGE_FRAC_BITS); + newHP += BATTLE_HP_DELTA(hpChangeRate * varTimerInc); if (newHP < 0) { newHP = 0; @@ -3923,8 +3923,8 @@ void func_800B0234(void) { newHP = g_BattleState.combatant[i].maxHP; } - barrierTimer -= BATTLE_TICKS(varTimerInc, BATTLE_TIMER_FRAC_BITS); - mbarrierTimer -= BATTLE_TICKS(varTimerInc, BATTLE_TIMER_FRAC_BITS); + barrierTimer -= BATTLE_TICKS(varTimerInc); + mbarrierTimer -= BATTLE_TICKS(varTimerInc); if (barrierTimer < 0) { barrierTimer = 0; } diff --git a/src/battle/battle_private.h b/src/battle/battle_private.h index 3a39e54b..158105c7 100644 --- a/src/battle/battle_private.h +++ b/src/battle/battle_private.h @@ -7,23 +7,27 @@ #define BATTLE_EVENT_QUEUE_SIZE 128 // Battle timers -// The timer accumulators are fixed-point values: the low fracBits bits are a fraction +// The timer accumulators are fixed-point values: the low *_FRAC_BITS bits are a fraction // carried to the next update, the rest is whole ticks. Status timers count down one per // tick. The HP change uses the same split, giving whole HP. // See the Battle Mechanics FAQ for more details. #define BATTLE_TIMER_FRAC_BITS 13 // global and var timers: 8192 per tick #define BATTLE_CONST_TIMER_FRAC_BITS 12 // const timer: 4096 per tick -#define BATTLE_CONST_TICK (1 << BATTLE_CONST_TIMER_FRAC_BITS) -#define BATTLE_CONST_SPEED_NORMAL (BATTLE_CONST_TICK / 60) // 1 tick per 60 updates #define BATTLE_HP_CHANGE_FRAC_BITS 15 -#define BATTLE_TICKS(value, fracBits) ((value) >> (fracBits)) -#define BATTLE_FRAC(value, fracBits) ((value) & ((1 << (fracBits)) - 1)) + +#define BATTLE_CONST_SPEED_NORMAL ((1 << BATTLE_CONST_TIMER_FRAC_BITS) / 60) // 1 tick per 60 updates + +#define BATTLE_TICKS(x) ((x) >> BATTLE_TIMER_FRAC_BITS) +#define BATTLE_CONST_TICKS(x) ((x) >> BATTLE_CONST_TIMER_FRAC_BITS) +#define BATTLE_HP_DELTA(x) ((x) >> BATTLE_HP_CHANGE_FRAC_BITS) +#define BATTLE_TIMER_FRAC(x) ((x) & ((1 << BATTLE_TIMER_FRAC_BITS) - 1)) +#define BATTLE_CONST_FRAC(x) ((x) & ((1 << BATTLE_CONST_TIMER_FRAC_BITS) - 1)) +#define BATTLE_HP_FRAC(x) ((x) & ((1 << BATTLE_HP_CHANGE_FRAC_BITS) - 1)) // Timer accumulators are double buffered: BattleAdvanceTimers adds into the current set // from the VSync interrupt, while BattleProcessTimers (BATTLE_Main) flips the index and // consumes the other set, so an interrupt arriving mid-consume never loses an update. // volatile: shared with the interrupt handler, so it must be re-read from memory. -// when consumed by the main loop. extern volatile s32 g_BattleTimerBufferIndex; // Added to on every VSync by BattleAdvanceTimers.