documentation for level builders: docs index, step-by-step guide, file format reference (wad, geometry, objects, textures, audio, testlevel chapters, engine limits), strat vm and script language with the arena as worked example, tools overview, developer notes; readme rewritten as project front page with screenshots; review notes moved to notes/dev
This commit is contained in:
1 parent
00b2880d67
commit
624718b893
41 files changed
+2175
-259
No files matched your search
@@ -0,0 +1,120 @@
|
||||
# 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](reading-scripts.md) | the editor's Script view and the assembly listing, with a worked example |
|
||||
| [The script language](script-language.md) | writing new behaviour in LlamaMod's structured language |
|
||||
| [Customising strats](custom-types.md) | changing constants, cloning types, custom types in the editor and in mods |
|
||||
| [Example: the Alligator Arena](arena.md) | a complete minigame written in the script language, explained step by step |
|
||||
| [Example: the roller coaster](roller-coaster.md) | coming soon |
|
||||
|
||||
Lab notes and tables: [notes/strat-opcodes.md](../../notes/strat-opcodes.md) (all 434 opcodes with
|
||||
semantics, confidence and Argonaut names), [notes/strat-language.md](../../notes/strat-language.md)
|
||||
(complete syntax), [notes/editor-object-schemas.md](../../notes/editor-object-schemas.md)
|
||||
(parameters of individual types). Machine-readable tables, used by the editor and the compiler:
|
||||
[`ops.tsv`](../../src/main/resources/com/eliasmueller/llamamod/format/strat/ops.tsv),
|
||||
[`props.tsv`](../../src/main/resources/com/eliasmueller/llamamod/format/strat/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](../reference/objects.md#strat-record-58-bytes-certain-unless-noted)), 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](../../notes/strat-opcodes.md#sources)). 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.
|
||||
Reference in new issue
Block a user