Skip to content

Testing

Both tools read the same YAML this site is built from, so neither can drift away from the contract.

Fires valid packets at SuperCollider. This is what unblocks you completely: the entire SuperCollider side can be built and tested before the engine exists.

Terminal window
node scripts/mock.mjs --list
Terminal window
node scripts/mock.mjs --mode synth

It runs the startup sequence from the lifecycle page — handshake, asset root, the reference wall’s /wall/config bundle, the mix — then /mode/load with that mode’s init parameters in order, the mode’s opening state, /mode/start, and then touches, the mode’s streams at their declared rates, and its events. Every value it sends is inside the declared range and of the declared type, because it is generated from the declaration.

Terminal window
# One specific message, once.
node scripts/mock.mjs --send /wall/hold/touch
# Every message in the protocol, once each — a smoke test for your handlers.
node scripts/mock.mjs --all
# Somewhere other than localhost.
node scripts/mock.mjs --mode chase --host 192.168.1.40 --port 57120
# Stop after 30 seconds.
node scripts/mock.mjs --mode arcade --seconds 30

The reverse, and the tool that will actually catch integration bugs. It listens and logs every packet, checking each one against the schema.

Terminal window
node scripts/monitor.mjs
✓ /wall/hold/touch iff [42, 0.81, 12.334]
✗ /wall/hold/touch iif [42, 1, 12] arg 1 "velocity" expects f, got i
✗ /wall/hold/touch iff [60, 0.5, 1.0] arg 0 "holdId" = 60 is outside 0..59 on this wall
✗ /mode/chase/hit iiii [2, 12, 1, 3] arg 0 "zone" = 2 is outside 0..1 (zoneCount 2)
✗ /mode/synth/notes iiiii [0, 2, 4, 5, 7] "keys" carries 5 values, at most 4 allowed (steps)
✗ /wall/hold/hover not in protocol v0.2.0

It learns from the traffic: hold indices are checked against the last /wall/config it saw, zones against the zoneCount of the last /mode/load, and the synth pattern against that load’s steps. Until those arrive it uses the reference installation, and says so.

Hand this to the engine developer on day one. It turns “it doesn’t work” into a specific line with a specific argument, and it enforces the float policy automatically — which is the single most valuable thing it does, because a wrong-typed float is otherwise completely silent.

Terminal window
# Watch one group only.
node scripts/monitor.mjs --filter /wall
# Errors only, for leaving open in a corner during a long session.
node scripts/monitor.mjs --errors-only
# Listen where the engine listens, to watch SuperCollider's replies instead.
node scripts/monitor.mjs --port 9000

Run the monitor on a different port from SuperCollider and point the engine at it, or run it on 57120 while SuperCollider is stopped. Two processes cannot bind the same UDP port.

Terminal window
npm run validate

Structure, then the semantics a JSON Schema cannot express: type tag strings that disagree with their argument list, duplicate addresses, examples that do not conform to their own declared types, hold, zone or speaker indices without a ref, zone counts the wall cannot hold, an inconsistent reference wall, streamed arrays that would exceed the packet budget, ackedBy pointing at a message that does not exist.

It runs in CI on every push, and it is worth wiring to a pre-commit hook:

Terminal window
echo 'npm run validate --silent' > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
Terminal window
npm run diff -- --against-tag

Compares the working tree against the last git tag and classifies every change as major, minor or patch. CI fails a pull request that lands a breaking change without a major version bump, which is the mechanism that keeps the version number honest — and the changelog on this site is generated from the same output, so it is always accurate.

  1. Terminal one: node scripts/monitor.mjs --port 9000 — watch what SuperCollider sends back.
  2. Terminal two: SuperCollider, with the generated responders loaded.
  3. Terminal three: node scripts/mock.mjs --mode synth.

You now have the whole protocol exercised in both directions with no engine and no wall, and anything that goes wrong is named on a specific line.