Author: HobbitDur
Camera sequence
The camera sequences reuse the same byte-code VM as animation sequences (arithmetic C0-E5 and jumps E6-F3 behave identically), with camera-specific callbacks (CameraSeq_DispatchActionOpcode, driver BS_UpdateCameraSequence).
Two container forms (important)
Camera data ships in two container shapes, and they are easy to confuse because both open with a u16 count + u16 pointer table:
- Full camera blob with a byte-code setting — battle-stage
.Xfiles, R0WIN section 2, and spell/effect camera globals (MAG_*). An 8-byte header (pointerCount, relative offset to the camera setting = the byte-code VM sequence, relative offset to the camera animation collection, size), then the camera-setting byte-code (e.g.a0stg000.x:05 00 C3 10 E5 7F C1 00 CB 7F EA 06 …), then the collection. This is theBattleStageCameraDatalayout, and this is the only form that contains a byte-code camera sequence. Started viaBattle_PlayCameraAnimation. - Bare camera animation collection — per-entity cameras: monster files section 6, and character files (
dXc) section 5. There is no 8-byte header and no camera-setting byte-code: the section is a camera animation collection and starts directly atNumOfSets, ending with an EOF word equal to the section’s byte length. The engine plays these throughcameraWhenDoingAction, which passes the acting entity’s own collection pointer (command_queue->camera_animation_collection, at offset 0x2C of the command-queue struct, set at load time to the entity’s section-6/section-5 destination — seeBattle_isLoadSquallEtc@0x5078CA) toBS_GetCameraAnimationPointer. Verified:- Monsters — over all
c0m*.dat: 133 files are a valid bare collection (NumOfSetsusually 1 → 8 slots), 66 empty, 0 malformed. - Characters — all 17
dXcsection-5 blocks parse with the same parser:NumOfSets = 2(16 slots, 8 per set),EOF == section length, every pointer resolves. What looks like an 8-byte blob header (02 00 08 00 08 04 b8 06) is really the collection header itself:NumOfSets = 2,setPtr[0] = 0x08,setPtr[1] = 0x408,EOF = 0x6B8(= 1720 = length). Characters carry no camera byte-code — like monsters, their “which shot / how” logic lives outside the file (incameraWhenDoingAction).
- Monsters — over all
In other words: the opcodes and the C3/E5 tables below describe the byte-code half, which only the stage/R0WIN/spell blobs carry. A per-entity camera section (monster §6 or character §5) is purely the key-framed collection (positions, look-at, FOV, roll) that the 00/05 opcodes would play. The FF8UltimateEditor Camera tab edits these collections directly; the same collection parser handles both the monster (1-set) and character (2-set) cases.
Who starts a camera sequence
A camera data blob starts with a small header containing two relative offsets: one to the camera sequence (the VM byte-code interpreted here) and one to the camera animation collection (the actual key-framed camera motions that opcodes 00/05 play).
A camera sequence is started by calling Battle_PlayCameraAnimation with a pointer to such a blob, which registers both pointers (CURRENT_CAMERA_SETTING_ADDR / CURRENT_CAMERA_ANIMATION_COLLECTION_HEADER_ADDR). In practice this is called:
- at battle start by
BS_CameraInit(the battle stage’s idle camera), - by every spell/ability effect routine (the
MAG_xxxfunctions in magic.fs and the monster-attack effect inits) when the action’s visual effect begins — each effect embeds/points to its own camera data. For monster abilities the camera comes from this section.
A camera sequence can adapt its framing to the monster it is filming through the camera category (information & stats byte 246): that per-monster value is converted at battle start into the entity’s camera variable, which camera sequences read back via camera-C3 0x15 (see the table below).
From then on, the battle camera update (updateBattleCamera, every frame unless battle is paused) calls BS_UpdateCameraSequence, which interprets the sequence: it runs until opcode 01 (yield, one frame), and stops when it reaches 02 (or any unknown opcode), releasing the camera back to the default behaviour.
Camera opcodes < 0xC0:
| Opcode | Params | Description |
|---|---|---|
| 00 XX | 1 | Play camera animation XX from the current camera animation collection |
| 01 | 0 | Yield (pause for this frame, resume next frame) |
| 02 | 0 | End the camera sequence and clear the camera pointer |
| 03 | 0 | Capture the current camera pose as the idle/rest pose (copies the live position/look-at into the default-pose registers) + clear flag bit 15 |
| 04 XX | 1 | Start a camera oscillation (wobble) task with param XX |
| 05 XX | 1 | Play camera animation whose id is read through the camera special-value reader (XX) |
| 06 | 0 | Set camera flag bit 15 |
| 07 | 0 | Clear camera flag bit 15 |
| 08 | 0 | Reset the wobble factor (4096) and the camera flag |
| 09 XX | 1 | Nop (parameter skipped) |
| other | - | Reset wobble factor and end the sequence |
Camera C3-family special reads (CameraSeq_ReadSpecialVar_C3):
| Param | Value read |
|---|---|
| 0x00-0x07 | Camera animation local variables |
| 0x10 | Camera flag variable |
| 0x11 | Random value |
| 0x13 | Count of party members in a specific animation state |
| 0x15 | Target entity’s camera var: its camera category (information & stats byte 246), also overridable at runtime by entity sequences via E5 0x2B |
| 0x16 | Target’s animation status (with dead-flag fixup) |
| 0x17 | Battle task value |
| 0x18 | Attacker slot id |
| 0x19 | Target slot id |
| 0x1A | Number of affected targets |
| 0x1B | Count of party members with animation status 0 |
| 0x1C-0x22 | Random value modulo 2..8 |
| > 0x77 | Camera stack (same principle as the entity stack) |
Camera E5 writes: params < 8 write the camera animation local variables, 0x10 writes the camera flag variable, > 0x77 writes the camera stack.
Function addresses
FF8_EN.exe:
| Function | Address | Description |
|---|---|---|
CameraSeq_DispatchActionOpcode | 0x509810 | Camera-sequence opcode dispatcher (verified IDA function) |
BS_UpdateCameraSequence | 0x509610 | Per-frame driver for the active camera sequence (verified IDA function) |
Battle_PlayCameraAnimation | 0x5099A0 | Registers a camera data blob’s sequence/animation pointers and starts the sequence (verified IDA function) |
updateBattleCamera | 0x504060 | Per-frame battle camera update; calls BS_UpdateCameraSequence (verified IDA function) |
Camera pose capture routine (sub_503300 in IDA, unnamed) | 0x503300 | Copies the live camera position/look-at into the default-pose registers, used by opcode 03 (verified IDA function) |
| Default-pose registers | 0xB8B800 | Global data holding the idle/rest camera pose (Global variable/data, not a function) |
CameraSeq_ReadSpecialVar_C3 | 0x509640 | Camera C3-family special-variable reader (verified IDA function; also referenced as a3ParamAnimSeqForCamera — an older name — on the Information & stats page) |