Characters

The five 3D meshes the engine keeps resident across every field scene - Vahn, Noa, Gala, and two smaller auxiliary actors - straight from your own disc image. The active party also runs an equipment-conditional mesh swap: a single per-character byte in the save record flips the visible weapon-bearing group between two pre-baked descriptors. Click any character to spin its model and try the toggle. Nothing leaves your machine.

Open a disc image

No file loaded. Provide a .bin Mode2/2352 image (or a raw PROT.DAT).

How it works

PROT entry 0874 (filename label befect_data; the retail dev name is player_data / player.lzs - the +2 label shift) carries a five-TMD pack at the head of its first LZS section. The engine installs every body at DAT_8007C018[0..=4] at scene entry - a contiguous global pool the field renderer, the actor allocators, and the world-map dispatcher all read. The same five meshes are reused across every town and dungeon (and the world map), so they're loaded once and survive every transition.

Slots 0/1/2 are the active-party characters (Vahn / Noa / Gala). Each ships with twelve group descriptors on disc, but the engine immediately caps the live group_count to ten - the last two are equipment-conditional templates. Every frame, FUN_8001EBEC reads a single byte from each character's save record and overwrites one visible group with either template. That single bit is the entire equipment-swap mechanism for the mesh; weapon identity is conveyed through the texture atlas, not by adding new geometry.

asset character-pack extracted/PROT/0874_befect_data.BIN

The equipment swap

The toggle in the viewer mirrors what the engine does at runtime: it copies one of two pre-baked 28-byte group descriptors over a visible group in the TMD. Group 10 (TMD+0x124) is the variant the engine picks for a non-zero equipment byte; group 11 (TMD+0x140) is the variant for a zero byte. The patched-group index is character-specific - Vahn patches group 0, Noa patches group 3, Gala patches group 5 - because each character has its primary visible-equipment object in a different mesh slot.

asset character-pack … --slot 0 --equip 1 --out vahn_equipped.tmd

The battle form - assembled per equipment, with PROT 1204 as the default-gear sibling

The party's in-battle meshes are higher-fidelity than the field pack - and a real battle builds them: at battle setup the engine assembles each character's TMD from their player battle file (data\battle\PLAYER<n>), picking one mesh section per equipment slot by the equipped item ids, and installs the merged result into DAT_8007C018[0..=2]. PROT 1204 (other5), shown in the viewer above, is the pre-assembled default-equipment copy of the same five fighters - the pack the Baka Fighter minigame loads directly. See character mesh packs for the full assembly chain.

Provenance (byte-verified). In a full-party battle save, DAT_8007C018[0] points at the assembler's output blob exactly (nobj=17, bone-tag bytes [0..14, 200, 201], attach bones [5, 8]), and every one of Vahn's 17 object vertex pools byte-matches a section of his player file, equipment-selectively - the Hunter Clothes body, the Survival Knife (bone object + visual extra), the Ra-Seru Meta piece, and the defaults for the bare slots. The five equipped-variant objects appear nowhere in PROT 1204; the other 12 default objects are byte-shared with it, which is what an earlier "battle renders PROT 1204 (12/17 match)" attribution was actually seeing. The runtime nobj +2 over the 1204 forms (15/16/15 → 17/18/17) is the weapon + Ra-Seru sections' extra objects - not FUN_8001EBEC, which only toggles a pose transform. (Two earlier reads are superseded in turn: "battle reuses the field pack", then "battle renders PROT 1204 directly".)

The Baka Fighter fist-fight minigame loads PROT 1204 directly - it lets you play as Vahn/Noa/Gala with default gear (overlay_baka_fighter loads data\field\other5.lzs + PROT 1205/1206), which is why captures taken during that minigame show the pack resident.

SlotField form (PROT 0874 §0)Battle form (PROT 1204)
0 - Vahn nobj=12, 13 220 bytesnobj=15, 33 516 bytes
1 - Noa nobj=12, 13 800 bytesnobj=16, 33 636 bytes
2 - Gala nobj=12, 11 656 bytesnobj=15, 24 780 bytes
3 - Extra nobj=3, 6 488 bytesnobj=20, 27 036 bytes
4 - Extra nobj=2, 1 048 bytesnobj=15, 33 340 bytes

The pack is a flat streaming-format container with asset type 0x09 (TMD2) chunks - distinct from the field form's parse_player_lzs-shaped pack. The 7 atlases are 256×256 4bpp TIMs with 256-color sub-CLUTs at stride 0x8224 (bundled CLUTs at rows 490..495, 497) - the face / hair / outfit textures the renderer samples. These same meshes + animations are also surfaced on the minigames page (where this pack drives the Baka Fighter roster).

The true battle palette - decoded from the disc

The bundled CLUTs in PROT 1204 are the pack's authoring palette - what the Baka Fighter minigame renders with directly. A real turn-based battle does two things at load that the minigame doesn't: it relocates every party prim's texpage + CBA into a packed per-slot VRAM band (Vahn → row 481, Noa → row 482, Gala → row 483; pinned by reading the runtime flags=1 TMD back out of DAT_8007C018[0..2] in a clean battle save), and it uploads a different palette there. That battle palette is a genuinely distinct asset: 146 of Vahn's 256 battle colours appear in no CLUT the pack ships. The band's pixels are likewise uploaded from the player battle file's per-section texture pools - each equipped section carries an upload block placed by a static per-slot rect table (FUN_80052FA0 → the LoadImage front-end FUN_80053B9C), tiling each character's 128×256 band exactly; the 1204 atlas is the default-gear fallback content.

