Tekken 4’s character models, and editing them in place

The CHRTK4 format, the bit that ends every vertex block, and reshaping a fighter without adding a byte

Tekken 4 (PS2) fighters are CHRTK4 models: VIF packets of skinned vertices and textured strips that the PS2’s vector unit consumes directly. The format is decoded well enough to reshape a head, widen a body and build a 3D beard out of hidden hair meshes, all at the original size, and the results render in PCSX2. Adding new vertices or bones is still out of reach.

The models are where Tekken 4 is closest to the hardware. A fighter’s model isn’t a mesh in any portable sense: it’s a stream of VIF packets that the PS2 feeds straight to its vector unit (VU1), with skinning, culling and draw setup baked in. This entry covers what’s decoded, the rules that only showed up by breaking them, and how far that goes for editing.

The model file

Each fighter row has two model files (see Tekken 4’s moveset files, decoded and edited in game for rows): k3 is used in normal fights, and k2 only in the Tekken Force modes. A model file is a section table: a count, then (offset, size) pairs.

SectionHolds
0Textures and palettes, as GS upload packets
3The CHRTK4 model
6, 9, 16Structure known, roles inferred: collision volumes, a hair and cloth chain, an effect ring

The fight model holds two CHRTK4 chunks: the body with a standard head, and the same body with a denser head (10 head meshes instead of 7; 1,156 head vertices instead of 757), presumably for close-ups. Every edit has to go into both, or the fighter changes shape when the camera moves in.

The CHRTK4 format

Each chunk starts with the magic CHRTK4 and a version string, 0.003. Then come a bone table at +0x100 (32 bytes per bone: an id, a position and a rotation) and a skeleton of 111 + N nodes, where N is stored per file and each node’s parent is the node before it.

The geometry is a VIF1 stream made of two kinds of block:

  • Vertex blocks. Up to 144 vertices of four 128-bit words each: position (y up: the head sits around 530 to 800 units, the feet around −1200), normal, bone weights and bone indices. The block ends in an MSCAL that starts the vertex program.
  • Strip batches. A GIF register block (alpha, clamp, texture and test registers), a strip tag, then for each strip vertex a texture coordinate, a vertex index and a flag. They also end in an MSCAL.

Bones are indexed through a per-mesh palette of up to 20 matrices, so the same index means different bones in different meshes.

A standard fighter has 2,500 to 4,300 vertices, 3,000 to 6,200 triangles, 21 to 35 meshes, and 160 to 190 KB of textures.

The bit that ends a vertex block

Clear that bit and the vector unit runs on through all 16 KB of its memory. The whole 3D layer goes black during play, and PCSX2 logs “GS packet size exceeded VU memory size”. Close-up shots that use the other chunk can still look fine, which makes the fault easy to misplace. Any tool that writes vertex data has to preserve that one bit.

The rule was found from the vector-unit microcode and confirmed by its failure in game. The notes don’t record a separate control run.

A strip flag that hides geometry

Strips carry a flag from 0 to 4. The project’s first parser skipped flag-4 strips, which left a hole in the chest of every offline render. The game draws them: in the template fighter, 16 torso strips use flag 4.

Textures belong to the model

Fighter textures aren’t separate files. They’re GS uploads inside section 0: 8-bit images with 256-colour palettes, or 4-bit (low nibble first).

PCSX2’s texture replacement (dumping a texture and loading a replacement by hash) can’t reach all of them. Some parts of a fighter (a shirt front, trousers, hands) never reached the replacement hook in 448 dumps. So the project writes textures directly into the model file instead.

That needed three more tools:

  • Find a texture’s copies. A tool takes a texture dumped by PCSX2, finds every copy of it inside the model file and replaces them all. It must reject tiny 8×8 and 16×16 matches, which hit by coincidence and once overwrote a palette.
  • Map texels to the body. Each strip’s clamp register selects a sub-texture, so every texel can be mapped to the point on the body where it’s drawn. Clothing is then painted by where it sits on the body, not by its texture coordinates.
  • Probe builds. Textures are filled with numbered colours, letter grids or gradients, and the fighter is photographed in game, to identify which texture is which.

Editing in place

New vertices can’t be added yet (see below), so every change so far moves existing vertices, writing the file back at the same size.

  • Seams are duplicated. Where two meshes meet, each has its own copy of the seam vertices. Moving whole meshes opens holes. Edits have to be smooth functions of position, so the copies move together. Widening whole meshes, the first attempt, tore the head away from a mixed head-and-body mesh.
  • Hiding by transparency leaves holes. Making ears transparent (alpha 0) exposed a gap behind them. They were pulled in geometrically instead.
  • Normals must follow. When a region is scaled, its normals are transformed by the inverse transpose of the scaling, or the lighting stays the old shape.

With those rules, a custom fighter’s head was widened and rounded (a smooth scale of up to 1.42 across the cheeks, leaving the neck seam untouched), and the body made heavyset. Both render without tearing, including in a crouching win pose.

A beard from hair that wasn’t there

The template fighter’s hair is eight “hair card” meshes (728 vertices). The custom fighter didn’t need them, so they were rebuilt as a beard and moustache by moving their existing vertices into a shell around the jaw. Vertex counts, strip lengths and end-of-block bits stayed unchanged, so the file is the same size.

That ran into the rig problem described above. In some of these meshes palette slot 1 is the scalp, but in others it’s a shoulder or a swinging hair-chain bone. Beard columns placed in those meshes moved with the wrong bone. That explained a stray “flake” that floated off the head in some animations; an earlier guess, a stock hair card, was ruled out by an audit of the strips. A rebind that tried to attach the beard to face bones made it explode, and was dropped. The beard now uses only meshes whose slot 1 is the head.

Not yet possible

The format can be edited but not extended:

  • Adding vertices, meshes or bones needs the VU1 microcode’s expectations understood. It’s one of the project’s open research lanes.
  • Building a model from scratch needs the same.
  • The palette-slot-to-bone mapping needs a dump of vector-unit memory during a fight.

The evidence status is reproduced: the format readings were borne out by many in-game builds over several rounds of edits, but no single check that could have failed was designed for the format as a whole.

↑ ↓ move, Enter open Search with filters