units
The unit table — the storage the whole battle runs on, and the only thing that writes it.
game.sim.units
The unit table — the storage the whole battle runs on, and the only thing that writes it.
Every soldier and every alien the field can hold lives as two vec4 records in
one flat run: a Luau byte buffer the simulation writes at native speed, uploaded
whole to the GPU buffer game.shared names (kessrel.units), which the armies
slice reads in its crowd compute pass.
unit i owns 32 bytes at i * 32:
+0 x +4 y +8 z +12 heading (metres, radians about +Y)
+16 kind +20 state +24 stateTime +28 seed (K.UNIT_KIND, K.UNIT_STATE, seconds, 0..1)
A slot whose state is 0 is empty. state 4 is dying and lasts about 1.2
seconds before the slot frees.
local U = require("game.sim.units")
U.ensure() -- allocate (or re-attach to) the GPU buffer
U.write(i, x, 0, z, h, kind, state, 0, seed)
U.upload() -- 339 KB to the card, about 0.06 ms
What it owns
The storage. U.mem is the byte buffer; the hot per-unit pass in
Battle.component writes straight into it with buffer.writef32 rather than
through a call per field. U.write, U.setState, U.clearRange, U.read and
U.countLive are the cold paths — spawning, killing, resetting, and reading
back for a test.
The block allocator. An alien pack owns exactly one fixed-size block inside
its caste's index range, so its units are always contiguous and a pack's whole
population can be written in one loop. U.allocBlock(kind) takes one,
U.freeBlock(kind, first) gives it back cleared, U.freeBlocks(kind) says how
many are left — which is what stops a wave from releasing more than the range can
hold. Skitters get 160 blocks of 40, brutes 120 of 10, titans 8 of 1.
The offset tables. U.scatter(kind, variant) is one of a caste's fixed pack
arrangements — a jittered lattice, several per caste so a hundred packs are not
a hundred copies of one clump — and U.formation() is the regiment's slot order,
front rank first. Both are computed once at load, so a unit's place inside its
group is never stored per unit and never recomputed.
What it does not do
It holds no rules. Damage, morale, targeting and waves are Battle.component's;
this module answers only where the bytes are and which slots are free.
Scoped to this part · feeds back into the world's score.