From 2c0c5ca5230e50caf93a2b468f3c3911cd37e109 Mon Sep 17 00:00:00 2001 From: Eduardo Vieira Date: Mon, 7 Sep 2026 16:12:06 -0300 Subject: [PATCH 1/3] battle: give each renderer its own descriptor type The two renderers share a footprint and nothing else, so the unions had no discriminator in the data. Names the flag bits, and updates barrier, brizad and lv5deth to match. Field readings cross-checked against Akari's q-gears_reverse notes. --- src/battle/battle.h | 56 +++++++++++++----- src/battle/battle1.c | 16 +++--- src/battle/battle2.c | 31 +++++----- src/magic/barrier.c | 80 ++++++++++++++++---------- src/magic/brizad.c | 46 ++++++++------- src/magic/lv5deth.c | 116 +++++++++++++++++++++----------------- src/magic/magic_private.h | 15 +++++ 7 files changed, 217 insertions(+), 143 deletions(-) create mode 100644 src/magic/magic_private.h diff --git a/src/battle/battle.h b/src/battle/battle.h index 42101e8a..b1d35a20 100644 --- a/src/battle/battle.h +++ b/src/battle/battle.h @@ -377,14 +377,43 @@ typedef struct { } Unk800BB75C; // size:0x38 +// Flag word at offset 4, as func_800D29D4 tests it. Each mirror bit negates +// one rotation column, reversing polygon winding, so the cull test XORs their +// parity back out. Readings agree with Akari's q-gears_reverse +// (ffvii/DISC/BATTLE/BATTLE.X_model.cpp, "funcd29d4"). +enum ModelRenderFlags { + MODEL_MIRROR_X = 0x1, // negates R11/R21/R31 + MODEL_MIRROR_Y = 0x2, // negates R12/R22/R32 + MODEL_MIRROR_Z = 0x4, // negates R13/R23/R33 + MODEL_SEMI_TRANS = 0x8, // sets bit 1 of the GPU command byte + MODEL_NO_DEPTH_CUE = 0x10, // skips dpcs/dpct under MODEL_DEPTH_CUE + MODEL_NO_CULL = 0x20, // skips nclip; both faces emitted + MODEL_PRIM_TPAGE = 0x40, // tpage from primitive byte 7, not tpage + MODEL_DEPTH_CUE = 0x80, // offset 0xA feeds GTE IR0; see below + MODEL_PRIM_PACKET_BITS = 0x100, // primitive byte 7 << 18 ORed into colour +}; + +// Model descriptor read by func_800D29D4. Field readings agree with Akari's +// q-gears_reverse (ffvii/DISC/BATTLE/BATTLE.X_model.cpp, "funcd29d4"). typedef struct { - s32* unk0; - s32 unk4; - s16 unk8; - s16 unkA; - s16 unkC; - s16 unkE; -} Unk801B0C98; + /* 0x0 */ s32* model; + /* 0x4 */ s32 flags; // ModelRenderFlags + /* 0x8 */ u16 uvOffset; // added to every UV halfword; all callers pass 0 + /* 0xA */ s16 color; // grey level ORed into the colour word; under + // MODEL_DEPTH_CUE it feeds GTE IR0 instead, so + // 0x1000 blends the model fully into SetFarColor + /* 0xC */ s16 tpage; // packet +0x16; Akari: "blending option" + /* 0xE */ s16 clut; // packet clut halfword +} ModelRenderDesc; // size:0x10 + +// Textured-quad descriptor read by func_800D4D90; ROM instances are packed +// 0xC apart. Akari: BATTLE.X_units_functions.cpp, "funcd4d90". +typedef struct { + /* 0x0 */ s32* frames; // per-frame quad blocks, count in each header + /* 0x4 */ CVECTOR color; // packet colour word; cd is 0x2C or 0x2E (POLY_FT4) + /* 0x8 */ u16 frameIndex; // blocks skipped; bit 15 enables clutBias + /* 0xA */ s16 clutBias; // added to each quad's clut halfword +} SpriteRenderDesc; // size:0xC typedef struct { s16 unk0; @@ -479,19 +508,18 @@ int BattleEffectRegister(void (*func)(void)); // battle2.c void func_800D2980(u_long* addr, s16 imgXY, s16 clutX, s16 clutY); -void* func_800D29D4(Unk801B0C98*, u_long**, int, void*); +void* func_800D29D4(ModelRenderDesc*, u_long**, int, void*); // Build the model matrix for a battle effect: `scale` goes on the matrix // diagonal, `pos` is transformed into view space to become the translation, // and `depthBias` nudges it along that view vector (negative pulls it toward // the camera). Leaves the result installed as the rot/trans matrix. -void func_800D4368(SVECTOR* pos, s32 scale, s32 depthBias); -// Same descriptor layout as func_800D29D4 (offsets 0/4/8/A), different -// renderer; callers that colour the model type offset 4 as a CVECTOR. -void* func_800D4D90(void* desc, u_long** ot, int otLen, void* prim); -void func_800D5444(int, int, int, void (*func)(int)); +MATRIX* func_800D4368(SVECTOR* pos, s32 scale, s32 depthBias); +void* func_800D4D90(SpriteRenderDesc* desc, u_long** ot, int otLen, void* prim); +// Returns a scale derived from the target's model size. +s32 func_800D55A4(s32 target); void BattleCommandSend(s32 cmdId, ...); -int func_800D574C(int); void BattleGetPartPosition(s32 arg0, s32 arg1, void* arg2); +// Runs `func` once per set bit in targetMask, frameStep frames apart. void MagicAnimationRegister(s32 targetMask, s32 arg1, s32 frameStep, void (*func)(int)); s32 BattlePositionToStereoPan(SVECTOR* sv); s32 BattleEntityGetStereoPan(s32 arg0); diff --git a/src/battle/battle1.c b/src/battle/battle1.c index f5126047..bccdb334 100644 --- a/src/battle/battle1.c +++ b/src/battle/battle1.c @@ -723,9 +723,9 @@ static void func_800BAFF8(MATRIX* m, VECTOR* v) { static void func_800BB030(s16 arg0) { s32 i; - Unk801B0C98* unk; + ModelRenderDesc* unk; - unk = (Unk801B0C98*)0x1F800020; + unk = (ModelRenderDesc*)0x1F800020; SetFarColor(D_801518E4[arg0].D_8015190C, D_801518E4[arg0].D_8015190D, D_801518E4[arg0].D_8015190E); SetRotMatrix(&D_801518E4[arg0].m); SetTransMatrix(&D_801518E4[arg0].m); @@ -737,12 +737,12 @@ static void func_800BB030(s16 arg0) { func_800BAF34(&D_800FA6D8[arg0].unk8[i]); if (!D_800FA6D8[arg0].unk4[i]) continue; - unk->unk0 = D_800FA6D8[arg0].unk4[i]; - unk->unk4 = D_800FA6D8[arg0].unk3E[i] | 0x180; - unk->unk8 = 0; - unk->unkA = D_801518E4[arg0].unk14[0]; - unk->unkC = 0x20; - unk->unkE = D_801518E4[arg0].unk14[1]; + unk->model = D_800FA6D8[arg0].unk4[i]; + unk->flags = D_800FA6D8[arg0].unk3E[i] | MODEL_PRIM_PACKET_BITS | MODEL_DEPTH_CUE; + unk->uvOffset = 0; + unk->color = D_801518E4[arg0].unk14[0]; + unk->tpage = 0x20; + unk->clut = D_801518E4[arg0].unk14[1]; if (D_801518E4[arg0].D_80151909 & 4) { continue; } diff --git a/src/battle/battle2.c b/src/battle/battle2.c index a0d09e84..deee3858 100644 --- a/src/battle/battle2.c +++ b/src/battle/battle2.c @@ -279,11 +279,14 @@ static void func_800CE638(void) { } } +// Shell over a covered unit; MBarrier wins when both masks are set. Masks +// named by Akari's q-gears_reverse (ffvii/address_battle.txt): D_800FA69C is +// the magic barrier mask, D_80163608 the physical one. static void func_800CE75C(void) { if ((D_800FA69C >> D_80162978[D_8015169C].D_80162980) & 1) { - func_800D67E8(D_80162978[D_8015169C].D_80162980); + func_800D67E8(D_80162978[D_8015169C].D_80162980); // MBarrier } else if ((D_80163608 >> D_80162978[D_8015169C].D_80162980) & 1) { - func_800D67BC(D_80162978[D_8015169C].D_80162980); + func_800D67BC(D_80162978[D_8015169C].D_80162980); // Barrier } } @@ -1270,29 +1273,30 @@ INCLUDE_ASM("asm/us/battle/nonmatchings/battle2", func_800D61AC); INCLUDE_ASM("asm/us/battle/nonmatchings/battle2", func_800D6260); -extern Unk801B0C98 D_800F14D0; +extern ModelRenderDesc D_800F14D0; // Draw a model 4 times through func_800D29D4 (same request-struct pattern as // barrier.c's D_801B0C98/D_801B0CB0), toggling the 0x1/0x2 flag bits between -// passes. +// passes. Those bits mirror on X and Y, so the four passes are the four +// quadrants of a symmetric model built from one quarter. void func_800D6394(s32* arg0, s16 arg1) { - D_800F14D0.unk0 = arg0; - D_800F14D0.unkA = arg1; + D_800F14D0.model = arg0; + D_800F14D0.color = arg1; SetFarColor(0, 0, 0); PushMatrix(); D_80163C74 = func_800D29D4(&D_800F14D0, g_cDb->unk70, 0xC, D_80163C74); PopMatrix(); PushMatrix(); - D_800F14D0.unk4 |= 1; + D_800F14D0.flags |= MODEL_MIRROR_X; D_80163C74 = func_800D29D4(&D_800F14D0, g_cDb->unk70, 0xC, D_80163C74); PopMatrix(); PushMatrix(); - D_800F14D0.unk4 |= 2; + D_800F14D0.flags |= MODEL_MIRROR_Y; D_80163C74 = func_800D29D4(&D_800F14D0, g_cDb->unk70, 0xC, D_80163C74); PopMatrix(); - D_800F14D0.unk4 &= ~1; + D_800F14D0.flags &= ~MODEL_MIRROR_X; D_80163C74 = func_800D29D4(&D_800F14D0, g_cDb->unk70, 0xC, D_80163C74); - D_800F14D0.unk4 &= ~2; + D_800F14D0.flags &= ~MODEL_MIRROR_Y; } INCLUDE_ASM("asm/us/battle/nonmatchings/battle2", func_800D650C); @@ -1304,20 +1308,19 @@ u8* const D_800A0DC8[] = {D_800F10EC, D_800F11E8, D_800F1304}; INCLUDE_ASM("asm/us/battle/nonmatchings/battle2", func_800D6734); void func_800D6734(s32, s32); -extern s32 D_800F14D4; static void func_800D67BC(s32 arg0) { - D_800F14D4 = 0x88; + D_800F14D0.flags = MODEL_DEPTH_CUE | MODEL_SEMI_TRANS; // Barrier func_800D6734(arg0, 0); } static void func_800D67E8(s32 arg0) { - D_800F14D4 = 0xA8; + D_800F14D0.flags = MODEL_DEPTH_CUE | MODEL_NO_CULL | MODEL_SEMI_TRANS; // MBarrier func_800D6734(arg0, 1); } void func_800D6814(s32 arg0) { - D_800F14D4 = 0x88; + D_800F14D0.flags = MODEL_DEPTH_CUE | MODEL_SEMI_TRANS; func_800D6734(arg0, 2); } diff --git a/src/magic/barrier.c b/src/magic/barrier.c index a09de6a9..9150227f 100644 --- a/src/magic/barrier.c +++ b/src/magic/barrier.c @@ -2,6 +2,14 @@ #include "common.h" #include "../battle/battle.h" +#include "magic_private.h" + +// Fade phase: the depth cue ramps by FADE_PER_FRAME to 0xE00 on the last +// frame, and the scale grows from SCALE_BASE by SCALE_PER_FADE_FRAME. +#define FADE_LAST_FRAME 7 +#define FADE_PER_FRAME 0x200 +#define SCALE_BASE 0xC00 +#define SCALE_PER_FADE_FRAME 0x180 // This is placeholder for now so I can access the SVECTORs correctly typedef struct BarrierData { @@ -11,7 +19,8 @@ typedef struct BarrierData { s16 unk6; SVECTOR Pos; SVECTOR Rot; - u16 FaceIndex; + u16 FaceIndex; // 0..3, straight into the descriptor's mirror bits + // 0x1 and 0x2; one instance per quadrant char pad1[0x6]; } BarrierData; @@ -67,11 +76,11 @@ static s32 bari_a2[] = { // Embedded Model 0x003F3F3F}; // Triangle 1: vertex 2 color, RGB 3F3F3F static int emptyPoly = 0x00000000; static SVECTOR BorderPivotOffset = {0, 0, -500}; -static Unk801B0C98 BorderRenderDesc = {bari_a1, 0, 0, 0, 0x20}; +static ModelRenderDesc BorderRenderDesc = {bari_a1, 0, 0, 0, 0x20}; static SVECTOR ShieldPivotOffset = {0, 0, -500}; -static Unk801B0C98 ShieldRenderDesc = {bari_a2, 0, 0, 0, 0x20}; +static ModelRenderDesc ShieldRenderDesc = {bari_a2, 0, 0, 0, 0x20}; static int BarrierBaseScale; -static char BarrierPrimBuffer[0x20000]; +static char BarrierPrimBuffer[2 * MAGIC_PAGE_SIZE]; static void* BarrierBufferPtr; // barrier.c forward declarations @@ -79,25 +88,34 @@ static void BarrierMainSetup(int arg0, int arg1); void MAGIC_Barrier(int arg0, int arg1) { BarrierMainSetup(arg0, arg1); } +// Four instances per cast, two frames apart, FaceIndex 0, 1, 3, 2 -- Gray +// code, so the shell grows through adjacent quadrants. Each draws the same +// quarter model and the mirror bits assemble the other three. static void BarrierRenderBorder(void) { MATRIX* matrix = (MATRIX*)0x1F800000; VECTOR* scale = (VECTOR*)0x1F800020; BarrierData* barrier = &D_80162978[D_8015169C]; int temp_a0 = (barrier->AnimationFrame + barrier->StartFrame) - 17; - int var_s4; - int var_s3; + int fade; + int faceFlags; + // temp_a0 counts from the fade: below 0 opaque, 0..7 fading, past 7 + // retires the instance. if (temp_a0 < 0) { - scale->vx = scale->vy = scale->vz = (BarrierBaseScale * 0xC00) >> 12; - var_s3 = barrier->FaceIndex; - var_s4 = 0; - } else if (temp_a0 > 7) { + scale->vx = scale->vy = scale->vz = (BarrierBaseScale * SCALE_BASE) >> FIXED_SHIFT; + faceFlags = barrier->FaceIndex; + fade = 0; + } else if (temp_a0 > FADE_LAST_FRAME) { barrier->StartFrame = -1; return; } else { - var_s3 = barrier->FaceIndex | 8; - var_s4 = temp_a0 << 9; - scale->vx = scale->vy = scale->vz = (((temp_a0 * 0x180) + 0xC00) * BarrierBaseScale) >> 12; + // semi-transparency for the fade; the cue ramps 0x200 a frame + // to 0xE00. FaceIndex is 0, 1, 3, 2 across the four instances, + // which are the mirror bits that place each quadrant. + faceFlags = barrier->FaceIndex | MODEL_SEMI_TRANS; + fade = temp_a0 * FADE_PER_FRAME; + scale->vx = scale->vy = scale->vz = + (((temp_a0 * SCALE_PER_FADE_FRAME) + SCALE_BASE) * BarrierBaseScale) >> FIXED_SHIFT; } SetFarColor(0, 0, 0); @@ -113,8 +131,8 @@ static void BarrierRenderBorder(void) { SetRotMatrix(matrix); SetTransMatrix(matrix); - BorderRenderDesc.unk4 = var_s3 | 0x80; - BorderRenderDesc.unkA = var_s4; + BorderRenderDesc.flags = faceFlags | MODEL_DEPTH_CUE; + BorderRenderDesc.color = fade; BarrierBufferPtr = func_800D29D4(&BorderRenderDesc, g_cDb->unk70, 12, BarrierBufferPtr); if (D_80062D98 == 0) { @@ -129,28 +147,28 @@ static void BarrierRenderShield(void) { VECTOR* scale2 = (VECTOR*)0x1F800050; BarrierData* barrier = &D_80162978[D_8015169C]; int temp_a0 = barrier->AnimationFrame + barrier->StartFrame - 17; - int var_s5; - int var_s6; + int faceFlags; + int fade; if (temp_a0 < 0) { if (barrier->AnimationFrame < 6) { - scale1->vx = scale1->vy = scale1->vz = (barrier->AnimationFrame * (BarrierBaseScale << 9)) >> 12; + scale1->vx = scale1->vy = scale1->vz = (barrier->AnimationFrame * (BarrierBaseScale << 9)) >> FIXED_SHIFT; } else { - scale1->vx = scale1->vy = scale1->vz = (BarrierBaseScale * 0xC00) >> 12; + scale1->vx = scale1->vy = scale1->vz = (BarrierBaseScale * SCALE_BASE) >> FIXED_SHIFT; } - scale2->vx = scale2->vy = scale2->vz = (BarrierBaseScale * 0xC00) >> 12; + scale2->vx = scale2->vy = scale2->vz = (BarrierBaseScale * SCALE_BASE) >> FIXED_SHIFT; - var_s5 = barrier->FaceIndex; - var_s6 = 0; - } else if (temp_a0 > 7) { + faceFlags = barrier->FaceIndex; + fade = 0; + } else if (temp_a0 > FADE_LAST_FRAME) { barrier->StartFrame = -1; return; } else { - var_s5 = barrier->FaceIndex | 8; - var_s6 = temp_a0 << 9; + faceFlags = barrier->FaceIndex | MODEL_SEMI_TRANS; + fade = temp_a0 * FADE_PER_FRAME; scale1->vx = scale1->vy = scale1->vz = scale2->vx = scale2->vy = scale2->vz = - (((temp_a0 * 0x180) + 0xC00) * BarrierBaseScale) >> 12; + (((temp_a0 * SCALE_PER_FADE_FRAME) + SCALE_BASE) * BarrierBaseScale) >> FIXED_SHIFT; } SetFarColor(0, 0, 0); @@ -166,8 +184,8 @@ static void BarrierRenderShield(void) { SetRotMatrix(matrix1); SetTransMatrix(matrix1); - ShieldRenderDesc.unk4 = var_s5 | 0x80; - ShieldRenderDesc.unkA = var_s6; + ShieldRenderDesc.flags = faceFlags | MODEL_DEPTH_CUE; + ShieldRenderDesc.color = fade; BarrierBufferPtr = func_800D29D4(&ShieldRenderDesc, g_cDb->unk70, 12, BarrierBufferPtr); if (D_80062D98 == 0) { @@ -259,8 +277,8 @@ static void BarrierAttachToTarget(int target) { BarrierData* barrier = &D_80162978[BattleEffectRegister(BarrierAnimationUpdate)]; BattleGetPartPosition(target, D_801518E4[target].D_8015190F, &barrier->Pos); - barrier->Pos.vx -= (rsin(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12; - barrier->Pos.vz -= (rcos(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12; + barrier->Pos.vx -= (rsin(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> FIXED_SHIFT; + barrier->Pos.vz -= (rcos(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> FIXED_SHIFT; barrier->Rot = D_801518E4[target].unk160; barrier->TargetIndex = target; } @@ -268,7 +286,7 @@ static void BarrierAttachToTarget(int target) { static void BarrierDoubleBufferFlip(void) { BarrierData* barrier = &D_80162978[D_8015169C]; - BarrierBufferPtr = &BarrierPrimBuffer[barrier->AnimationFrame * 65536]; + BarrierBufferPtr = &BarrierPrimBuffer[barrier->AnimationFrame * MAGIC_PAGE_SIZE]; barrier->AnimationFrame ^= 1; if (D_80162080 < 2) { diff --git a/src/magic/brizad.c b/src/magic/brizad.c index 10ed49a7..8ada3c59 100644 --- a/src/magic/brizad.c +++ b/src/magic/brizad.c @@ -2,18 +2,19 @@ #include "common.h" #include "../battle/battle.h" +#include "magic_private.h" // Ice (ブリザド / Blizzard), tier 1. Structurally a sibling of barrier.c: // a 3D model effect built through func_800D29D4 with the PSYQ matrix helpers, // double-buffered into a 0x20000 primitive page per frame. - -// The primitive buffer holds two pages; the flip slot alternates between them -// so the GPU can read last frame's primitives while this frame builds. -#define BRIZAD_PAGE_SIZE 0x10000 +// +// The model lives in this overlay's data segment at 0x801B03F0: 84 vertices +// and 120 Gouraud triangles, no textures, forming a twelve-spike burst whose +// tips point at the vertices of an icosahedron. // .bss -- laid out so BrizadBufferPtr lands immediately after the buffer, at // 0x801B1014 + 0x20000. Matches the BarrierPrimBuffer/BarrierBufferPtr pair. -static char BrizadPrimBuffer[2 * BRIZAD_PAGE_SIZE]; +static char BrizadPrimBuffer[2 * MAGIC_PAGE_SIZE]; static void* BrizadBufferPtr; // current write pointer into the above typedef struct { @@ -29,28 +30,25 @@ typedef struct { /* 0x1E */ s16 unk1E; } BrizadData; // size:0x20 -// PSX fixed point: 1.0 == 1 << FIXED_SHIFT. Angles: FIXED_ONE == a full turn. -#define FIXED_SHIFT 12 -#define FIXED_ONE (1 << FIXED_SHIFT) - -// The block renders on frames 0..14, retiring after the last one, so both rates -// are per (BRIZAD_LIFETIME - 1) frames: over its life it grows from nothing to -// 3x the target's size and spins through exactly one revolution. +// Frames 0..14; the model grows and fades out. func_800D29D4 loads the fade +// as the GTE's depth-cue factor, driving the vertex colour toward +// SetFarColor, black here. Every primitive is emitted semi-transparent and +// the GPU blends additively, so black adds nothing and the model fades to +// invisible rather than to a dark shape. Both rates are per (BRIZAD_LIFETIME +// - 1) frames and the divisions truncate exactly; the compiler emits them as +// shift-add chains. By the last frame growth reaches 0x2FF6 (2.998x), fade +// 0xFF8. #define BRIZAD_LIFETIME 15 #define GROWTH_PER_FRAME (3 * FIXED_ONE / (BRIZAD_LIFETIME - 1)) // 0x36D -#define SPIN_PER_FRAME (FIXED_ONE / (BRIZAD_LIFETIME - 1)) // 0x124 +#define FADE_PER_FRAME (FIXED_ONE / (BRIZAD_LIFETIME - 1)) // 0x124 -// ScaleMatrix writes into MATRIX.m, which is s16; a scale of 0x7FFF against an -// identity entry of FIXED_ONE lands exactly on the ceiling, so clamp there. +// ScaleMatrix writes into MATRIX.m, which is s16, so the scale clamps here. #define SCALE_MAX 0x7FFF -extern Unk801B0C98 BrizadRenderDesc; +extern ModelRenderDesc BrizadRenderDesc; extern BrizadData D_80162978[]; extern s16 D_80151774; -// Returns a scale derived from the target's model size. -s32 func_800D55A4(s32 target); - // Render pass; registered by BrizadSpawnIce, so it takes no arguments. static void BrizadRenderIce(void) { MATRIX matrix; @@ -58,7 +56,7 @@ static void BrizadRenderIce(void) { BrizadData* effect = &D_80162978[D_8015169C]; s16 nextFrame; s16 frame; - s16 spin; + s16 fade; s32 growth = (effect->Scale * GROWTH_PER_FRAME); s32 scale = (s32)(effect->AnimationFrame * growth) >> FIXED_SHIFT; @@ -68,9 +66,9 @@ static void BrizadRenderIce(void) { scaleVec.vx = scaleVec.vy = scaleVec.vz = scale; frame = effect->AnimationFrame; if (frame < 0) { - spin = 0; + fade = 0; } else { - spin = frame * SPIN_PER_FRAME; + fade = frame * FADE_PER_FRAME; } RotMatrixYXZ(&effect->Rot, &matrix); ScaleMatrix(&matrix, &scaleVec); @@ -81,7 +79,7 @@ static void BrizadRenderIce(void) { SetRotMatrix(&matrix); SetTransMatrix(&matrix); SetFarColor(0, 0, 0); - BrizadRenderDesc.unkA = spin; + BrizadRenderDesc.color = fade; BrizadBufferPtr = func_800D29D4(&BrizadRenderDesc, g_cDb->unk70, 12, BrizadBufferPtr); if (D_80062D98 == 0) { nextFrame = (u16)effect->AnimationFrame + 1; @@ -129,7 +127,7 @@ static void BrizadAttachToTarget(s32 target) { D_80162978[BattleEffectRegister(B static void BrizadDoubleBufferFlip(void) { BrizadData* flip = &D_80162978[D_8015169C]; - BrizadBufferPtr = flip->AnimationFrame * BRIZAD_PAGE_SIZE + BrizadPrimBuffer; + BrizadBufferPtr = &BrizadPrimBuffer[flip->AnimationFrame * MAGIC_PAGE_SIZE]; flip->AnimationFrame = (u16)flip->AnimationFrame ^ 1; if (D_80162080 < 2) { flip->StartFrame = -1; diff --git a/src/magic/lv5deth.c b/src/magic/lv5deth.c index 9ce4cb77..391cca34 100644 --- a/src/magic/lv5deth.c +++ b/src/magic/lv5deth.c @@ -9,6 +9,19 @@ extern CVECTOR D_800F5B70; extern s16 D_800F5B74; extern u8 D_80062D98; // set while the battle is paused +// Ring and sprite, spawned together per target: fade in over FADE_IN_FRAMES, +// hold, fade out from FADE_OUT_START_FRAME, retire at TARGET_LIFETIME. +#define TARGET_LIFETIME 45 +#define FADE_IN_FRAMES 8 +#define FADE_OUT_START_FRAME 37 +#define RESULT_POPUP_FRAME 35 + +// Screen fade: the far-colour depth ramps by FAR_DEPTH_PER_FRAME to +// FAR_DEPTH_MAX, holds while targets animate, then eases back down. +#define FAR_DEPTH_PER_FRAME 320 +#define FAR_DEPTH_MAX 2560 +#define SCREEN_FADE_LIFETIME 53 + // One slot of the shared battle effect array, as Lv5 Death lays it out. Only // StartFrame/AnimationFrame are common to every magic overlay; the remaining // 0x1C bytes are payload each effect defines for itself -- compare BarrierData @@ -17,7 +30,8 @@ typedef struct Lv5DeathEffect { /* 0x00 */ s16 StartFrame; /* 0x02 */ s16 AnimationFrame; /* 0x04 */ SVECTOR Pos; - /* 0x0C */ u16 Scale; // matrix diagonal handed to func_800D4368 + /* 0x0C */ u16 Scale; // set once at spawn; func_800D4368 puts it on + // all three diagonal entries of its matrix /* 0x0E */ union { s16 TargetIndex; // ring / sprite / attach effects s16 FadeOutStartFrame; // screen-fade effect only @@ -38,17 +52,8 @@ extern u_long Lv5DeathTexture[]; // 8bpp TIM + 256-colour CLUT, uploaded on setu // Flat 16-point ring of radius 976 lying in the XY plane at z = -21. extern s32 Lv5DeathRingModel[]; -// Render descriptor for the func_800D4D90 pass. Same layout func_800D29D4 -// reads, but this renderer only touches offsets 0x0..0xB, so the instance in -// ROM is truncated to 0xC bytes. -typedef struct { - /* 0x0 */ s32* model; - /* 0x4 */ CVECTOR color; // .cd holds the GPU primitive code (0x2E) - /* 0x8 */ s16 TextureFrame; - /* 0xA */ s16 unkA; -} Lv5DeathDesc; // size:0xC - -extern Lv5DeathDesc Lv5DeathSpriteDesc; +// .color.cd holds the GPU primitive code (0x2E). +extern SpriteRenderDesc Lv5DeathSpriteDesc; static void Lv5DeathBufferFlip(void) { Lv5DeathBufferPtr = g_dbIndex == 0 ? Lv5DeathPrimBuffer0 : Lv5DeathPrimBuffer1; @@ -63,41 +68,44 @@ static void Lv5DeathMainSetup(s32 targetMask, s32 arg1); // here. Trampolines to Lv5DeathMainSetup, which sits at the end of the file. void MAGIC_Lv5Death(s32 targetMask, s32 arg1) { Lv5DeathMainSetup(targetMask, arg1); } -// The ring spins 0 -> 180 degrees while it fades in, holds, then completes the -// turn on the way out. Scales from 1.0 down to 0.5 over the same window. +// One fixed-size ring held at the target for 46 frames. The depth cue carries +// the animation: 0x1000 down to 0x800 over frames 0..8, held to 36, back to +// 0x1000 by 45, fading the ring up to half intensity and out again. static void Lv5DeathRenderRing(void) { // Unused, but required for the match; it gives the function its 0x58 frame. char pad[0x34]; Lv5DeathEffect* effect; - s32 rot; - s32 val; - Unk801B0C98* desc; + s32 scale; + s32 frame; + ModelRenderDesc* desc; effect = &D_80162978[D_8015169C]; - rot = effect->Scale << 16; - func_800D4368(&effect->Pos, rot >> 16, -(rot >> 19)); + // The shift pair sign-extends Scale; the bias pulls the ring an eighth + // of its size toward the camera, clear of the target. + scale = effect->Scale << 16; + func_800D4368(&effect->Pos, scale >> 16, -(scale >> 19)); // Render descriptor built in scratchpad RAM. - desc = (Unk801B0C98*)0x1F800000; - desc->unk0 = Lv5DeathRingModel; - desc->unk4 = 0x88; - desc->unk8 = 0; - desc->unkA = 0x800; - desc->unkC = 0; - desc->unkE = 0; - - val = effect->AnimationFrame; - if (val < 8) { - val <<= 8; - desc->unkA = 0x1000 - val; - } else if (val >= 37) { - desc->unkA = (val << 8) - 0x1D00; + desc = (ModelRenderDesc*)0x1F800000; + desc->model = Lv5DeathRingModel; + desc->flags = MODEL_DEPTH_CUE | MODEL_SEMI_TRANS; + desc->uvOffset = 0; + desc->color = 0x800; + desc->tpage = 0; + desc->clut = 0; + + frame = effect->AnimationFrame; + if (frame < FADE_IN_FRAMES) { + frame <<= 8; + desc->color = 0x1000 - frame; + } else if (frame >= FADE_OUT_START_FRAME) { + desc->color = (frame << 8) - 0x1D00; } SetFarColor(0, 0, 0); Lv5DeathBufferPtr = func_800D29D4(desc, g_cDb->unk70, 12, Lv5DeathBufferPtr); - if (effect->AnimationFrame >= 45) { + if (effect->AnimationFrame >= TARGET_LIFETIME) { effect->StartFrame = -1; } if (D_80062D98 == 0) { @@ -111,30 +119,30 @@ static void Lv5DeathRenderRing(void) { static void Lv5DeathRenderTargetSprite(void) { Lv5DeathEffect* effect; s32 frame; - u8 color; + u8 intensity; effect = &D_80162978[D_8015169C]; - Lv5DeathSpriteDesc.TextureFrame = effect->AnimationFrame & 7; + Lv5DeathSpriteDesc.frameIndex = effect->AnimationFrame & 7; frame = effect->AnimationFrame; - if (frame < 8) { - color = frame * 16; - } else if (frame >= 37) { - color = -128 - ((frame - 37) * 16); + if (frame < FADE_IN_FRAMES) { + intensity = frame * 16; + } else if (frame >= FADE_OUT_START_FRAME) { + intensity = -128 - ((frame - FADE_OUT_START_FRAME) * 16); } else { - color = 128; + intensity = 128; } - Lv5DeathSpriteDesc.color.r = Lv5DeathSpriteDesc.color.g = Lv5DeathSpriteDesc.color.b = color; + Lv5DeathSpriteDesc.color.r = Lv5DeathSpriteDesc.color.g = Lv5DeathSpriteDesc.color.b = intensity; func_800D4368(&effect->Pos, (s16)effect->Scale, -((s16)effect->Scale >> 2)); Lv5DeathBufferPtr = func_800D4D90(&Lv5DeathSpriteDesc, g_cDb->unk70, 12, Lv5DeathBufferPtr); - if (effect->AnimationFrame >= 45) { + if (effect->AnimationFrame >= TARGET_LIFETIME) { effect->StartFrame = -1; Lv5DeathTargetsRemaining--; } if (D_80062D98 == 0) { - if (effect->AnimationFrame == 35) { + if (effect->AnimationFrame == RESULT_POPUP_FRAME) { func_800D5774(effect->u.TargetIndex); } effect->AnimationFrame++; @@ -146,27 +154,30 @@ static void Lv5DeathRenderTargetSprite(void) { // easing back down once the last one retires. static void Lv5DeathScreenFade(void) { Lv5DeathEffect* effect; - s32 val; + s32 farDepth; effect = &D_80162978[D_8015169C]; - if (effect->AnimationFrame < 8) { + if (effect->AnimationFrame < FADE_IN_FRAMES) { D_800F5B70.r = D_800F5B70.g = D_800F5B70.b = 0; - val = effect->AnimationFrame * 320; + farDepth = effect->AnimationFrame * FAR_DEPTH_PER_FRAME; } else if (Lv5DeathTargetsRemaining <= 0) { if (effect->u.FadeOutStartFrame == 0) { effect->u.FadeOutStartFrame = effect->AnimationFrame; } - val = 2560 - (effect->AnimationFrame - effect->u.FadeOutStartFrame) * 320; + farDepth = FAR_DEPTH_MAX - (effect->AnimationFrame - effect->u.FadeOutStartFrame) * FAR_DEPTH_PER_FRAME; } else { - val = 2560; + farDepth = FAR_DEPTH_MAX; } - if (effect->AnimationFrame >= 53) { - val = 0; + // Frame 53 closes the slot on 0 wherever the ease-out has reached: it + // starts where the last target retired, so one target drops from 640 + // and three, retiring on 46/48/50, drop from 1920. + if (effect->AnimationFrame >= SCREEN_FADE_LIFETIME) { + farDepth = 0; effect->StartFrame = -1; } - D_800F5B74 = val; + D_800F5B74 = farDepth; if (D_80062D98 == 0) { effect->AnimationFrame++; } @@ -198,6 +209,7 @@ static void Lv5DeathMainSetup(s32 targetMask, s32 arg1) { func_800D2980(Lv5DeathTexture, 0, 0, 0); effect = &D_80162978[BattleEffectRegister(Lv5DeathScreenFade)]; effect->u.FadeOutStartFrame = 0; + // frameStep 2: with three targets the pairs spawn on frames 1, 3 and 5. MagicAnimationRegister(targetMask, arg1, 2, Lv5DeathAttachToTarget); i = 0; diff --git a/src/magic/magic_private.h b/src/magic/magic_private.h new file mode 100644 index 00000000..0185c82a --- /dev/null +++ b/src/magic/magic_private.h @@ -0,0 +1,15 @@ +#ifndef MAGIC_PRIVATE_H +#define MAGIC_PRIVATE_H + +// Shared by the magic overlays only; battle.h stays the public interface. + +// PSX fixed point: 1.0 == 1 << FIXED_SHIFT. Angles: FIXED_ONE == a full turn. +#define FIXED_SHIFT 12 +#define FIXED_ONE (1 << FIXED_SHIFT) + +// Primitive buffer page. Each overlay holds two and its buffer-flip slot +// alternates between them, so the GPU can read last frame's primitives while +// this frame builds. +#define MAGIC_PAGE_SIZE 0x10000 + +#endif From b5c88c529cdc933026dcf277f1d1acd0a23e7692 Mon Sep 17 00:00:00 2001 From: Eduardo Vieira Date: Mon, 7 Sep 2026 16:13:04 -0300 Subject: [PATCH 2/3] magic: decompile the Bolt and MBarrier overlays Both overlays match in full. --- config/sym_ovl_export.us.txt | 11 ++ config/symbols.magic-mabaria.us.txt | 9 ++ config/symbols.magic-thunder.us.txt | 16 +++ config/us.yaml | 26 ++++ src/magic/mabaria.c | 140 +++++++++++++++++++ src/magic/thunder.c | 206 ++++++++++++++++++++++++++++ tools/ninja/gen.py | 2 + 7 files changed, 410 insertions(+) create mode 100644 config/symbols.magic-mabaria.us.txt create mode 100644 config/symbols.magic-thunder.us.txt create mode 100644 src/magic/mabaria.c create mode 100644 src/magic/thunder.c diff --git a/config/sym_ovl_export.us.txt b/config/sym_ovl_export.us.txt index ddf91d64..2cbd2e56 100644 --- a/config/sym_ovl_export.us.txt +++ b/config/sym_ovl_export.us.txt @@ -2366,3 +2366,14 @@ Lv5DeathPrimBuffer1 = 0x801cce50; Lv5DeathBufferPtr = 0x801d8e50; Lv5DeathFlipEffect = 0x801d8e54; Lv5DeathTargetsRemaining = 0x801d8e58; +MAGIC_Thunder = 0x801b0000; +ThunderRenderData0 = 0x801b073c; +ThunderRenderData1 = 0x801b07d0; +ThunderModel = 0x801b0898; +ThunderTexture = 0x801b0dac; +ThunderModelMatrix = 0x801c0e2c; +ThunderModelDesc = 0x801c0e4c; +ThunderRenderDesc0 = 0x801c0e5c; +ThunderRenderDesc1 = 0x801c0e68; +MAGIC_MBarrier = 0x801b0000; +MabariaRenderDesc = 0x801b0c90; diff --git a/config/symbols.magic-mabaria.us.txt b/config/symbols.magic-mabaria.us.txt new file mode 100644 index 00000000..aca4a4be --- /dev/null +++ b/config/symbols.magic-mabaria.us.txt @@ -0,0 +1,9 @@ +MAGIC_MBarrier = 0x801B0000; +MabariaRenderModel = 0x801B0020; +MabariaAnimationUpdate = 0x801B01C0; +MabariaAttachToTarget = 0x801B0378; +MabariaDoubleBufferFlip = 0x801B04DC; +MabariaMainSetup = 0x801B053C; +MabariaRenderDesc = 0x801B0C90; // size:0x10 +MabariaPrimBuffer = 0x801B0CA8; +MabariaBufferPtr = 0x801D0CA8; diff --git a/config/symbols.magic-thunder.us.txt b/config/symbols.magic-thunder.us.txt new file mode 100644 index 00000000..36054364 --- /dev/null +++ b/config/symbols.magic-thunder.us.txt @@ -0,0 +1,16 @@ +MAGIC_Thunder = 0x801B0000; +ThunderRenderModel = 0x801B0020; +ThunderSpawnBolt = 0x801B0350; +ThunderAttachToTarget = 0x801B05A4; +ThunderDoubleBufferFlip = 0x801B066C; +ThunderMainSetup = 0x801B06CC; +ThunderRenderData0 = 0x801B073C; +ThunderRenderData1 = 0x801B07D0; +ThunderModel = 0x801B0898; +ThunderTexture = 0x801B0DAC; +ThunderModelMatrix = 0x801C0E2C; // size:0x20 +ThunderModelDesc = 0x801C0E4C; // size:0x10 +ThunderRenderDesc0 = 0x801C0E5C; // size:0xC +ThunderRenderDesc1 = 0x801C0E68; // size:0xC +ThunderPrimBuffer = 0x801C0E78; +ThunderBufferPtr = 0x801E0E78; diff --git a/config/us.yaml b/config/us.yaml index 78c738bf..7c71d8d1 100644 --- a/config/us.yaml +++ b/config/us.yaml @@ -305,3 +305,29 @@ overlays: segments: - [0, c, lv5deth] - [0x5e0, data, lv5deth] + # THUNDER.BIN -- "Bolt", the tier-1 lightning spell + - name: thunder + disk_path: disks/us/MAGIC/THUNDER.BIN + sha1: 5767aed5f73876075f01b3958a2e4738fa33b043 + base_path: magic + vram_start: 0x801B0000 + symbol_addrs_path: + - config/sym_export.us.txt + - config/symbols.magic-thunder.us.txt + - config/sym_export_battle.us.txt + segments: + - [0, c, thunder] + - [0x73c, data, thunder] + # MABARIA.BIN -- "MBarrier", the magic-damage half of Wall + - name: mabaria + disk_path: disks/us/MAGIC/MABARIA.BIN + sha1: 9169442d3e1702b7ad4425d95367f52334f84d60 + base_path: magic + vram_start: 0x801B0000 + symbol_addrs_path: + - config/sym_export.us.txt + - config/symbols.magic-mabaria.us.txt + - config/sym_export_battle.us.txt + segments: + - [0, c, mabaria] + - [0x5b4, data, mabaria] diff --git a/src/magic/mabaria.c b/src/magic/mabaria.c new file mode 100644 index 00000000..39882d5c --- /dev/null +++ b/src/magic/mabaria.c @@ -0,0 +1,140 @@ +//! PSYQ=3.3 CC1=2.6.3 + +#include "common.h" +#include "../battle/battle.h" +#include "magic_private.h" + +// Frame 16 would land on 0x7FFE, just under ScaleMatrix's 0x7FFF s16 ceiling. +#define GROWTH_TOTAL 0x3BFF +#define SCALE_BASE 0x400 + +// Fade is the GTE depth cue toward SetFarColor, black here, reaching 0xE00 on +// frame 15. Drawn semi-transparent and blended additively, so black vanishes. +#define MABARIA_LIFETIME 16 +#define FADE_START_FRAME 8 +#define FADE_PER_FRAME 0x200 + +typedef struct { + /* 0x00 */ s16 StartFrame; + /* 0x02 */ s16 AnimationFrame; + /* 0x04 */ SVECTOR Pos; + /* 0x0C */ SVECTOR Rot; + /* 0x14 */ s16 TargetIndex; + /* 0x16 */ char pad16[0xA]; // untouched by the overlay +} MabariaData; // size:0x20 + +typedef struct { + /* 0x0000 */ char pad[MAGIC_PAGE_SIZE]; +} MabariaPrimPage; + +extern s32 D_801B0CA0; +extern s32 D_801B0CA4; +extern MabariaData D_80162978[]; +extern ModelRenderDesc MabariaRenderDesc; +extern MabariaPrimPage MabariaPrimBuffer[]; +extern void* MabariaBufferPtr; + +static void MabariaMainSetup(s32 arg0, s32 arg1); + +void MAGIC_MBarrier(s32 arg0, s32 arg1) { MabariaMainSetup(arg0, arg1); } + +// Grows over the 16 frames, fading out over the last 8. +static void MabariaRenderModel(void) { + MabariaData* effect = &D_80162978[D_8015169C]; + // D_801B0CA0 is always 0x2000: scale runs 0.5x on frame 0 to 7.5x on 15. + s32 growth = effect->AnimationFrame * GROWTH_TOTAL / MABARIA_LIFETIME; + MATRIX matrix; + s32 fade; + s16 nextFrame; + VECTOR scale; + + scale.vx = scale.vy = scale.vz = ((growth + SCALE_BASE) * D_801B0CA0) >> FIXED_SHIFT; + // D_801B0CA4 holds 0, so the lerp passes the fade through. + if (effect->AnimationFrame < FADE_START_FRAME) { + fade = 0; + } else { + fade = (effect->AnimationFrame - FADE_START_FRAME) * FADE_PER_FRAME; + } + fade += ((FIXED_ONE - fade) * D_801B0CA4) >> FIXED_SHIFT; + RotMatrixYXZ(&effect->Rot, &matrix); + matrix.t[0] = (s32)effect->Pos.vx; + matrix.t[1] = (s32)effect->Pos.vy; + matrix.t[2] = (s32)effect->Pos.vz; + ScaleMatrix(&matrix, &scale); + CompMatrix(&D_800FA63C.m, &matrix, &matrix); + SetRotMatrix(&matrix); + SetTransMatrix(&matrix); + SetFarColor(0, 0, 0); + MabariaRenderDesc.color = fade; + MabariaBufferPtr = func_800D29D4(&MabariaRenderDesc, g_cDb->unk70, 0xC, MabariaBufferPtr); + if (D_80062D98 == 0) { + nextFrame = effect->AnimationFrame + 1; + effect->AnimationFrame = nextFrame; + if (nextFrame >= MABARIA_LIFETIME) { + effect->StartFrame = -1; + } + } +} + +// Spawns a render instance on frames 0, 4 and 8; retires on 16. +static void MabariaAnimationUpdate(void) { + MabariaData* effect = &D_80162978[D_8015169C]; + MabariaData* next; + + if (D_80062D98 != 0) { + return; + } + + if (effect->AnimationFrame == 0) { + next = &D_80162978[BattleEffectRegister(MabariaRenderModel)]; + next->Pos = effect->Pos; + next->Rot = effect->Rot; + } + + if (effect->AnimationFrame == 4) { + next = &D_80162978[BattleEffectRegister(MabariaRenderModel)]; + next->Pos = effect->Pos; + next->Rot = effect->Rot; + } + + if (effect->AnimationFrame == 8) { + next = &D_80162978[BattleEffectRegister(MabariaRenderModel)]; + next->Pos = effect->Pos; + next->Rot = effect->Rot; + } + + if (effect->AnimationFrame == 16) { + func_800D5774(effect->TargetIndex); + effect->StartFrame = -1; + } + + effect->AnimationFrame++; +} + +static void MabariaAttachToTarget(s32 target) { + MabariaData* effect = &D_80162978[BattleEffectRegister(MabariaAnimationUpdate)]; + + BattleGetPartPosition(target, D_801518E4[target].D_8015190F, &effect->Pos); + effect->Pos.vx = effect->Pos.vx - ((rsin(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> FIXED_SHIFT); + effect->Pos.vz = effect->Pos.vz - ((rcos(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> FIXED_SHIFT); + effect->Rot = D_801518E4[target].unk160; + effect->TargetIndex = target; +} + +static void MabariaDoubleBufferFlip(void) { + MabariaData* effect = &D_80162978[D_8015169C]; + + MabariaBufferPtr = &MabariaPrimBuffer[effect->AnimationFrame]; + effect->AnimationFrame = effect->AnimationFrame ^ 1; + if (D_80162080 < 2) { + effect->StartFrame = -1; + } +} + +static void MabariaMainSetup(s32 arg0, s32 arg1) { + D_801B0CA0 = 0x2000; + D_801B0CA4 = 0; + BattleEffectRegister(MabariaDoubleBufferFlip); + MagicAnimationRegister(arg0, arg1, 0, MabariaAttachToTarget); + BattleCommandSend(0x20, 0x40, 0x43); +} diff --git a/src/magic/thunder.c b/src/magic/thunder.c new file mode 100644 index 00000000..04ed825c --- /dev/null +++ b/src/magic/thunder.c @@ -0,0 +1,206 @@ +//! PSYQ=3.3 CC1=2.6.3 + +#include "common.h" +#include "../battle/battle.h" +#include "magic_private.h" + +// Bolt (サンダー / Thunder), tier 1. Three passes share the effect slot array: +// a model drawn through func_800D29D4, and two textured-quad passes drawn +// through func_800D4D90, all double-buffered into a 0x20000 primitive page. + +// Model pass: full grey for the first half, then dimmed by GREY_PER_FRAME. +#define MODEL_LIFETIME 16 +#define DIM_START_FRAME 8 +#define GREY_FULL 0x80 +#define GREY_PER_FRAME 0x10 + +// Bolt pass: registers the renderers on frame 0, scatters a spark from +// SPARK_START_FRAME on, retires at BOLT_LIFETIME. +#define BOLT_LIFETIME 16 +#define SPARK_START_FRAME 2 + +typedef struct { + /* 0x00 */ s16 StartFrame; + /* 0x02 */ s16 AnimationFrame; + /* 0x04 */ SVECTOR Pos; + /* 0x0C */ SVECTOR unk0C; + /* 0x14 */ s16 unk14; + /* 0x16 */ u16 Scale; // 0x1000 == 1.0, grows by ScaleStep + /* 0x18 */ u16 ScaleStep; // per-frame growth, seeded 0x200 + /* 0x1A */ u16 unk1A; + /* 0x1C */ s16 unk1C; + /* 0x1E */ char pad1E[2]; +} ThunderData; // size:0x20 + +typedef struct { + /* 0x00 */ char pad[MAGIC_PAGE_SIZE]; +} ThunderPrimPage; // size:0x10000 + +// The renderers write several primitive kinds at varying sizes and hand +// back the next write position. Every magic overlay declares it this way. +extern void* ThunderBufferPtr; +extern ThunderData D_80162978[]; +extern ThunderPrimPage ThunderPrimBuffer[]; +extern u_long ThunderTexture[]; // 8bpp TIM + CLUT, uploaded on setup +extern SpriteRenderDesc ThunderRenderDesc0; +extern SpriteRenderDesc ThunderRenderDesc1; +extern MATRIX ThunderModelMatrix; +extern ModelRenderDesc ThunderModelDesc; + +static void ThunderMainSetup(s32 arg0, s32 arg1); + +void MAGIC_Thunder(s32 arg0, s32 arg1) { ThunderMainSetup(arg0, arg1); } + +// Draws the embedded model through the model path, growing it by 0x200 a +// frame from 1.0x to 2.875x across the 16 frames and dimming it over the +// last 8. +// +// Flags 0x08 leave MODEL_DEPTH_CUE clear, so color is a grey level: it +// steps from 0x80 down to 0x10, dimming the model from grey 128 to 16. +// +// The matrix holds a fixed orientation scaled by Scale -- m[0][0] and +// m[2][1] take Scale, m[1][2] takes -Scale. Confirmed live: the other six +// entries hold zero on all 16 frames. +static void ThunderRenderModel(void) { + MATRIX matrix; + ThunderData* effect = &D_80162978[D_8015169C]; + s16 frame = effect->AnimationFrame; + u16* scale; // read through a pointer; a plain field read does not match + + if (frame < DIM_START_FRAME) { + ThunderModelDesc.color = GREY_FULL; + } else if (frame < MODEL_LIFETIME) { + ThunderModelDesc.color = GREY_FULL - ((frame - DIM_START_FRAME) * GREY_PER_FRAME); + } else { + effect->StartFrame = -1; + return; + } + + scale = &effect->Scale; + ThunderModelMatrix.m[0][0] = ThunderModelMatrix.m[2][1] = *scale; + ThunderModelMatrix.m[1][2] = -(s16)*scale; + ThunderModelMatrix.t[0] = (s32)effect->Pos.vx; + ThunderModelMatrix.t[1] = (s32)effect->Pos.vy; + ThunderModelMatrix.t[2] = (s32)effect->Pos.vz; + CompMatrix(&D_800FA63C.m, &ThunderModelMatrix, &matrix); + SetRotMatrix(&matrix); + SetTransMatrix(&matrix); + ThunderBufferPtr = func_800D29D4(&ThunderModelDesc, g_cDb->unk70, 0xC, ThunderBufferPtr); + if (D_80062D98 == 0) { + effect->AnimationFrame = (u16)effect->AnimationFrame + 1; + effect->Scale += effect->ScaleStep; + } +} + +// Quad pass over ThunderRenderDesc0, 9 frames. Each block in the data draws +// one quad at a stepped position offset, two on the last frame. Not renamed: +// what it draws is not established. +static void func_801B0180(void) { + ThunderData* effect; + s16 nextFrame; + + effect = &D_80162978[D_8015169C]; + func_800D4368(&effect->Pos, 0x2000, effect->unk1C); + ThunderRenderDesc0.frameIndex = effect->AnimationFrame >> 1; + ThunderBufferPtr = func_800D4D90(&ThunderRenderDesc0, g_cDb->unk70, 0xC, ThunderBufferPtr); + if (D_80062D98 == 0) { + nextFrame = (u16)effect->AnimationFrame + 1; + effect->AnimationFrame = nextFrame; + if (nextFrame == 9) { + effect->StartFrame = -1; + } + } +} + +// Quad pass over ThunderRenderDesc1, 8 frames, mirrored per instance by the +// random bits in unk1A. Not renamed: what it draws is not established. +static void func_801B023C(void) { + MATRIX* matrix; + ThunderData* effect = &D_80162978[D_8015169C]; + s16 nextFrame; + + matrix = func_800D4368(&effect->Pos, 0x2000, effect->unk1C); + if (effect->unk1A & 1) { + matrix->m[0][0] = -matrix->m[0][0]; + } + if (effect->unk1A & 2) { + matrix->m[1][1] = -matrix->m[1][1]; + } + SetRotMatrix(matrix); + SetTransMatrix(matrix); + ThunderRenderDesc1.frameIndex = effect->AnimationFrame; + ThunderBufferPtr = func_800D4D90(&ThunderRenderDesc1, g_cDb->unk70, 0xC, ThunderBufferPtr); + if (D_80062D98 == 0) { + nextFrame = (u16)effect->AnimationFrame + 1; + effect->AnimationFrame = nextFrame; + if (nextFrame == 8) { + effect->StartFrame = -1; + } + } +} + +// Spawn callback for the bolt. On frame 0 it registers the two one-shot +// renderers; from frame 2 on it scatters a spark each frame, retiring at 16. +static void ThunderSpawnBolt(void) { + ThunderData* next; + ThunderData* effect = &D_80162978[D_8015169C]; + s16 nextFrame; + + if (D_80062D98 == 0) { + if (effect->AnimationFrame == 0) { + next = &D_80162978[BattleEffectRegister(func_801B0180)]; + next->Pos = effect->Pos; + next->Pos.vy = 0; + next->unk1C = (u16)effect->unk1C; + func_800D5774(effect->unk14); + // Re-tested after the call; the original re-loads the frame here + // and the match needs it. + if (effect->AnimationFrame == 0) { + next = &D_80162978[BattleEffectRegister(ThunderRenderModel)]; + next->Pos = effect->Pos; + next->Scale = FIXED_ONE; + next->Pos.vy = 0; + next->ScaleStep = 0x200; + next->unk1C = (u16)effect->unk1C; + } + } + if (effect->AnimationFrame >= SPARK_START_FRAME) { + next = &D_80162978[BattleEffectRegister(func_801B023C)]; + next->Pos.vx = ((u16)effect->Pos.vx + rand() % 1000) - 500; + next->Pos.vy = ((u16)effect->Pos.vy + rand() % 1000) - 500; + next->Pos.vz = ((u16)effect->Pos.vz + rand() % 1000) - 500; + next->unk1A = rand() & 3; + next->unk1C = (u16)effect->unk1C; + } + nextFrame = (u16)effect->AnimationFrame + 1; + effect->AnimationFrame = nextFrame; + if (nextFrame == BOLT_LIFETIME) { + effect->StartFrame = -1; + } + } +} + +static void ThunderAttachToTarget(s32 target) { + ThunderData* effect = &D_80162978[BattleEffectRegister(ThunderSpawnBolt)]; + + BattleGetPartPosition(target, D_801518E4[target].D_8015190F, &effect->Pos); + effect->unk14 = target; + effect->unk1C = -D_801518E4[target].unk12; + BattleCommandSend(0x20, BattlePositionToStereoPan(&effect->Pos), 0xB); +} + +static void ThunderDoubleBufferFlip(void) { + ThunderData* data = &D_80162978[D_8015169C]; + + ThunderBufferPtr = &ThunderPrimBuffer[data->AnimationFrame]; + data->AnimationFrame = data->AnimationFrame ^ 1; + if (D_80162080 < 2) { + data->StartFrame = -1; + } +} + +static void ThunderMainSetup(s32 arg0, s32 arg1) { + func_800D2980(ThunderTexture, 0, 0, 0); + BattleEffectRegister(ThunderDoubleBufferFlip); + MagicAnimationRegister(arg0, arg1, 2, ThunderAttachToTarget); +} diff --git a/tools/ninja/gen.py b/tools/ninja/gen.py index 1b629064..3f31e8df 100644 --- a/tools/ninja/gen.py +++ b/tools/ninja/gen.py @@ -433,5 +433,7 @@ def get_ovl_elf_list(file_path) -> list[str]: "barrier", "lv5deth", "brizad", + "thunder", + "mabaria", ]: add_splat_config(os.path.join(work_dir, f"{ovl}.yaml")) From 4bcfe44390c33d6e4c3f66f6569e518c459d20f2 Mon Sep 17 00:00:00 2001 From: Eduardo Vieira Date: Tue, 8 Sep 2026 21:20:14 -0300 Subject: [PATCH 3/3] magic: address review feedback Simpler increments, fewer casts, split declarations, trimmed comments, and STYLE.md naming. --- config/sym_ovl_export.us.txt | 48 +++++----- config/symbols.magic-brizad.us.txt | 4 +- config/symbols.magic-lv5deth.us.txt | 18 ++-- config/symbols.magic-mabaria.us.txt | 6 +- config/symbols.magic-thunder.us.txt | 20 ++--- src/magic/barrier.c | 108 ++++++++++++----------- src/magic/brizad.c | 87 +++++++++---------- src/magic/lv5deth.c | 74 ++++++---------- src/magic/mabaria.c | 54 ++++++------ src/magic/magic_private.h | 8 +- src/magic/thunder.c | 130 +++++++++++----------------- 11 files changed, 252 insertions(+), 305 deletions(-) diff --git a/config/sym_ovl_export.us.txt b/config/sym_ovl_export.us.txt index 2cbd2e56..4cbf9767 100644 --- a/config/sym_ovl_export.us.txt +++ b/config/sym_ovl_export.us.txt @@ -2349,31 +2349,31 @@ func_800B00DC = 0x800b00dc; func_800B01B0 = 0x800b01b0; func_800B0E7C = 0x800b0e7c; MAGIC_Barrier = 0x801b0000; -BarrierBaseScale = 0x801b0cc0; -BarrierPrimBuffer = 0x801b0cc4; -BarrierBufferPtr = 0x801d0cc4; +barrier_base_scale = 0x801b0cc0; +barrier_prim_buffer = 0x801b0cc4; +barrier_buffer_ptr = 0x801d0cc4; MAGIC_Brizad = 0x801b037c; -BrizadRenderDesc = 0x801b1004; -BrizadPrimBuffer = 0x801b1014; -BrizadBufferPtr = 0x801d1014; +g_BrizadRenderDesc = 0x801b1004; +brizad_prim_buffer = 0x801b1014; +brizad_buffer_ptr = 0x801d1014; MAGIC_Lv5Death = 0x801b0054; -Lv5DeathTexture = 0x801b05e0; -Lv5DeathSpriteModel = 0x801c0700; -Lv5DeathRingModel = 0x801c0868; -Lv5DeathSpriteDesc = 0x801c0e44; -Lv5DeathPrimBuffer0 = 0x801c0e50; -Lv5DeathPrimBuffer1 = 0x801cce50; -Lv5DeathBufferPtr = 0x801d8e50; -Lv5DeathFlipEffect = 0x801d8e54; -Lv5DeathTargetsRemaining = 0x801d8e58; +g_Lv5DeathTexture = 0x801b05e0; +g_Lv5DeathSpriteModel = 0x801c0700; +g_Lv5DeathRingModel = 0x801c0868; +g_Lv5DeathSpriteDesc = 0x801c0e44; +g_Lv5DeathPrimBuffer0 = 0x801c0e50; +g_Lv5DeathPrimBuffer1 = 0x801cce50; +g_Lv5DeathBufferPtr = 0x801d8e50; +g_Lv5DeathFlipEffect = 0x801d8e54; +g_Lv5DeathTargetsRemaining = 0x801d8e58; MAGIC_Thunder = 0x801b0000; -ThunderRenderData0 = 0x801b073c; -ThunderRenderData1 = 0x801b07d0; -ThunderModel = 0x801b0898; -ThunderTexture = 0x801b0dac; -ThunderModelMatrix = 0x801c0e2c; -ThunderModelDesc = 0x801c0e4c; -ThunderRenderDesc0 = 0x801c0e5c; -ThunderRenderDesc1 = 0x801c0e68; +g_ThunderRenderData0 = 0x801b073c; +g_ThunderRenderData1 = 0x801b07d0; +g_ThunderModel = 0x801b0898; +g_ThunderTexture = 0x801b0dac; +g_ThunderModelMatrix = 0x801c0e2c; +g_ThunderModelDesc = 0x801c0e4c; +g_ThunderRenderDesc0 = 0x801c0e5c; +g_ThunderRenderDesc1 = 0x801c0e68; MAGIC_MBarrier = 0x801b0000; -MabariaRenderDesc = 0x801b0c90; +g_MabariaRenderDesc = 0x801b0c90; diff --git a/config/symbols.magic-brizad.us.txt b/config/symbols.magic-brizad.us.txt index 164795ea..68e18211 100644 --- a/config/symbols.magic-brizad.us.txt +++ b/config/symbols.magic-brizad.us.txt @@ -4,6 +4,4 @@ BrizadAttachToTargetUnused = 0x801B028C; BrizadAttachToTarget = 0x801B02D4; BrizadDoubleBufferFlip = 0x801B031C; MAGIC_Brizad = 0x801B037C; -BrizadRenderDesc = 0x801B1004; // size:0x10 -BrizadPrimBuffer = 0x801B1014; -BrizadBufferPtr = 0x801D1014; +g_BrizadRenderDesc = 0x801B1004; // size:0x10 diff --git a/config/symbols.magic-lv5deth.us.txt b/config/symbols.magic-lv5deth.us.txt index 39e4fbcf..25f536f9 100644 --- a/config/symbols.magic-lv5deth.us.txt +++ b/config/symbols.magic-lv5deth.us.txt @@ -1,9 +1,9 @@ -Lv5DeathTexture = 0x801B05E0; -Lv5DeathSpriteModel = 0x801C0700; -Lv5DeathRingModel = 0x801C0868; -Lv5DeathSpriteDesc = 0x801C0E44; // size:0xC -Lv5DeathPrimBuffer0 = 0x801C0E50; -Lv5DeathPrimBuffer1 = 0x801CCE50; -Lv5DeathBufferPtr = 0x801D8E50; -Lv5DeathFlipEffect = 0x801D8E54; -Lv5DeathTargetsRemaining = 0x801D8E58; +g_Lv5DeathTexture = 0x801B05E0; +g_Lv5DeathSpriteModel = 0x801C0700; +g_Lv5DeathRingModel = 0x801C0868; +g_Lv5DeathSpriteDesc = 0x801C0E44; // size:0xC +g_Lv5DeathPrimBuffer0 = 0x801C0E50; +g_Lv5DeathPrimBuffer1 = 0x801CCE50; +g_Lv5DeathBufferPtr = 0x801D8E50; +g_Lv5DeathFlipEffect = 0x801D8E54; +g_Lv5DeathTargetsRemaining = 0x801D8E58; diff --git a/config/symbols.magic-mabaria.us.txt b/config/symbols.magic-mabaria.us.txt index aca4a4be..beee4507 100644 --- a/config/symbols.magic-mabaria.us.txt +++ b/config/symbols.magic-mabaria.us.txt @@ -4,6 +4,6 @@ MabariaAnimationUpdate = 0x801B01C0; MabariaAttachToTarget = 0x801B0378; MabariaDoubleBufferFlip = 0x801B04DC; MabariaMainSetup = 0x801B053C; -MabariaRenderDesc = 0x801B0C90; // size:0x10 -MabariaPrimBuffer = 0x801B0CA8; -MabariaBufferPtr = 0x801D0CA8; +g_MabariaRenderDesc = 0x801B0C90; // size:0x10 +g_MabariaPrimBuffer = 0x801B0CA8; +g_MabariaBufferPtr = 0x801D0CA8; diff --git a/config/symbols.magic-thunder.us.txt b/config/symbols.magic-thunder.us.txt index 36054364..5098ed28 100644 --- a/config/symbols.magic-thunder.us.txt +++ b/config/symbols.magic-thunder.us.txt @@ -4,13 +4,13 @@ ThunderSpawnBolt = 0x801B0350; ThunderAttachToTarget = 0x801B05A4; ThunderDoubleBufferFlip = 0x801B066C; ThunderMainSetup = 0x801B06CC; -ThunderRenderData0 = 0x801B073C; -ThunderRenderData1 = 0x801B07D0; -ThunderModel = 0x801B0898; -ThunderTexture = 0x801B0DAC; -ThunderModelMatrix = 0x801C0E2C; // size:0x20 -ThunderModelDesc = 0x801C0E4C; // size:0x10 -ThunderRenderDesc0 = 0x801C0E5C; // size:0xC -ThunderRenderDesc1 = 0x801C0E68; // size:0xC -ThunderPrimBuffer = 0x801C0E78; -ThunderBufferPtr = 0x801E0E78; +g_ThunderRenderData0 = 0x801B073C; +g_ThunderRenderData1 = 0x801B07D0; +g_ThunderModel = 0x801B0898; +g_ThunderTexture = 0x801B0DAC; +g_ThunderModelMatrix = 0x801C0E2C; // size:0x20 +g_ThunderModelDesc = 0x801C0E4C; // size:0x10 +g_ThunderRenderDesc0 = 0x801C0E5C; // size:0xC +g_ThunderRenderDesc1 = 0x801C0E68; // size:0xC +g_ThunderPrimBuffer = 0x801C0E78; +g_ThunderBufferPtr = 0x801E0E78; diff --git a/src/magic/barrier.c b/src/magic/barrier.c index 9150227f..e18d6fa3 100644 --- a/src/magic/barrier.c +++ b/src/magic/barrier.c @@ -4,8 +4,6 @@ #include "../battle/battle.h" #include "magic_private.h" -// Fade phase: the depth cue ramps by FADE_PER_FRAME to 0xE00 on the last -// frame, and the scale grows from SCALE_BASE by SCALE_PER_FADE_FRAME. #define FADE_LAST_FRAME 7 #define FADE_PER_FRAME 0x200 #define SCALE_BASE 0xC00 @@ -74,54 +72,57 @@ static s32 bari_a2[] = { // Embedded Model 0x303F3F3F, // Primitive/color: command 0x30, RGB 3F3F3F 0x00D4D4D4, // Triangle 1: vertex 1 color, RGB D4D4D4 0x003F3F3F}; // Triangle 1: vertex 2 color, RGB 3F3F3F -static int emptyPoly = 0x00000000; -static SVECTOR BorderPivotOffset = {0, 0, -500}; -static ModelRenderDesc BorderRenderDesc = {bari_a1, 0, 0, 0, 0x20}; -static SVECTOR ShieldPivotOffset = {0, 0, -500}; -static ModelRenderDesc ShieldRenderDesc = {bari_a2, 0, 0, 0, 0x20}; -static int BarrierBaseScale; -static char BarrierPrimBuffer[2 * MAGIC_PAGE_SIZE]; -static void* BarrierBufferPtr; +static int empty_poly = 0x00000000; +static SVECTOR border_pivot_offset = {0, 0, -500}; +static ModelRenderDesc border_render_desc = {bari_a1, 0, 0, 0, 0x20}; +static SVECTOR shield_pivot_offset = {0, 0, -500}; +static ModelRenderDesc shield_render_desc = {bari_a2, 0, 0, 0, 0x20}; +static int barrier_base_scale; + +typedef struct { + /* 0x00 */ char pad[MAGIC_PAGE_SIZE]; +} BarrierPrimPage; // size:0x10000 + +static BarrierPrimPage barrier_prim_buffer[2]; +static void* barrier_buffer_ptr; // barrier.c forward declarations static void BarrierMainSetup(int arg0, int arg1); void MAGIC_Barrier(int arg0, int arg1) { BarrierMainSetup(arg0, arg1); } -// Four instances per cast, two frames apart, FaceIndex 0, 1, 3, 2 -- Gray -// code, so the shell grows through adjacent quadrants. Each draws the same -// quarter model and the mirror bits assemble the other three. +// FaceIndex runs 0, 1, 3, 2 across the four instances -- Gray code, so the +// shell grows through adjacent quadrants. static void BarrierRenderBorder(void) { - MATRIX* matrix = (MATRIX*)0x1F800000; - VECTOR* scale = (VECTOR*)0x1F800020; - BarrierData* barrier = &D_80162978[D_8015169C]; - int temp_a0 = (barrier->AnimationFrame + barrier->StartFrame) - 17; + MATRIX* matrix; + VECTOR* scale; + BarrierData* barrier; + int temp_a0; int fade; int faceFlags; - // temp_a0 counts from the fade: below 0 opaque, 0..7 fading, past 7 - // retires the instance. + matrix = (MATRIX*)0x1F800000; + scale = (VECTOR*)0x1F800020; + barrier = &D_80162978[D_8015169C]; + temp_a0 = (barrier->AnimationFrame + barrier->StartFrame) - 17; if (temp_a0 < 0) { - scale->vx = scale->vy = scale->vz = (BarrierBaseScale * SCALE_BASE) >> FIXED_SHIFT; + scale->vx = scale->vy = scale->vz = (barrier_base_scale * SCALE_BASE) >> 12; faceFlags = barrier->FaceIndex; fade = 0; } else if (temp_a0 > FADE_LAST_FRAME) { barrier->StartFrame = -1; return; } else { - // semi-transparency for the fade; the cue ramps 0x200 a frame - // to 0xE00. FaceIndex is 0, 1, 3, 2 across the four instances, - // which are the mirror bits that place each quadrant. faceFlags = barrier->FaceIndex | MODEL_SEMI_TRANS; fade = temp_a0 * FADE_PER_FRAME; scale->vx = scale->vy = scale->vz = - (((temp_a0 * SCALE_PER_FADE_FRAME) + SCALE_BASE) * BarrierBaseScale) >> FIXED_SHIFT; + (((temp_a0 * SCALE_PER_FADE_FRAME) + SCALE_BASE) * barrier_base_scale) >> 12; } SetFarColor(0, 0, 0); RotMatrixYXZ(&barrier->Rot, matrix); ScaleMatrix(matrix, scale); - ApplyMatrix(matrix, &BorderPivotOffset, matrix->t); + ApplyMatrix(matrix, &border_pivot_offset, matrix->t); matrix->t[0] += barrier->Pos.vx; matrix->t[1] += barrier->Pos.vy; @@ -131,9 +132,9 @@ static void BarrierRenderBorder(void) { SetRotMatrix(matrix); SetTransMatrix(matrix); - BorderRenderDesc.flags = faceFlags | MODEL_DEPTH_CUE; - BorderRenderDesc.color = fade; - BarrierBufferPtr = func_800D29D4(&BorderRenderDesc, g_cDb->unk70, 12, BarrierBufferPtr); + border_render_desc.flags = faceFlags | MODEL_DEPTH_CUE; + border_render_desc.color = fade; + barrier_buffer_ptr = func_800D29D4(&border_render_desc, g_cDb->unk70, 12, barrier_buffer_ptr); if (D_80062D98 == 0) { barrier->AnimationFrame++; @@ -141,23 +142,29 @@ static void BarrierRenderBorder(void) { } static void BarrierRenderShield(void) { - MATRIX* matrix1 = (MATRIX*)0x1F800000; - MATRIX* matrix2 = (MATRIX*)0x1F800020; - VECTOR* scale1 = (VECTOR*)0x1F800040; - VECTOR* scale2 = (VECTOR*)0x1F800050; - BarrierData* barrier = &D_80162978[D_8015169C]; - int temp_a0 = barrier->AnimationFrame + barrier->StartFrame - 17; + MATRIX* matrix1; + MATRIX* matrix2; + VECTOR* scale1; + VECTOR* scale2; + BarrierData* barrier; + int temp_a0; int faceFlags; int fade; + matrix1 = (MATRIX*)0x1F800000; + matrix2 = (MATRIX*)0x1F800020; + scale1 = (VECTOR*)0x1F800040; + scale2 = (VECTOR*)0x1F800050; + barrier = &D_80162978[D_8015169C]; + temp_a0 = barrier->AnimationFrame + barrier->StartFrame - 17; if (temp_a0 < 0) { if (barrier->AnimationFrame < 6) { - scale1->vx = scale1->vy = scale1->vz = (barrier->AnimationFrame * (BarrierBaseScale << 9)) >> FIXED_SHIFT; + scale1->vx = scale1->vy = scale1->vz = (barrier->AnimationFrame * (barrier_base_scale << 9)) >> 12; } else { - scale1->vx = scale1->vy = scale1->vz = (BarrierBaseScale * SCALE_BASE) >> FIXED_SHIFT; + scale1->vx = scale1->vy = scale1->vz = (barrier_base_scale * SCALE_BASE) >> 12; } - scale2->vx = scale2->vy = scale2->vz = (BarrierBaseScale * SCALE_BASE) >> FIXED_SHIFT; + scale2->vx = scale2->vy = scale2->vz = (barrier_base_scale * SCALE_BASE) >> 12; faceFlags = barrier->FaceIndex; fade = 0; @@ -168,7 +175,7 @@ static void BarrierRenderShield(void) { faceFlags = barrier->FaceIndex | MODEL_SEMI_TRANS; fade = temp_a0 * FADE_PER_FRAME; scale1->vx = scale1->vy = scale1->vz = scale2->vx = scale2->vy = scale2->vz = - (((temp_a0 * SCALE_PER_FADE_FRAME) + SCALE_BASE) * BarrierBaseScale) >> FIXED_SHIFT; + (((temp_a0 * SCALE_PER_FADE_FRAME) + SCALE_BASE) * barrier_base_scale) >> 12; } SetFarColor(0, 0, 0); @@ -176,7 +183,7 @@ static void BarrierRenderShield(void) { *matrix2 = *matrix1; ScaleMatrix(matrix1, scale1); ScaleMatrix(matrix2, scale2); - ApplyMatrix(matrix2, &ShieldPivotOffset, matrix1->t); + ApplyMatrix(matrix2, &shield_pivot_offset, matrix1->t); matrix1->t[0] += barrier->Pos.vx; matrix1->t[1] += barrier->Pos.vy; matrix1->t[2] += barrier->Pos.vz; @@ -184,9 +191,9 @@ static void BarrierRenderShield(void) { SetRotMatrix(matrix1); SetTransMatrix(matrix1); - ShieldRenderDesc.flags = faceFlags | MODEL_DEPTH_CUE; - ShieldRenderDesc.color = fade; - BarrierBufferPtr = func_800D29D4(&ShieldRenderDesc, g_cDb->unk70, 12, BarrierBufferPtr); + shield_render_desc.flags = faceFlags | MODEL_DEPTH_CUE; + shield_render_desc.color = fade; + barrier_buffer_ptr = func_800D29D4(&shield_render_desc, g_cDb->unk70, 12, barrier_buffer_ptr); if (D_80062D98 == 0) { barrier->AnimationFrame++; @@ -194,9 +201,10 @@ static void BarrierRenderShield(void) { } static void BarrierAnimationUpdate(void) { - BarrierData* barrier = &D_80162978[D_8015169C]; // model instance + BarrierData* barrier; // model instance BarrierData* next; + barrier = &D_80162978[D_8015169C]; if (D_80062D98 != 0) { return; } @@ -274,19 +282,21 @@ static void BarrierAnimationUpdate(void) { } static void BarrierAttachToTarget(int target) { - BarrierData* barrier = &D_80162978[BattleEffectRegister(BarrierAnimationUpdate)]; + BarrierData* barrier; + barrier = &D_80162978[BattleEffectRegister(BarrierAnimationUpdate)]; BattleGetPartPosition(target, D_801518E4[target].D_8015190F, &barrier->Pos); - barrier->Pos.vx -= (rsin(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> FIXED_SHIFT; - barrier->Pos.vz -= (rcos(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> FIXED_SHIFT; + barrier->Pos.vx -= (rsin(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12; + barrier->Pos.vz -= (rcos(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12; barrier->Rot = D_801518E4[target].unk160; barrier->TargetIndex = target; } static void BarrierDoubleBufferFlip(void) { - BarrierData* barrier = &D_80162978[D_8015169C]; + BarrierData* barrier; - BarrierBufferPtr = &BarrierPrimBuffer[barrier->AnimationFrame * MAGIC_PAGE_SIZE]; + barrier = &D_80162978[D_8015169C]; + barrier_buffer_ptr = &barrier_prim_buffer[barrier->AnimationFrame]; barrier->AnimationFrame ^= 1; if (D_80162080 < 2) { @@ -295,7 +305,7 @@ static void BarrierDoubleBufferFlip(void) { } static void BarrierMainSetup(int arg0, int arg1) { - BarrierBaseScale = 0x3000; + barrier_base_scale = 0x3000; BattleEffectRegister(BarrierDoubleBufferFlip); MagicAnimationRegister(arg0, arg1, 4, BarrierAttachToTarget); BattleCommandSend(32, BattleEntityGetStereoPan(arg0), 94); diff --git a/src/magic/brizad.c b/src/magic/brizad.c index 8ada3c59..174cf815 100644 --- a/src/magic/brizad.c +++ b/src/magic/brizad.c @@ -4,18 +4,20 @@ #include "../battle/battle.h" #include "magic_private.h" -// Ice (ブリザド / Blizzard), tier 1. Structurally a sibling of barrier.c: -// a 3D model effect built through func_800D29D4 with the PSYQ matrix helpers, -// double-buffered into a 0x20000 primitive page per frame. +// Ice (ブリザド / Blizzard), tier 1. // -// The model lives in this overlay's data segment at 0x801B03F0: 84 vertices -// and 120 Gouraud triangles, no textures, forming a twelve-spike burst whose -// tips point at the vertices of an icosahedron. +// The model at 0x801B03F0 is 84 vertices and 120 untextured Gouraud +// triangles: a twelve-spike burst whose tips point at the vertices of an +// icosahedron. -// .bss -- laid out so BrizadBufferPtr lands immediately after the buffer, at -// 0x801B1014 + 0x20000. Matches the BarrierPrimBuffer/BarrierBufferPtr pair. -static char BrizadPrimBuffer[2 * MAGIC_PAGE_SIZE]; -static void* BrizadBufferPtr; // current write pointer into the above +// brizad_buffer_ptr must land immediately after the buffer, at 0x801B1014 + +// 0x20000. +typedef struct { + /* 0x00 */ char pad[MAGIC_PAGE_SIZE]; +} BrizadPrimPage; // size:0x10000 + +static BrizadPrimPage brizad_prim_buffer[2]; +static void* brizad_buffer_ptr; typedef struct { /* 0x00 */ s16 StartFrame; @@ -30,36 +32,32 @@ typedef struct { /* 0x1E */ s16 unk1E; } BrizadData; // size:0x20 -// Frames 0..14; the model grows and fades out. func_800D29D4 loads the fade -// as the GTE's depth-cue factor, driving the vertex colour toward -// SetFarColor, black here. Every primitive is emitted semi-transparent and -// the GPU blends additively, so black adds nothing and the model fades to -// invisible rather than to a dark shape. Both rates are per (BRIZAD_LIFETIME -// - 1) frames and the divisions truncate exactly; the compiler emits them as -// shift-add chains. By the last frame growth reaches 0x2FF6 (2.998x), fade -// 0xFF8. +// The fade is the GTE depth-cue factor, driving vertex colour toward +// SetFarColor -- black here. Primitives are semi-transparent and blended +// additively, so the model fades to invisible rather than to a dark shape. #define BRIZAD_LIFETIME 15 -#define GROWTH_PER_FRAME (3 * FIXED_ONE / (BRIZAD_LIFETIME - 1)) // 0x36D -#define FADE_PER_FRAME (FIXED_ONE / (BRIZAD_LIFETIME - 1)) // 0x124 +#define GROWTH_PER_FRAME (3 * 4096 / (BRIZAD_LIFETIME - 1)) // 0x36D +#define FADE_PER_FRAME (4096 / (BRIZAD_LIFETIME - 1)) // 0x124 // ScaleMatrix writes into MATRIX.m, which is s16, so the scale clamps here. #define SCALE_MAX 0x7FFF -extern ModelRenderDesc BrizadRenderDesc; +extern ModelRenderDesc g_BrizadRenderDesc; extern BrizadData D_80162978[]; extern s16 D_80151774; -// Render pass; registered by BrizadSpawnIce, so it takes no arguments. static void BrizadRenderIce(void) { MATRIX matrix; VECTOR scaleVec; - BrizadData* effect = &D_80162978[D_8015169C]; - s16 nextFrame; + BrizadData* effect; s16 frame; s16 fade; - s32 growth = (effect->Scale * GROWTH_PER_FRAME); - s32 scale = (s32)(effect->AnimationFrame * growth) >> FIXED_SHIFT; + s32 growth; + s32 scale; + effect = &D_80162978[D_8015169C]; + growth = (effect->Scale * GROWTH_PER_FRAME); + scale = (effect->AnimationFrame * growth) >> 12; if (scale > SCALE_MAX) { scale = SCALE_MAX; } @@ -72,19 +70,18 @@ static void BrizadRenderIce(void) { } RotMatrixYXZ(&effect->Rot, &matrix); ScaleMatrix(&matrix, &scaleVec); - matrix.t[0] = (s32)effect->Pos.vx; - matrix.t[1] = (s32)effect->Pos.vy; - matrix.t[2] = (s32)effect->Pos.vz; + matrix.t[0] = effect->Pos.vx; + matrix.t[1] = effect->Pos.vy; + matrix.t[2] = effect->Pos.vz; CompMatrix(&D_800FA63C.m, &matrix, &matrix); SetRotMatrix(&matrix); SetTransMatrix(&matrix); SetFarColor(0, 0, 0); - BrizadRenderDesc.color = fade; - BrizadBufferPtr = func_800D29D4(&BrizadRenderDesc, g_cDb->unk70, 12, BrizadBufferPtr); + g_BrizadRenderDesc.color = fade; + brizad_buffer_ptr = func_800D29D4(&g_BrizadRenderDesc, g_cDb->unk70, 12, brizad_buffer_ptr); if (D_80062D98 == 0) { - nextFrame = (u16)effect->AnimationFrame + 1; - effect->AnimationFrame = nextFrame; - if (nextFrame >= BRIZAD_LIFETIME) { + effect->AnimationFrame++; + if (effect->AnimationFrame >= BRIZAD_LIFETIME) { effect->StartFrame = -1; } } @@ -92,8 +89,9 @@ static void BrizadRenderIce(void) { static void BrizadSpawnIce(void) { BrizadData* next; - BrizadData* effect = &D_80162978[D_8015169C]; + BrizadData* effect; + effect = &D_80162978[D_8015169C]; if (D_80062D98 == 0) { if (effect->AnimationFrame == 0) { next = &D_80162978[BattleEffectRegister(BrizadRenderIce)]; @@ -117,29 +115,24 @@ static void BrizadAttachToTargetUnused(s32 target) { D_80162978[BattleEffectRegister(BrizadSpawnIce)].TargetIndex = target; } -// Registered as the MagicAnimationRegister per-target callback by MAGIC_Brizad. -// Spawns a BrizadSpawnIce slot and tells it which target it belongs to. static void BrizadAttachToTarget(s32 target) { D_80162978[BattleEffectRegister(BrizadSpawnIce)].TargetIndex = target; } -// Points the write pointer at this frame's page, then toggles to the other one. // This slot uses AnimationFrame as a 0/1 page index, not as a frame counter. -// Compare BarrierDoubleBufferFlip in barrier.c and Lv5DeathBufferFlip. static void BrizadDoubleBufferFlip(void) { - BrizadData* flip = &D_80162978[D_8015169C]; + BrizadData* flip; - BrizadBufferPtr = &BrizadPrimBuffer[flip->AnimationFrame * MAGIC_PAGE_SIZE]; - flip->AnimationFrame = (u16)flip->AnimationFrame ^ 1; + flip = &D_80162978[D_8015169C]; + brizad_buffer_ptr = &brizad_prim_buffer[flip->AnimationFrame]; + flip->AnimationFrame ^= 1; if (D_80162080 < 2) { flip->StartFrame = -1; } } -// Overlay entry, at 0x801B037C; the D_800EFAF0 dispatch table sends the ice -// magic id here. Registers the buffer flip and the per-target callback, then -// fires the SFX. Unlike barrier and lv5deth this is not a trampoline -- it is -// the last function in the overlay, so it must stay last in this file. +// Overlay entry, at 0x801B037C. This is the last function in the overlay, so +// it must stay last in this file. void MAGIC_Brizad(s32 targetMask, s32 arg1) { BattleEffectRegister(BrizadDoubleBufferFlip); MagicAnimationRegister(targetMask, arg1, 4, BrizadAttachToTarget); - BattleCommandSend(0x20, BattleEntityGetStereoPan((s32)D_80151774), 0x18); + BattleCommandSend(0x20, BattleEntityGetStereoPan(D_80151774), 0x18); } diff --git a/src/magic/lv5deth.c b/src/magic/lv5deth.c index 391cca34..a1373c62 100644 --- a/src/magic/lv5deth.c +++ b/src/magic/lv5deth.c @@ -9,29 +9,23 @@ extern CVECTOR D_800F5B70; extern s16 D_800F5B74; extern u8 D_80062D98; // set while the battle is paused -// Ring and sprite, spawned together per target: fade in over FADE_IN_FRAMES, -// hold, fade out from FADE_OUT_START_FRAME, retire at TARGET_LIFETIME. #define TARGET_LIFETIME 45 #define FADE_IN_FRAMES 8 #define FADE_OUT_START_FRAME 37 #define RESULT_POPUP_FRAME 35 -// Screen fade: the far-colour depth ramps by FAR_DEPTH_PER_FRAME to -// FAR_DEPTH_MAX, holds while targets animate, then eases back down. #define FAR_DEPTH_PER_FRAME 320 #define FAR_DEPTH_MAX 2560 #define SCREEN_FADE_LIFETIME 53 -// One slot of the shared battle effect array, as Lv5 Death lays it out. Only -// StartFrame/AnimationFrame are common to every magic overlay; the remaining -// 0x1C bytes are payload each effect defines for itself -- compare BarrierData -// in barrier.c, which puts its SVECTORs at completely different offsets. +// Only StartFrame/AnimationFrame are common to every magic overlay; the +// remaining 0x1C bytes are payload each effect lays out for itself. typedef struct Lv5DeathEffect { /* 0x00 */ s16 StartFrame; /* 0x02 */ s16 AnimationFrame; /* 0x04 */ SVECTOR Pos; - /* 0x0C */ u16 Scale; // set once at spawn; func_800D4368 puts it on - // all three diagonal entries of its matrix + /* 0x0C */ u16 Scale; // func_800D4368 puts it on all three diagonal + // entries of its matrix /* 0x0E */ union { s16 TargetIndex; // ring / sprite / attach effects s16 FadeOutStartFrame; // screen-fade effect only @@ -42,37 +36,34 @@ typedef struct Lv5DeathEffect { extern Lv5DeathEffect D_80162978[]; // Primitive buffer, one 0xC000 page per double-buffered frame. -extern char Lv5DeathPrimBuffer0[]; -extern char Lv5DeathPrimBuffer1[]; -extern void* Lv5DeathBufferPtr; -extern Lv5DeathEffect* Lv5DeathFlipEffect; // slot running Lv5DeathBufferFlip -extern s32 Lv5DeathTargetsRemaining; -extern u_long Lv5DeathTexture[]; // 8bpp TIM + 256-colour CLUT, uploaded on setup +extern char g_Lv5DeathPrimBuffer0[]; +extern char g_Lv5DeathPrimBuffer1[]; +extern void* g_Lv5DeathBufferPtr; +extern Lv5DeathEffect* g_Lv5DeathFlipEffect; // slot running Lv5DeathBufferFlip +extern s32 g_Lv5DeathTargetsRemaining; +extern u_long g_Lv5DeathTexture[]; // 8bpp TIM + 256-colour CLUT, uploaded on setup // Flat 16-point ring of radius 976 lying in the XY plane at z = -21. -extern s32 Lv5DeathRingModel[]; +extern s32 g_Lv5DeathRingModel[]; // .color.cd holds the GPU primitive code (0x2E). -extern SpriteRenderDesc Lv5DeathSpriteDesc; +extern SpriteRenderDesc g_Lv5DeathSpriteDesc; static void Lv5DeathBufferFlip(void) { - Lv5DeathBufferPtr = g_dbIndex == 0 ? Lv5DeathPrimBuffer0 : Lv5DeathPrimBuffer1; + g_Lv5DeathBufferPtr = g_dbIndex == 0 ? g_Lv5DeathPrimBuffer0 : g_Lv5DeathPrimBuffer1; if (D_80162080 < 2) { - *(s32*)Lv5DeathFlipEffect = -1; + *(s32*)g_Lv5DeathFlipEffect = -1; } } static void Lv5DeathMainSetup(s32 targetMask, s32 arg1); // Overlay entry point, at 0x801B0054; func_800D0C80 dispatches magic id 32 -// here. Trampolines to Lv5DeathMainSetup, which sits at the end of the file. +// here. void MAGIC_Lv5Death(s32 targetMask, s32 arg1) { Lv5DeathMainSetup(targetMask, arg1); } -// One fixed-size ring held at the target for 46 frames. The depth cue carries -// the animation: 0x1000 down to 0x800 over frames 0..8, held to 36, back to -// 0x1000 by 45, fading the ring up to half intensity and out again. static void Lv5DeathRenderRing(void) { - // Unused, but required for the match; it gives the function its 0x58 frame. + // Unused; gives the function its 0x58 stack frame. char pad[0x34]; Lv5DeathEffect* effect; s32 scale; @@ -80,14 +71,13 @@ static void Lv5DeathRenderRing(void) { ModelRenderDesc* desc; effect = &D_80162978[D_8015169C]; - // The shift pair sign-extends Scale; the bias pulls the ring an eighth - // of its size toward the camera, clear of the target. + // The shift pair sign-extends Scale. scale = effect->Scale << 16; func_800D4368(&effect->Pos, scale >> 16, -(scale >> 19)); // Render descriptor built in scratchpad RAM. desc = (ModelRenderDesc*)0x1F800000; - desc->model = Lv5DeathRingModel; + desc->model = g_Lv5DeathRingModel; desc->flags = MODEL_DEPTH_CUE | MODEL_SEMI_TRANS; desc->uvOffset = 0; desc->color = 0x800; @@ -103,7 +93,7 @@ static void Lv5DeathRenderRing(void) { } SetFarColor(0, 0, 0); - Lv5DeathBufferPtr = func_800D29D4(desc, g_cDb->unk70, 12, Lv5DeathBufferPtr); + g_Lv5DeathBufferPtr = func_800D29D4(desc, g_cDb->unk70, 12, g_Lv5DeathBufferPtr); if (effect->AnimationFrame >= TARGET_LIFETIME) { effect->StartFrame = -1; @@ -113,16 +103,13 @@ static void Lv5DeathRenderRing(void) { } } -// Textured sprite over the target: an 8-frame texture cycle, fading up to -// half intensity over 8 frames, holding, then fading back out from frame 37. -// The result popup fires at frame 35, the slot retires at 45. static void Lv5DeathRenderTargetSprite(void) { Lv5DeathEffect* effect; s32 frame; u8 intensity; effect = &D_80162978[D_8015169C]; - Lv5DeathSpriteDesc.frameIndex = effect->AnimationFrame & 7; + g_Lv5DeathSpriteDesc.frameIndex = effect->AnimationFrame & 7; frame = effect->AnimationFrame; if (frame < FADE_IN_FRAMES) { @@ -132,14 +119,14 @@ static void Lv5DeathRenderTargetSprite(void) { } else { intensity = 128; } - Lv5DeathSpriteDesc.color.r = Lv5DeathSpriteDesc.color.g = Lv5DeathSpriteDesc.color.b = intensity; + g_Lv5DeathSpriteDesc.color.r = g_Lv5DeathSpriteDesc.color.g = g_Lv5DeathSpriteDesc.color.b = intensity; func_800D4368(&effect->Pos, (s16)effect->Scale, -((s16)effect->Scale >> 2)); - Lv5DeathBufferPtr = func_800D4D90(&Lv5DeathSpriteDesc, g_cDb->unk70, 12, Lv5DeathBufferPtr); + g_Lv5DeathBufferPtr = func_800D4D90(&g_Lv5DeathSpriteDesc, g_cDb->unk70, 12, g_Lv5DeathBufferPtr); if (effect->AnimationFrame >= TARGET_LIFETIME) { effect->StartFrame = -1; - Lv5DeathTargetsRemaining--; + g_Lv5DeathTargetsRemaining--; } if (D_80062D98 == 0) { if (effect->AnimationFrame == RESULT_POPUP_FRAME) { @@ -149,9 +136,6 @@ static void Lv5DeathRenderTargetSprite(void) { } } -// Darkens the whole battlefield by driving the far-colour depth from 0 up to -// 2560 over 8 frames, holding while any target is still animating, then -// easing back down once the last one retires. static void Lv5DeathScreenFade(void) { Lv5DeathEffect* effect; s32 farDepth; @@ -160,7 +144,7 @@ static void Lv5DeathScreenFade(void) { if (effect->AnimationFrame < FADE_IN_FRAMES) { D_800F5B70.r = D_800F5B70.g = D_800F5B70.b = 0; farDepth = effect->AnimationFrame * FAR_DEPTH_PER_FRAME; - } else if (Lv5DeathTargetsRemaining <= 0) { + } else if (g_Lv5DeathTargetsRemaining <= 0) { if (effect->u.FadeOutStartFrame == 0) { effect->u.FadeOutStartFrame = effect->AnimationFrame; } @@ -169,9 +153,6 @@ static void Lv5DeathScreenFade(void) { farDepth = FAR_DEPTH_MAX; } - // Frame 53 closes the slot on 0 wherever the ease-out has reached: it - // starts where the last target retired, so one target drops from 640 - // and three, retiring on 46/48/50, drop from 1920. if (effect->AnimationFrame >= SCREEN_FADE_LIFETIME) { farDepth = 0; effect->StartFrame = -1; @@ -183,7 +164,6 @@ static void Lv5DeathScreenFade(void) { } } -// Per-target callback: spawns the sprite and the ring on the target's part. static void Lv5DeathAttachToTarget(s32 target) { Lv5DeathEffect* effect; Lv5DeathEffect* ring; @@ -205,8 +185,8 @@ static void Lv5DeathMainSetup(s32 targetMask, s32 arg1) { s32 count; s32 i; - Lv5DeathFlipEffect = &D_80162978[BattleEffectRegister(Lv5DeathBufferFlip)]; - func_800D2980(Lv5DeathTexture, 0, 0, 0); + g_Lv5DeathFlipEffect = &D_80162978[BattleEffectRegister(Lv5DeathBufferFlip)]; + func_800D2980(g_Lv5DeathTexture, 0, 0, 0); effect = &D_80162978[BattleEffectRegister(Lv5DeathScreenFade)]; effect->u.FadeOutStartFrame = 0; // frameStep 2: with three targets the pairs spawn on frames 1, 3 and 5. @@ -219,5 +199,5 @@ static void Lv5DeathMainSetup(s32 targetMask, s32 arg1) { count++; } } - Lv5DeathTargetsRemaining = count; + g_Lv5DeathTargetsRemaining = count; } diff --git a/src/magic/mabaria.c b/src/magic/mabaria.c index 39882d5c..c3cf232a 100644 --- a/src/magic/mabaria.c +++ b/src/magic/mabaria.c @@ -24,63 +24,61 @@ typedef struct { } MabariaData; // size:0x20 typedef struct { - /* 0x0000 */ char pad[MAGIC_PAGE_SIZE]; -} MabariaPrimPage; + /* 0x00 */ char pad[MAGIC_PAGE_SIZE]; +} MabariaPrimPage; // size:0x10000 extern s32 D_801B0CA0; extern s32 D_801B0CA4; extern MabariaData D_80162978[]; -extern ModelRenderDesc MabariaRenderDesc; -extern MabariaPrimPage MabariaPrimBuffer[]; -extern void* MabariaBufferPtr; +extern ModelRenderDesc g_MabariaRenderDesc; +extern MabariaPrimPage g_MabariaPrimBuffer[]; +extern void* g_MabariaBufferPtr; static void MabariaMainSetup(s32 arg0, s32 arg1); void MAGIC_MBarrier(s32 arg0, s32 arg1) { MabariaMainSetup(arg0, arg1); } -// Grows over the 16 frames, fading out over the last 8. static void MabariaRenderModel(void) { - MabariaData* effect = &D_80162978[D_8015169C]; - // D_801B0CA0 is always 0x2000: scale runs 0.5x on frame 0 to 7.5x on 15. - s32 growth = effect->AnimationFrame * GROWTH_TOTAL / MABARIA_LIFETIME; + MabariaData* effect; + s32 growth; MATRIX matrix; s32 fade; - s16 nextFrame; VECTOR scale; - scale.vx = scale.vy = scale.vz = ((growth + SCALE_BASE) * D_801B0CA0) >> FIXED_SHIFT; + effect = &D_80162978[D_8015169C]; + growth = effect->AnimationFrame * GROWTH_TOTAL / MABARIA_LIFETIME; + scale.vx = scale.vy = scale.vz = ((growth + SCALE_BASE) * D_801B0CA0) >> 12; // D_801B0CA4 holds 0, so the lerp passes the fade through. if (effect->AnimationFrame < FADE_START_FRAME) { fade = 0; } else { fade = (effect->AnimationFrame - FADE_START_FRAME) * FADE_PER_FRAME; } - fade += ((FIXED_ONE - fade) * D_801B0CA4) >> FIXED_SHIFT; + fade += ((4096 - fade) * D_801B0CA4) >> 12; RotMatrixYXZ(&effect->Rot, &matrix); - matrix.t[0] = (s32)effect->Pos.vx; - matrix.t[1] = (s32)effect->Pos.vy; - matrix.t[2] = (s32)effect->Pos.vz; + matrix.t[0] = effect->Pos.vx; + matrix.t[1] = effect->Pos.vy; + matrix.t[2] = effect->Pos.vz; ScaleMatrix(&matrix, &scale); CompMatrix(&D_800FA63C.m, &matrix, &matrix); SetRotMatrix(&matrix); SetTransMatrix(&matrix); SetFarColor(0, 0, 0); - MabariaRenderDesc.color = fade; - MabariaBufferPtr = func_800D29D4(&MabariaRenderDesc, g_cDb->unk70, 0xC, MabariaBufferPtr); + g_MabariaRenderDesc.color = fade; + g_MabariaBufferPtr = func_800D29D4(&g_MabariaRenderDesc, g_cDb->unk70, 0xC, g_MabariaBufferPtr); if (D_80062D98 == 0) { - nextFrame = effect->AnimationFrame + 1; - effect->AnimationFrame = nextFrame; - if (nextFrame >= MABARIA_LIFETIME) { + effect->AnimationFrame++; + if (effect->AnimationFrame >= MABARIA_LIFETIME) { effect->StartFrame = -1; } } } -// Spawns a render instance on frames 0, 4 and 8; retires on 16. static void MabariaAnimationUpdate(void) { - MabariaData* effect = &D_80162978[D_8015169C]; + MabariaData* effect; MabariaData* next; + effect = &D_80162978[D_8015169C]; if (D_80062D98 != 0) { return; } @@ -112,19 +110,21 @@ static void MabariaAnimationUpdate(void) { } static void MabariaAttachToTarget(s32 target) { - MabariaData* effect = &D_80162978[BattleEffectRegister(MabariaAnimationUpdate)]; + MabariaData* effect; + effect = &D_80162978[BattleEffectRegister(MabariaAnimationUpdate)]; BattleGetPartPosition(target, D_801518E4[target].D_8015190F, &effect->Pos); - effect->Pos.vx = effect->Pos.vx - ((rsin(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> FIXED_SHIFT); - effect->Pos.vz = effect->Pos.vz - ((rcos(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> FIXED_SHIFT); + effect->Pos.vx = effect->Pos.vx - ((rsin(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12); + effect->Pos.vz = effect->Pos.vz - ((rcos(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12); effect->Rot = D_801518E4[target].unk160; effect->TargetIndex = target; } static void MabariaDoubleBufferFlip(void) { - MabariaData* effect = &D_80162978[D_8015169C]; + MabariaData* effect; - MabariaBufferPtr = &MabariaPrimBuffer[effect->AnimationFrame]; + effect = &D_80162978[D_8015169C]; + g_MabariaBufferPtr = &g_MabariaPrimBuffer[effect->AnimationFrame]; effect->AnimationFrame = effect->AnimationFrame ^ 1; if (D_80162080 < 2) { effect->StartFrame = -1; diff --git a/src/magic/magic_private.h b/src/magic/magic_private.h index 0185c82a..4cf9b72c 100644 --- a/src/magic/magic_private.h +++ b/src/magic/magic_private.h @@ -3,13 +3,7 @@ // Shared by the magic overlays only; battle.h stays the public interface. -// PSX fixed point: 1.0 == 1 << FIXED_SHIFT. Angles: FIXED_ONE == a full turn. -#define FIXED_SHIFT 12 -#define FIXED_ONE (1 << FIXED_SHIFT) - -// Primitive buffer page. Each overlay holds two and its buffer-flip slot -// alternates between them, so the GPU can read last frame's primitives while -// this frame builds. +// Primitive buffer page. Each overlay holds two and alternates between them. #define MAGIC_PAGE_SIZE 0x10000 #endif diff --git a/src/magic/thunder.c b/src/magic/thunder.c index 04ed825c..d0f5dc3c 100644 --- a/src/magic/thunder.c +++ b/src/magic/thunder.c @@ -4,18 +4,13 @@ #include "../battle/battle.h" #include "magic_private.h" -// Bolt (サンダー / Thunder), tier 1. Three passes share the effect slot array: -// a model drawn through func_800D29D4, and two textured-quad passes drawn -// through func_800D4D90, all double-buffered into a 0x20000 primitive page. +// Bolt (サンダー / Thunder), tier 1. -// Model pass: full grey for the first half, then dimmed by GREY_PER_FRAME. #define MODEL_LIFETIME 16 #define DIM_START_FRAME 8 #define GREY_FULL 0x80 #define GREY_PER_FRAME 0x10 -// Bolt pass: registers the renderers on frame 0, scatters a spark from -// SPARK_START_FRAME on, retires at BOLT_LIFETIME. #define BOLT_LIFETIME 16 #define SPARK_START_FRAME 2 @@ -25,8 +20,8 @@ typedef struct { /* 0x04 */ SVECTOR Pos; /* 0x0C */ SVECTOR unk0C; /* 0x14 */ s16 unk14; - /* 0x16 */ u16 Scale; // 0x1000 == 1.0, grows by ScaleStep - /* 0x18 */ u16 ScaleStep; // per-frame growth, seeded 0x200 + /* 0x16 */ s16 Scale; // 0x1000 == 1.0 + /* 0x18 */ u16 ScaleStep; // seeded 0x200 /* 0x1A */ u16 unk1A; /* 0x1C */ s16 unk1C; /* 0x1E */ char pad1E[2]; @@ -36,89 +31,70 @@ typedef struct { /* 0x00 */ char pad[MAGIC_PAGE_SIZE]; } ThunderPrimPage; // size:0x10000 -// The renderers write several primitive kinds at varying sizes and hand -// back the next write position. Every magic overlay declares it this way. -extern void* ThunderBufferPtr; +extern void* g_ThunderBufferPtr; extern ThunderData D_80162978[]; -extern ThunderPrimPage ThunderPrimBuffer[]; -extern u_long ThunderTexture[]; // 8bpp TIM + CLUT, uploaded on setup -extern SpriteRenderDesc ThunderRenderDesc0; -extern SpriteRenderDesc ThunderRenderDesc1; -extern MATRIX ThunderModelMatrix; -extern ModelRenderDesc ThunderModelDesc; +extern ThunderPrimPage g_ThunderPrimBuffer[]; +extern u_long g_ThunderTexture[]; // 8bpp TIM + CLUT, uploaded on setup +extern SpriteRenderDesc g_ThunderRenderDesc0; +extern SpriteRenderDesc g_ThunderRenderDesc1; +extern MATRIX g_ThunderModelMatrix; +extern ModelRenderDesc g_ThunderModelDesc; static void ThunderMainSetup(s32 arg0, s32 arg1); void MAGIC_Thunder(s32 arg0, s32 arg1) { ThunderMainSetup(arg0, arg1); } -// Draws the embedded model through the model path, growing it by 0x200 a -// frame from 1.0x to 2.875x across the 16 frames and dimming it over the -// last 8. -// -// Flags 0x08 leave MODEL_DEPTH_CUE clear, so color is a grey level: it -// steps from 0x80 down to 0x10, dimming the model from grey 128 to 16. -// -// The matrix holds a fixed orientation scaled by Scale -- m[0][0] and -// m[2][1] take Scale, m[1][2] takes -Scale. Confirmed live: the other six -// entries hold zero on all 16 frames. static void ThunderRenderModel(void) { MATRIX matrix; - ThunderData* effect = &D_80162978[D_8015169C]; - s16 frame = effect->AnimationFrame; - u16* scale; // read through a pointer; a plain field read does not match + ThunderData* effect; + s16 frame; + effect = &D_80162978[D_8015169C]; + frame = effect->AnimationFrame; if (frame < DIM_START_FRAME) { - ThunderModelDesc.color = GREY_FULL; + g_ThunderModelDesc.color = GREY_FULL; } else if (frame < MODEL_LIFETIME) { - ThunderModelDesc.color = GREY_FULL - ((frame - DIM_START_FRAME) * GREY_PER_FRAME); + g_ThunderModelDesc.color = GREY_FULL - ((frame - DIM_START_FRAME) * GREY_PER_FRAME); } else { effect->StartFrame = -1; return; } - scale = &effect->Scale; - ThunderModelMatrix.m[0][0] = ThunderModelMatrix.m[2][1] = *scale; - ThunderModelMatrix.m[1][2] = -(s16)*scale; - ThunderModelMatrix.t[0] = (s32)effect->Pos.vx; - ThunderModelMatrix.t[1] = (s32)effect->Pos.vy; - ThunderModelMatrix.t[2] = (s32)effect->Pos.vz; - CompMatrix(&D_800FA63C.m, &ThunderModelMatrix, &matrix); + g_ThunderModelMatrix.m[0][0] = g_ThunderModelMatrix.m[2][1] = effect->Scale; + g_ThunderModelMatrix.m[1][2] = -effect->Scale; + g_ThunderModelMatrix.t[0] = effect->Pos.vx; + g_ThunderModelMatrix.t[1] = effect->Pos.vy; + g_ThunderModelMatrix.t[2] = effect->Pos.vz; + CompMatrix(&D_800FA63C.m, &g_ThunderModelMatrix, &matrix); SetRotMatrix(&matrix); SetTransMatrix(&matrix); - ThunderBufferPtr = func_800D29D4(&ThunderModelDesc, g_cDb->unk70, 0xC, ThunderBufferPtr); + g_ThunderBufferPtr = func_800D29D4(&g_ThunderModelDesc, g_cDb->unk70, 0xC, g_ThunderBufferPtr); if (D_80062D98 == 0) { - effect->AnimationFrame = (u16)effect->AnimationFrame + 1; + effect->AnimationFrame++; effect->Scale += effect->ScaleStep; } } -// Quad pass over ThunderRenderDesc0, 9 frames. Each block in the data draws -// one quad at a stepped position offset, two on the last frame. Not renamed: -// what it draws is not established. static void func_801B0180(void) { ThunderData* effect; - s16 nextFrame; effect = &D_80162978[D_8015169C]; func_800D4368(&effect->Pos, 0x2000, effect->unk1C); - ThunderRenderDesc0.frameIndex = effect->AnimationFrame >> 1; - ThunderBufferPtr = func_800D4D90(&ThunderRenderDesc0, g_cDb->unk70, 0xC, ThunderBufferPtr); + g_ThunderRenderDesc0.frameIndex = effect->AnimationFrame >> 1; + g_ThunderBufferPtr = func_800D4D90(&g_ThunderRenderDesc0, g_cDb->unk70, 0xC, g_ThunderBufferPtr); if (D_80062D98 == 0) { - nextFrame = (u16)effect->AnimationFrame + 1; - effect->AnimationFrame = nextFrame; - if (nextFrame == 9) { + effect->AnimationFrame++; + if (effect->AnimationFrame == 9) { effect->StartFrame = -1; } } } -// Quad pass over ThunderRenderDesc1, 8 frames, mirrored per instance by the -// random bits in unk1A. Not renamed: what it draws is not established. static void func_801B023C(void) { MATRIX* matrix; - ThunderData* effect = &D_80162978[D_8015169C]; - s16 nextFrame; + ThunderData* effect; + effect = &D_80162978[D_8015169C]; matrix = func_800D4368(&effect->Pos, 0x2000, effect->unk1C); if (effect->unk1A & 1) { matrix->m[0][0] = -matrix->m[0][0]; @@ -128,61 +104,56 @@ static void func_801B023C(void) { } SetRotMatrix(matrix); SetTransMatrix(matrix); - ThunderRenderDesc1.frameIndex = effect->AnimationFrame; - ThunderBufferPtr = func_800D4D90(&ThunderRenderDesc1, g_cDb->unk70, 0xC, ThunderBufferPtr); + g_ThunderRenderDesc1.frameIndex = effect->AnimationFrame; + g_ThunderBufferPtr = func_800D4D90(&g_ThunderRenderDesc1, g_cDb->unk70, 0xC, g_ThunderBufferPtr); if (D_80062D98 == 0) { - nextFrame = (u16)effect->AnimationFrame + 1; - effect->AnimationFrame = nextFrame; - if (nextFrame == 8) { + effect->AnimationFrame++; + if (effect->AnimationFrame == 8) { effect->StartFrame = -1; } } } -// Spawn callback for the bolt. On frame 0 it registers the two one-shot -// renderers; from frame 2 on it scatters a spark each frame, retiring at 16. static void ThunderSpawnBolt(void) { ThunderData* next; - ThunderData* effect = &D_80162978[D_8015169C]; - s16 nextFrame; + ThunderData* effect; + effect = &D_80162978[D_8015169C]; if (D_80062D98 == 0) { if (effect->AnimationFrame == 0) { next = &D_80162978[BattleEffectRegister(func_801B0180)]; next->Pos = effect->Pos; next->Pos.vy = 0; - next->unk1C = (u16)effect->unk1C; + next->unk1C = effect->unk1C; func_800D5774(effect->unk14); - // Re-tested after the call; the original re-loads the frame here - // and the match needs it. if (effect->AnimationFrame == 0) { next = &D_80162978[BattleEffectRegister(ThunderRenderModel)]; next->Pos = effect->Pos; - next->Scale = FIXED_ONE; + next->Scale = 4096; next->Pos.vy = 0; next->ScaleStep = 0x200; - next->unk1C = (u16)effect->unk1C; + next->unk1C = effect->unk1C; } } if (effect->AnimationFrame >= SPARK_START_FRAME) { next = &D_80162978[BattleEffectRegister(func_801B023C)]; - next->Pos.vx = ((u16)effect->Pos.vx + rand() % 1000) - 500; - next->Pos.vy = ((u16)effect->Pos.vy + rand() % 1000) - 500; - next->Pos.vz = ((u16)effect->Pos.vz + rand() % 1000) - 500; + next->Pos.vx = (effect->Pos.vx + rand() % 1000) - 500; + next->Pos.vy = (effect->Pos.vy + rand() % 1000) - 500; + next->Pos.vz = (effect->Pos.vz + rand() % 1000) - 500; next->unk1A = rand() & 3; - next->unk1C = (u16)effect->unk1C; + next->unk1C = effect->unk1C; } - nextFrame = (u16)effect->AnimationFrame + 1; - effect->AnimationFrame = nextFrame; - if (nextFrame == BOLT_LIFETIME) { + effect->AnimationFrame++; + if (effect->AnimationFrame == BOLT_LIFETIME) { effect->StartFrame = -1; } } } static void ThunderAttachToTarget(s32 target) { - ThunderData* effect = &D_80162978[BattleEffectRegister(ThunderSpawnBolt)]; + ThunderData* effect; + effect = &D_80162978[BattleEffectRegister(ThunderSpawnBolt)]; BattleGetPartPosition(target, D_801518E4[target].D_8015190F, &effect->Pos); effect->unk14 = target; effect->unk1C = -D_801518E4[target].unk12; @@ -190,9 +161,10 @@ static void ThunderAttachToTarget(s32 target) { } static void ThunderDoubleBufferFlip(void) { - ThunderData* data = &D_80162978[D_8015169C]; + ThunderData* data; - ThunderBufferPtr = &ThunderPrimBuffer[data->AnimationFrame]; + data = &D_80162978[D_8015169C]; + g_ThunderBufferPtr = &g_ThunderPrimBuffer[data->AnimationFrame]; data->AnimationFrame = data->AnimationFrame ^ 1; if (D_80162080 < 2) { data->StartFrame = -1; @@ -200,7 +172,7 @@ static void ThunderDoubleBufferFlip(void) { } static void ThunderMainSetup(s32 arg0, s32 arg1) { - func_800D2980(ThunderTexture, 0, 0, 0); + func_800D2980(g_ThunderTexture, 0, 0, 0); BattleEffectRegister(ThunderDoubleBufferFlip); MagicAnimationRegister(arg0, arg1, 2, ThunderAttachToTarget); }