Author: HobbitDur

Battle effects and true 30 fps

This page explains how FF8 animates its battle effects — GF summons and spells — and how the FFNx True30FPS work makes them run at a real 30 frames per second without changing how they play. It is written for readers who know FF8 well but do not read code: every mechanism is described by what it does on screen. The detailed technical pages are linked in each section.

  1. Battle effects and true 30 fps
    1. The battle runs 15 times per second
    2. How an effect is organised: tasks and queues
    3. The effect families
      1. Timeline family, creature-led
      2. Timeline family, particle-led
      3. Cinematic engine
      4. Actor engine (the heal effect library)
      5. Spells
    4. Shared building blocks
    5. How the effects are made 30 fps
      1. Step 1 — rewrite every effect in FFNx, exactly
      2. Step 2 — draw the in-between frame
      3. What stays at 15 steps per second on purpose
    6. Status (FFNx True30FPS)
    7. Related pages
    8. Reference (names & addresses)

The battle runs 15 times per second

Everything that happens in a battle — a GF moving, a particle flying, a damage number appearing — is computed in steps called ticks. The battle computes 15 ticks per second. Each tick the game moves every object one step forward and draws the result; the screen therefore shows 15 different pictures per second during effects.

FFNx can present 30 frames per second. The picture between two ticks — the in-between frame — has to come from somewhere. The two naive answers both fail:

Naive approach What it looks like
Run the battle 30 times per second Everything plays twice as fast: summons, damage timing, ATB, music sync break
Show each tick twice The effect still moves at 15 pictures per second: no gain

True 30 fps keeps the game’s logic at exactly 15 ticks per second — the same summon, the same timing, the same random numbers, the same damage — and draws each in-between frame at the exact halfway state of every moving object.


How an effect is organised: tasks and queues

An effect is not an animation file played by a generic player. It is code: every spell and every GF summon has its own small program in the game executable. That program is split into tasks.

A task is one living element of the effect: a spark, a lightning branch, the GF model, a camera move, a fade. It has a small block of memory (its position, speed, age, colour…) and a routine the game calls once per tick. The routine draws the element and moves it one step. When the element has finished — the spark has faded, the fade is complete — the routine says so and the task is removed.

Tasks live in queues (lists of tasks). An effect usually has a master task that owns several queues and runs them in a fixed order every tick; tasks can add new tasks (a bolt spawning a flash, a creature spawning debris).

