Strats: how objects work
Every object in the game – a coin, a squirrel, a moving platform, a camera, the llama itself – is
driven by a small program called a strat (Argonaut's "strategy" scripts, inherited from Croc).
The programs are compiled bytecode for a virtual machine in groove.exe (FUN_0054d180). LlamaMod
can read every one of them, change their numbers, write new ones and move them between levels.
| Page | For |
|---|---|
| this page | how the VM works: units, instances, frames, variables, events, spawning, collision |
| Reading scripts | the editor's Script view and the assembly listing, with a worked example |
| The script language | writing new behaviour in LlamaMod's structured language |
| Customising strats | changing constants, cloning types, custom types in the editor and in mods |
| Example: the Alligator Arena | a complete minigame written in the script language, explained step by step |
| Example: the roller coaster | a new ride for the original coaster cart: how the ride works, track and waypoint data, speed patch |
Lab notes and tables: notes/strat-opcodes.md (all 434 opcodes with
semantics, confidence and Argonaut names), notes/strat-language.md
(complete syntax), notes/editor-object-schemas.md
(parameters of individual types). Machine-readable tables, used by the editor and the compiler:
ops.tsv,
props.tsv.
Code units and types
The strat code of a level sits at the end of its STPC chunk as a list of code units:
u32 nUnits, then per unit u32 nWords and the words. A unit is one compiled source file and can
contain several strats plus their name strings and data (for example Spider + RopeForSpider).
The village has 53 units, Lab 1 has 65.
A type is just an entry point into this code: a MAP record or a spawn instruction names a
byte offset in STPC, and the code there starts with debugName "Name". Code refers to models,
animation sets and other code by absolute STPC offsets – which is why STPC cannot be
rearranged without relocating every reference, and why each level carries its own copy of the
code of every type it uses.
LlamaMod's disassembler covers the code of all 34 level WADs (1461 units, 670 555 words) and assembles every unit back byte-identically.
One frame in the life of a strat
The VM is a stack machine that runs every live strat once per frame (30 frames per second):
- The strat continues where it stopped last frame.
- It executes instructions – push values, compute, read and write properties, play animations,
move – until it reaches a yield (
jmpYield,jzYield,callYield…). - At the yield its turn ends; the next strat runs. Next frame it continues after the yield.
So a typical object is "set up, then loop { …; yield }", and anything that takes time is a loop
that counts frames. Speeds and turn rates in scripts are per frame.
end / remove deletes the strat; return returns from a call.
Data a strat works with
| Kind | Syntax | What it is |
|---|---|---|
| variables | v0, v1, … (or an alias name) |
the strat's own variables (count = the type's nVars). The first n hold the instance's MAP parameters |
| properties | self.x, self.rotY, self.floorHeight, self.damageTaken … |
194 named engine fields of the strat: position, rotation, floor under it, distance to the player, animation state, alpha, collision damage … |
| other strats' properties | player.x, parent.v3, camera.…, target.…, boss.…, dialog.… |
the player, the strat that spawned this one (parent), the camera and a few registered special strats |
| globals | g0 … |
shared by all strats; the engine itself never reads most of them – their meaning comes from how scripts use them (e.g. g95 = event bits, g101–g105 = exit target and cell, g17–g20 = dive target) |
| numbers | 1.0, 90deg, 204r |
20.12 fixed point (4096 = 1.0 = one cell); angles 0x1000000 = 360°; r marks a raw integer |
Binary operators pop b (top) then a and push a op b; comparisons push 0/1; mul = a·b >> 12.
Events
A strat registers event handlers in its slots (onEvent, in the script language
on touched { … } / on event(type, param) { … }). When the event fires, the handler runs like a
subroutine. Known event types:
| Type | Fires when |
|---|---|
| 4 | touched: contact with another strat (pick-ups, enemies). Needs collision (collisionOn + collRadius) |
| 0x40 | timer |
| 0x80 / 0x100 | player distance |
Spawning
spawn<params, vars, slots, typeFlags, extra>(arguments…, unit:N+offset) creates a new strat from
code: the five inline values are exactly the type fields of a MAP record
(Objects), the stack
arguments become the child's parameters, and the child's parent is the spawner. Facts found
the hard way (by the arena):
- The child appears at the spawner's matrix position of the last frame, not at its current
x/y/z: afterself.x = …the spawner must yield before spawning. - A type that uses collision needs collision type flags (e.g. 0x28000 like Coin) and enough
extrawords for its collision bones – otherwise the game crashes in the heap allocator. - The strat pool must have room for every spawned strat (
poolExtra).
Collision and damage
On contact, each side's hitDamage is copied into the other's damageTaken, and both get event 4.
The llama's attacks set bits in its hitDamage: 256 kick (Action on the ground), 512 dive
(Action in the air; it homes in on the target an enemy publishes in g17–g19), 1024 charge.
The llama counts a contact as its hit if the other's hitDamage shares a bit with its own,
otherwise damageTaken & 127 == 1 costs it a heart and knocks it back. The original Alligator has
513 (= 512 | 1): only the dive hurts it, a kick gets you bitten.
References to level data
Some operands are indices into the level's own data and differ between levels; the cross-level import rewrites exactly these:
| Operand | Points to |
|---|---|
model:N, anim:N, unit:N[+off] (address, opcode 0xb2) |
a model, an animation set, code of a unit; negative = shared animations in DEFANIM.WAD |
sound:N (opcode 0xd7) |
a sample of the level's sound bank |
text:N (opcodes 0xf0, 0xfa) |
a dialogue line in LGPC |
tex:N (pushed before texture opcodes) |
a texture entry (shadows, sprites, particles) |
How the names were found
All 434 opcodes (0x000–0x1b1) were named by decompiling every handler with Ghidra; 252 of them match Argonaut's own names from the Croc 2 PlayStation demo's jump table and the Croc 1 decompilation (knowledge only, no code used – see the sources in notes/strat-opcodes.md). Confidence: 353 high, 79 medium, 2 low; high-confidence opcodes cover 97 % of all instructions in the levels. The names of rarely used opcodes and properties can still be wrong.