This folder holds The Broken Seal's field engine: the sprites of people and things, the 64 field objects and the scripts that move them, the player's control by the keys, the layered maps of towns and dungeons, the world map in perspective, and what the ground is like under a point.
Scenes (in games/tbs/src/scene) drive all of it through the engine's services.
See also: Field objects, which runs a field scene on this engine; Scenes, which drive it; and Maps, the data its map code loads.
| File | What it holds |
|---|---|
| field.c | the guide; it includes field_internal.h, then the parts under field/ in ROM order |
| field_internal.h | the includes and the few names more than one part uses; it places no bytes |
| field/*.c | the field object's code in 14 parts; never compiled on their own |
| update.c | Object_UpdateAll, an ARM update of the first eight objects, made to run from RAM; only the unused Object_RunUpdateAllFromHeap runs it |
| vertices.c | Transform_UpdateVertices, the ARM routine that turns the world map's ground plane into BG2's per-scanline affine values |
| object_script.c, operand.c | the object scripts the engine starts itself, the command table, and the work value handlers |
| facing.c | the facing tables, then the parts' small constant tables: the tile dissolve order, the fixed camera's matrix, the float wave, the idle script, the world map's pad headings, footprint scripts and turn rates |
| curve_data.c, mapdata.c | the terrain height tables, and the height function table with the world map's terrain behaviour and steps |
| world_map_resources.c, griddata.c | the world map's graphics sets, and the sprite viewer's scale |
| lucky_dice.c | the Lucky Dice minigame, with the camera defaults and 8.8 helpers it uses |
| layered.tsv, remap*.tsv, scenes.s | the layered map table, the byte remap table and the scene table |
The headers are in games/tbs/include/unit/field/, one for each file; the field object's functions are declared in unit/field/field.h. The objects' link order is their rows of games/tbs/objects.tsv. In the ROM field.c is one object; its split into parts is ours, cut where one family ends and the next begins, and head -3 field/*.c says what each part holds.
The same locals carry the same names: object and interpreter for the object a command runs on, sprite and layer for a sprite and its layer, work or mapWork for the map's work (gMapWork[0]), and perspective for the world map's.
The folder's functions keep the prefixes of the engine family they belong to, as other folders call them: Sprite_ and Render_ draw sprites, Animation_, AnimationObjects_, ResourceMetadata_, ResourceObject_ and ResourceSlot_ keep sprites and layers, ObjectSystem_, ObjectDispatch_, FieldObject_ and Object_ keep objects, Script_ are object script commands, Field_ the configured keys, Map_ and Tilemap_ the layered maps and cells (TileMap_DrawRowsFromRam after the routine it runs), WorldMap_ the world map, GetWorldMap and CheckWorldMap the ground under a point on it, MapAnimation_, DisplayBlend_ and PaletteQueue_ the timed map effects, Curve_ the ground height functions, Ui_ the debug sprite viewer and LuckyDice_ the minigame. A name other folders or The Lost Age's twin also use changes only in a rename across folders, even where it no longer says what its code does.
A part's own types, macros and static helpers start with its part's or family's name (ObjectCamera, LayeredScene, WorldMap), and its macros are undefined at its end.
The spellings some sites keep to match, and the comment markers, are in Matching.
An object's script is an array of s32 words, and its cursor is a word index. Each frame, for each live object, the update runs the object's hook, then counts down its wait if it has one, and otherwise runs commands until one of them yields. A command is a function in gScriptObject_CommandTable that takes the object and returns 1 or 0:
So a command's return value decides time, and its cursor decides place; a command that returned 0 without moving would be run again forever, which is what waiting is.
Words above 63 are not commands, and the loop steps over them. That is what lets a label sit in the stream: SC_LABEL(n) is a word with its top bit set, which execution skips, and a jump's operand is SC_TO(n), the same word with bit 30 also set. Script_FindLabel clears bit 30 from the operand and searches the first 1,024 words for the result, so it finds the label and never the jump's own operand. Jumps are found by searching on every jump, not by offsets, so a script needs no assembler to fix up its branches.
SC_END does not free the object: it points the script at gScript_MainScript, a lone SC_END, and returns 0, so the object idles, re-running that one command each frame until something gives it a new script. SC_FREE frees it.
The loop that runs scripts is Object_UpdateAllThumb (field/object_motion.c), for all 64 objects, with the field's extras (free flight, terrain, floating and solid objects). Object_UpdateAll (update.c) is an ARM copy of the same protocol for the first eight objects, written to be copied to RAM; the only routine that copies and runs it, Object_RunUpdateAllFromHeap, is called by nothing, so the ROM keeps it unused.
A field object is 0x70 bytes, and five structs describe it: ObjectRuntime (include/object_runtime.h), DispatchObject (include/object_dispatch.h), ScriptInterpreter (include/field/script.h), ScriptMotionObject (include/script_motion.h) and FieldActor (include/field_obj.h). Each was written from the code that reads it, and each names only the fields that code uses. They agree on most of the block (the script at 0, the position at 8, the velocity at 0x24, the target at 0x38) and disagree where their readers do:
They are kept apart, rather than merged into one struct, because the views are also what the code matches with. agscc addresses a field differently by its declared type and by whether it is reached through a union (the FieldActor single or group sprite union moves a load, as Matching records under Scenes), so one merged struct would change the bytes of the sites that each view now matches. Even gObjectSlots is declared as ObjectRuntime * in include/field/field.h and as ScriptMotionObject * in update.c. XXX: Camelot surely had one struct; merging the five waits until every field has one name and one type that every site matches with.
A town or dungeon map is three background layers that share one grid of cells. The grid is 128 by 128 cells, 64 KB, decoded into Ram_MapCellBuffer; each layer is a window into it, starting at the cell its header gives (Map_LoadLayeredScene sets layers[n].cells to the cell at half the header's 8-pixel tile position). Layer 0 shows on BG3, layer 1 on BG2 and layer 2 on BG1, leaving BG0 to the windows; each has its own parallax, drift and wrap. A layer's priority byte in the header is also its switch: Map_LoadLayeredScene turns a layer on only when its priority is not zero, so map layers use priorities 1 to 3. The data, its format and how the build makes it are in Maps.
The command names (SC_WAIT, SC_SYNC and the rest) are the macros of objcmd.h, named from what each handler does; the struct views from the fields each family reads; the map's words from the loader. None of them is recovered from the original.
Still unknown:
Source: src/field