flowchart TD
    A[Battle tick<br>15 per second] --> B[Battle frame routine]
    B --> C[Effect tick: run the effect's queue]
    C --> M[Master task]
    M --> Q1[Queue: creature / timeline]
    M --> Q2[Queue: particles]
    M --> Q3[Queue: lightning, debris...]
    Q1 --> T1[Task: GF model]
    Q2 --> T2[Task: spark]
    Q2 --> T3[Task: spark]
    Q3 --> T4[Task: bolt]
    T4 -. spawns .-> T5[Task: impact flash]
    B --> K[Camera update]
    K --> R[Screen drawn]

The same task-queue machine runs the whole battle (models, hit effects, the camera), which is why an effect can move the camera, hide a target, or wait for a file to finish loading.


The effect families

Almost 350 effect programs exist (spells, GF summons, limit breaks, enemy attacks). They were written by different people, but most fall into a handful of families that share the same structure. Knowing the family of an effect tells you how it animates.

flowchart LR
    subgraph TA[Timeline, creature-led]
      Q[Quezacotl] --- D[Diablos] --- C[Carbuncle] --- P[Pandemona] --- PH[Phoenix] --- MO[Moomba] --- GR[Griever]
    end
    subgraph TB[Timeline, particle-led]
      SH[Shiva] --- CA[Cactuar] --- OD[Odin] --- OR[Odin killed by Seifer] --- DT[Doomtrain] --- GI[Gilgamesh x4]
    end
    subgraph CE[Cinematic engine]
      IF[Ifrit] --- LE[Leviathan] --- BA[Bahamut] --- CE2[Cerberus] --- AL[Alexander] --- BR[Brothers] --- ED[Eden]
    end
    subgraph AE[Actor engine - heal effect library]
      SI[Siren] --- MM[MiniMog] --- TO[Tonberry] --- BK[Boko x4]
      HL[Cure, Protect, Double... spells]
    end
    subgraph SP[Spells]
      F[Fire family] --- T[Thunder family] --- I[Ice family] --- O[other spells]
    end
    TB -. camera .-> CS((Shared camera script))
    TA -. models .-> PP((Shared prim-model player))
    TB -. models .-> PP
    SP -. models .-> PP
    AE -. models .-> PP
Effect Effect id Family
Quezacotl — Thunder Storm 116 Timeline, creature-led
Diablos — Dark Messenger 325 Timeline, creature-led
Carbuncle — Ruby Light 278 Timeline, creature-led
Pandemona — Tornado Zone 291 Timeline, creature-led
Phoenix — Rebirth Flame 140 Timeline, creature-led
Moomba — Friendship 338 Timeline, creature-led
Griever (boss summon) 069 Timeline, creature-led
Shiva — Diamond Dust 185 Timeline, particle-led
Cactuar — 1000 Needles 199 Timeline, particle-led
Odin — Zantetsuken 187 Timeline, particle-led
Odin killed by Seifer 326 Timeline, particle-led
Doomtrain — Runaway Train 191 Timeline, particle-led
Gilgamesh (4 outcomes) 327-330 Timeline, particle-led
Ifrit — Hell Fire 201 Cinematic engine
Leviathan — Tsunami 006 Cinematic engine
Bahamut — Mega Flare 202 Cinematic engine
Cerberus — Counter Rockets 203 Cinematic engine
Alexander — Holy Judgment 204 Cinematic engine
Brothers — Brotherly Love 205 Cinematic engine
Eden — Eternal Breath 206 Cinematic engine
Siren — Silent Voice 095 Actor engine
MiniMog — Moogle Dance 096 Actor engine
Tonberry — Chef’s Knife 090 Actor engine
Boko — ChocoFire / Flare / Meteor / Bocle 097-100 Actor engine
Fire, Fira, Firaga 2, 142, 143 Spell (director + parts)
Thunder, Thundara, Thundaga 3, 102, 105 Spell (director + parts)
Blizzard, Blizzara, Blizzaga 144, 103, 104 Spell (director + parts)

Timeline family, creature-led

Quezacotl, Diablos, Carbuncle, Pandemona, Phoenix, Moomba and Griever share one design. The master task starts, and two ticks later it spawns the creature task. The creature task is the conductor: it keeps a tick counter and every event of the summon happens at a fixed tick number — the GF materialising, each camera cut, each lightning bolt, each file load, the damage. The master task then runs the creature, the creature’s parts, and the particle queues in a fixed order.

Quezacotl’s timeline shows the idea (tick numbers counted from the creature’s start):

flowchart LR
    A[1-76<br>intro model] --> B[86-256<br>Quezacotl materialises<br>animations switch at 86, 118, 238]
    B --> C[86-161<br>camera sweep<br>first bolts]
    C --> D[207-226<br>camera on the head]
    D --> E[257-276<br>camera orbit]
    E --> F[277-302<br>ascent]
    F --> G[303-318<br>debris]
    G --> H[319-354<br>final flash model]
    H --> I[345 damage<br>355 end]

When the timeline asks for a file (the next part of the summon’s graphics), it waits: the same tick repeats until the file is loaded. On a slow disk the summon therefore pauses for a moment without desynchronising anything.

The GF model is a normal battle model (skeleton + animations, the same format as monsters). A detail that matters for 30 fps: when the creature task draws the model, it uses the pose computed at the end of the previous tick — the model is always drawn one tick “late” relative to its animation counter.

Timeline family, particle-led

Shiva, Cactuar, Odin, Odin killed by Seifer, Doomtrain and Gilgamesh also run on a tick-counted timeline, but the timeline mostly spawns many small tasks — ice rings, needles, steam puffs, sword trails — into one shared sub-queue. Each small task animates itself for its own lifetime.

These effects support a draw-only mode: while the battle logic is paused, every task still draws but does not advance. Their camera is not moved by the effect itself but by a shared camera script (the code lives in Doomtrain’s program and is reused by all of them): a small list of camera moves — jump to a point, glide linearly, ease in/out, follow a curve, change zoom, change roll — played one after the other.

Gilgamesh is one program for four summons: the outcome (Zantetsuken, Masamune, Excalibur, Excalipoor) only selects which sword attack the timeline plays.

Cinematic engine

Ifrit, Leviathan, Bahamut, Cerberus, Alexander, the Brothers and Eden are driven by a script interpreter: the summon’s .00 / .01 files contain a byte-code program that places up to 128 “bones” (GF body parts, rocks, flames, cameras…), gives them speeds and accelerations, and chooses how each is drawn. Each tick the engine runs the script until it waits, moves every bone by its speed, then draws every bone through a table of draw routines (meshes, sprites, particle fountains, trails…).

The engine exists seven times in the executable — one compiled copy per GF — with small differences (Leviathan has lit meshes, Eden has its own screen effects, the Brothers attach battle models). The spells Water and Meteor use the same program layout. See GF cinematic engine and GF cinematic script.

Actor engine (the heal effect library)

Siren, MiniMog, Tonberry and the four Boko effects are built from a library of about 120 routines — emitters, particle actors, sprite strips, a creature actor with states, a camera stepper — that also powers the heal and support spells (Cure, Protect, Double…). The library is copied into each module, so every copy reads its own memory but behaves identically. A module adds its own director (what to spawn when) and a few specific routines (Tonberry walking with his lantern, ChocoMeteor’s meteor fall).

Spells

A spell usually has a root task (waits for the target, plays the sound), a director (spawns the parts at fixed ticks, one set per target) and part tasks: flames, sparks, embers, bolts, ice shards, rings. Most parts are one of three kinds: a sprite (a 2D picture, often a flipbook of a few frames), a prim model (a small 3D model with keyframed position/rotation/scale/colour) or a mesh built on the fly (Blizzard’s crystal, Thundaga’s lightning). See Magic effect anatomy and the case studies (Fire, Firaga, Blizzard, Cure).


Shared building blocks

Many families draw through the same few engine services. These matter for 30 fps because solving one of them solves it for every effect that uses it.

Building block What it does on screen Used by
Prim-model player Plays a small keyframed 3D model: each piece has a position, rotation, scale, colour, each either keyed on a frame or driven by a speed and an acceleration About 300 places: most GFs and many spells
Sprite sequence (flipbook) Draws a 2D picture; the picture number advances every tick Sparks, flames, smoke everywhere
Battle model animation Reads the next pose of a skeleton model and builds its bone matrices Every GF creature, every monster/character
Camera keyframe player Plays a camera “shot” from a camera file (eye, target, zoom, roll) Battle cameras, some effects
Shared camera script Plays a list of camera moves written for one summon Shiva, Cactuar, Odin, Doomtrain, Gilgamesh
Direct camera writes The effect writes the camera position itself every tick Timeline creature-led GFs, cinematic engine, actor engine
Screen flash / fade Brightens or darkens the whole screen by a level Almost every summon

How the effects are made 30 fps

Step 1 — rewrite every effect in FFNx, exactly

Because effects are code, the in-between frame cannot be produced by editing data. FFNx contains a native copy of every effect routine: each task routine of the executable is rewritten as C++ that does exactly the same thing — the same integer rounding, the same order of random numbers, the same calls to sound and damage. When a task runs, FFNx runs its copy instead of the original.

Exactness is not taken on trust. While an effect plays, every tick is computed twice, from the same starting memory:

flowchart TD
    S[Save the effect's whole memory] --> A[Run the ORIGINAL routines<br>record every sound, damage, file load]
    A --> P[Remember the result]
    P --> R[Restore the saved memory]
    R --> B[Run the FFNx copy<br>replay the recorded sounds, damage, loads<br>instead of doing them again]
    B --> C{Every byte identical?}
    C -->|yes| K[Keep the FFNx result]
    C -->|no| O[Put the original result back<br>and write the difference in FFNx.log]

So the game always continues exactly as the original would, sounds and damage happen once, and any imperfection in a rewritten routine shows up in the log instead of on screen. The same comparison is also run outside the game: a test program loads the executable, starts any effect by its id with fake targets, and compares the original and the copy on every tick until the effect ends.

The work is kept in two layers (two branches of the FFNx fork):

flowchart LR
    V[VanillaDecompiledForFutur30FPS<br>native copies of the effects<br>plays exactly like the original] --> T[True30FPS<br>same code<br>+ in-between frames]

The first layer alone changes nothing visible: it is the decompiled effect code, useful for any future work (60 fps, editing effects). The second layer adds the in-between frames on top, through a few marked hook points.

Step 2 — draw the in-between frame

With an exact copy of each effect, FFNx knows everything the effect knows: where each element is, how fast it moves, what the next tick will do. The in-between frame then only draws; it never moves the effect forward, never draws a random number, never plays a sound.

sequenceDiagram
    participant G as Game logic
    participant S as Screen
    G->>S: tick N (original timing): elements drawn at their tick-N state
    Note over S: in-between frame: each element drawn half way to its tick N+1 state
    G->>S: tick N+1: elements drawn at their tick N+1 state
    Note over S: in-between frame ...

The rule used everywhere is exact vanilla shapes: the in-between frame shows the true halfway point of the original motion, and anything that changes in jumps keeps its jumps.

What the element does In-between frame
Moves with a speed (sparks, embers, debris, falling ice) Half a step along the speed of this tick
Spins, grows, fades by a fixed amount per tick Half the amount
Follows a formula of the tick number (sine pulses, ring sizes) The formula at tick + ½
Keyframed prim model The model evaluated at the next frame on a copy, then the halfway point
Skeleton model (the GF, a target) The halfway pose between two ticks, joints turning the short way round
Flipbook sprite (flame art, bolt flicker) Holds its picture — a flipbook has no half picture
Random events (a new lightning fork, a spawned particle) Stay at 15 per second; what they spawn moves smoothly afterwards
Camera written by the effect The effect’s camera code is run for the next tick on a copy, the camera is shown half way; a cut stays a cut
Camera shots and the shared camera script Same: the next camera is computed on a copy and shown half way

Two examples:

Quezacotl’s lightning. A bolt is a “stepping leader” that grows a few random steps per tick. The growth stays in 15-per-second steps (the random shape is exactly the original’s), while the glow fades and the impact flashes move half way. The creature is drawn at the halfway pose — taking into account that the creature’s draw uses the previous tick’s pose.

Blizzard’s crystal. The shards fly out with a speed and spin; on the in-between frame each flying shard is advanced half a step and drawn with half its spin, so the burst is smooth, while shards that settle on the next tick hold their place.

What stays at 15 steps per second on purpose

  • Flipbook pictures (Fire’s flame art, Thunder’s bolt layers, sprite animations).
  • Random growth (lightning branches, spawn bursts).
  • Hard camera cuts and screen flashes that jump.
  • Anything the original draws only on some ticks (it is shown the same way).

These keep the original look; only continuous motion gains the extra frames.


Status (FFNx True30FPS)

Group Native copy In-between frames
Timeline GFs, creature-led (7) yes yes
Timeline GFs, particle-led (6 programs) yes yes
Cinematic engine GFs (7) yes yes
Actor engine GFs (Siren, MiniMog, Tonberry, Boko x4) yes yes
Fire, Thunder, Ice families yes yes
Other spells, limit breaks, enemy attacks in progress / not yet generic replay of the last tick

Effects without a native copy use the generic method: their in-between frame redraws the last tick’s picture with a generic position correction.



Reference (names & addresses)

FF8_EN.exe (Steam 2013, English 1.2).

Name Address Role
ExecuteTaskQueue 0x508420 Runs every task of a queue once, removes finished tasks
AddTaskToQueue 0x508360 Adds a task to a queue
BdLink battle frame 0x500900 One battle tick
Effect tick call 0x50093A The call in the battle frame that runs the effect’s queue
Magic_GetIDLoad 0x50AF20 Clears the magic buffer and loads an effect’s files
Effect setup table 0xC81774 Setup routine per effect id
Effect file loader table 0xC81DB8 File loader per effect id
Effect_PrimPlayer_Play 0x701970 Shared prim-model player
ProcessCameraAnimation 0x5035E0 Camera keyframe player
MAG066_CamScript_Task 0x63E9C0 Shared camera script
Battle_ReadAnimation 0x508F90 Reads the next pose of a battle model
BattleModel_BuildBoneMatricesFromPose 0x508C90 Builds a model’s bone matrices from its pose
BS_ComputeBonesWorldMatrices 0x5095B0 Places the bone matrices in the world
GF_116Quezacotl_SequenceTask 0x6C3760 Quezacotl master task
GF_116Quezacotl_CreatureTask 0x6C3940 Quezacotl timeline (creature task)
GF_199Cactuar_SequenceTick 0x5AA3A0 Cactuar master task
Ifrit sequence tick 0xB25DF0 Cinematic engine tick (Ifrit copy)
ActEng_MasterSequenceTick 0x739F40 Actor engine master task (Siren copy)
Battle camera eye / look-at 0xB8B7F0 / 0xB8B7F8 Camera position written by effects