That palette is on the disc - it lives inside the character's player battle file (the data the data\battle\PLAYERn path resolves to - Vahn is extraction PROT 0863, raw TOC 0x361 = PLAYER1; the extraction filename label is edstati3, the +2 label shift), embedded in a small LZS record set that the loader decodes and STP-copies to VRAM. Running that decode + assembly as a unit (FUN_80052FA0: decode record[0] + 5 scattered sub-records into one buffer, read the CLUT structs at the header offsets, set bit 15 on non-zero colours) reproduces the live battle VRAM byte-exact. Vahn's three bands - base 0x00 (record[0]'s CLUT B), 0x40 (sub#0), 0x70 (sub#4) - are decoded straight from your disc and overlaid onto the rows his mesh samples, so the Battle form above shows his real blue-haired colours. Every earlier byte-search missed only because it used the bit-15-set runtime form of the colours instead of the disc's bit-15-clear form. Noa (PROT 0864) and Gala (PROT 0865) are wired too: a character carries one CLUT per equipment id plus an id == 0 separator (the unequipped default), and picking the separator default for each band the mesh samples reproduces the palette - Noa ~98%, Gala 100% vs a full-party capture, every sampled column covered. All three player files load by char + 0x360FUN_8003e8a8toc[idx+2] (a sector offset into PROT.DAT): Vahn, Noa, Gala, Terra sit contiguously, and the extractor's 0863..0866 entries begin at those offsets exactly; the historical 0861 reading matched the same bytes through an over-read window across two 1-sector stub entries (0861/0862) that reaches Vahn's file 0x1000 in. The one gotcha Gala exposed: the sub-record section base is rec0 + align_up(recbase, 0x2000) - a 0x1000 alignment happens to match for Vahn/Noa but lands Gala's subs on a zero-padded block.

asset battle-char-pack extracted/PROT/1204_other5.BIN
asset battle-char-pack extracted/PROT/1204_other5.BIN --slot 0 --out-tmd vahn_battle.tmd
asset battle-char-pack extracted/PROT/1204_other5.BIN --atlas 0 --out-tim vahn_atlas.tim

What's wired

The status of each rendering input behind the viewer above:

  • Field-form texture atlas. Wired (textured). The atlas lives in PROT 0874 section 2, the third LZS section of the player.lzs container - extraction 0874 is the retail player_data file; the extraction file named player_data (0876, a VAB + an empty TIM_LIST + a SEQ trailer) is the +2 filename shift that first sent the texture hunt to the wrong entry. FUN_8001E890 loads that container (disc index 0x36c) and uploads §2 to VRAM via FUN_800198e0: three atlas pages at texpage (832, 256) (Vahn / Noa / Gala, tiling cols 832..892) with per-character CLUTs on row 478 (cols 0..63 / 64..127 / 128..191). The CLUT goes up as a flat horizontal strip (w·h colours at one row), not the declared w×h rectangle - which is exactly why each character samples several CBA columns of one row. Byte-exact against a live field VRAM dump; the Field form above renders against this VRAM through the same paletted pipeline the Battle form uses. The field mesh is a hybrid: only about a third of its prims are textured (the face / eyes / skin), and the rest - hair, vest, boots - are untextured flat / gouraud prims that carry per-vertex RGB in the TMD (a colour block before the vertex indices: one RGB for flat prims, one per corner for gouraud). The shader uses those colours for the untextured body parts (a u_use_flat_colors-gated branch) instead of sampling VRAM, so the whole figure renders in its real colours - blue-haired Vahn, auburn green-eyed Noa, orange-haired Gala - not just a textured face on an empty body.
  • Battle-form rendering. Wired (geometry + textures + assembly + Vahn's true palette). The seven trailing TIMs in PROT 1204 upload into the same 1024×512 paletted VRAM the world-overview viewer uses; the fragment shader does the per-prim CBA / TSB lookup the retail GPU does, and the Battle form overlays Vahn's true per-battle palette (decoded from his player battle file, extraction PROT 0863) onto the rows his mesh samples - see the battle-palette section. The party mesh is assembled exactly as the retail engine does it (each object's local origin placed at its joint via the ANM per-bone (T, R); see the Animation panel and note below). Orbit / zoom to inspect.
  • All three party palettes are wired. Vahn (PROT 0863, byte-exact), Noa (PROT 0864, ~98%) and Gala (PROT 0865, 100%) are decoded from your disc. The player files are resolved by char + 0x360FUN_8003e8a8 reading toc[idx+2] as a sector offset into PROT.DAT (the retail ISO9660 open FUN_800608f0 is a trap stub on this build). Terra (PROT 0866) would follow the same path if a fourth battle slot ever needed it.
  • Animations. Wired. Live in the ANM container and feed through the per-actor anim record at actor[+0x4C]. The disc source is pinned: per-scene bundles for the field rig, and PROT 1203 (other5) for the battle rig, whose 30 records are organised in per-character banks (15-bone Vahn / 16-bone Noa / 15-bone Gala). The Animation panel steps records and plays frames; each frame poses through the exact retail per-bone pipeline (R·v + T, FUN_8001B964 / FUN_8001BE80), so the rig stays assembled across the whole clip. Frame 0 of each bank's idle record is the combat-stance rest pose.
Note on assembly. Each character is a set of object-local TMD pieces (head, torso, limbs), not a single pre-assembled mesh. The retail engine places each piece by rotating its object-local vertices about their own origin and translating to the joint position - v_world = Rbone·v + Tbone, a flat per-object transform with no skeleton hierarchy (FUN_8001B964 loads the actor matrix, FUN_8001BE80 decodes the bone (T, R) and pushes T through the GTE's MVMVA, then FUN_8002735C draws the piece). The (T, R) values come from the ANM stream; frame 0 of the idle clip is the rest pose. This viewer applies that exact pipeline, so the pieces connect at their joints the way they do in-game. Both forms are textured: the field rig samples its atlas from PROT 0874 §2, the battle rig from PROT 1204.