WINDAGE ENGINE v1.0.0
Contents

Docs / Checking your game

Scripted walks

A scripted walk plays the game from a line of keys and seconds and logs where the feet were at the end of each segment. Unlike "it felt right on the stairs", anyone can repeat it, and Tools/walks.py turns the walks into a check that fails when a change moves where they end.

This is the sample's tooling, not engine API. The script reader, the log line and Tools/walks.py belong to WindageSample. The engine provides the pieces they are built from, listed at the end, and a game that wants the same check builds its own reader on top of them.

The script

Set WINDAGE_SAMPLE_WALK to segments separated by spaces. Each segment is keys:seconds:

WINDAGE_SAMPLE_LEVEL=1 \
WINDAGE_SAMPLE_WALK="wait:0.5 d:0.75 wait:0.5 w:1.0 wait:0.6" swift run WindageSample
SegmentWhat it does
w:1.0holds W for one second, then releases it
w+shift:0.8holds several keys at once, joined with +
space:0.05any name KeyCode(named:) knows: letters, space, shift, up, down, left, right
wait:1.2holds nothing
click:0.1starts with a mouse click
yaw:90, pitch:-40turns the view to that many degrees, and takes no time
mousex:500, mousey:-80moves the mouse that many points, through the real mouse path
pad.south:0.1presses a controller element
pad.lookRight@0.2:1holds a stick a fifth of the way over

The pad elements are south, east, west, north, leftShoulder, rightShoulder, leftStick, rightStick and menu. The d-pad is up, down, left and right, the look stick is lookUp, lookDown, lookLeft and lookRight, and the move stick is moveUp, moveDown, moveLeft and moveRight. A script that names any of them gets a virtual pad for its whole run.

The keys go through the game, not around it: the same keyPressed a keyboard calls, so the bindings, the settings screen and the editor all see them.

Segments are whole simulation steps. Each boundary is measured from the start of the script and lands on the nearest step, so roundings can't add up over a long script. A walk that must hold at several step rates uses whole steps of the slowest one, which means multiples of 1/30 s.

The log

When a segment ends, the sample prints where the feet are, the highest they got during the segment, whether they're on the ground, and how high the eye is above them. Indented lines after it describe the same segment. Here is the walk above going up the ruins' stairs:

WindageSample: walk 'w:1.0' → feet (4.50, 1.20, -0.00), highest 1.20, on the ground, eye 1.69 m up
WindageSample:     4 footsteps on dirt and stone and wood
WindageSample:     lift top 0.75 m, feet at 1.20 m
WindageSample:     head bob 2.7 cm up and down, 1.7 cm side to side

The indented lines can also report a landing's impact speed and how far the eye dipped. A segment that turned the view prints look '…' → view turned …° right, …° up.

Checking every walk

Tools/walks.json lists the walks the README and the handoff document, each with what it measured. Tools/walks.py runs them all:

python3 Tools/walks.py            # every walk
python3 Tools/walks.py piston     # the walks whose names contain "piston"
python3 Tools/walks.py --record   # print what each walk gives now; writes nothing
python3 Tools/walks.py --serial   # one at a time

Each walk runs as a check (WINDAGE_SAMPLE_CHECK=<name>), with no window, no keyboard, no sound and its own saves, so it runs while you work and a player's rebinding can't make it fail. Four run at a time. A result depends on the step count, not the clock, so running them together only changes how long the run takes.

A walk entry names its level and script, and each item under expect says which segment it checks (after, with an optional occurrence) and what to compare:

2 cm is wide enough for a real change to hide in, so --record is there for looking. A recorded number changes only on purpose, with its reason in the CHANGELOG.

like reuses another walk's script and expectations. "crate behind the splash" uses it to check that the engine's splash takes nothing from the walk underneath. rates runs a walk at several step rates (WINDAGE_SAMPLE_RATE). The course walk runs at 30, 60, 120 and 240 steps a second and must give the same numbers at each.

The same approach in your game

The engine does not ship a script reader. These are the parts the sample's is built on, and each one is public:

The rest is your game's: parse the segments, press and release on the step boundaries, and print what the body did. The sample's ScriptedWalk, in Sources/WindageSample/SampleGame.swift, is one way to write it.