Files

7.2 KiB
Raw Permalink Blame History

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):

  1. The strat continues where it stopped last frame.
  2. It executes instructions – push values, compute, read and write properties, play animations, move – until it reaches a yield (jmpYield, jzYield, callYield …).
  3. 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: after self.x = … the spawner must yield before spawning.
  • A type that uses collision needs collision type flags (e.g. 0x28000 like Coin) and enough extra words 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.