Skip to content

Deterministic harness tools over MCP: queued events, pause/step clock, screenshots - #6

Merged
mtdowling merged 1 commit into
tecs-dev:mainfrom
albanmgicquel:harness-tools
Oct 12, 2026
Merged

mtdowling merged 1 commit into
tecs-dev:mainfrom
albanmgicquel:harness-tools

Conversation

@albanmgicquel

Copy link
Copy Markdown
Collaborator

External harnesses driving a Tecs game through the MCP server had to work around three gaps. This closes them.

Queued synthetic input

send_event used to fold its event into input state as soon as the tool ran, which is between frames. The next frame's beginFrame then cleared the edges, so an injected key was down but never pressed. Events are now queued on the application and delivered on the next frame that runs, after the host's own batch for that turn. They go through the same path a platform event does (window state, Input, world observers, onEvent, quit). New API: Application:queueEvent(event).

Pause and deterministic stepping

  • clock_pause: held turns run no systems and take in no input, and frame/elapsed stand still. Host events are kept for the next frame that runs (only the latest 1024 are kept); a quit still stops the app. Rendering continues.
  • clock_step {frames?, dt?}: grants frames to a paused clock, one per host turn, each with a fixed dt (default 1/60 s) instead of wall-clock time. The same script gives the same simulated time on every run and every host.
  • clock_resume: back to the host's delta; drops grants that haven't run.
  • clock_info: {paused, frame, elapsed, pendingSteps, pendingEvents, stepDt, parked, settled}. Wait for settled before reading the world.

New API: Application:pause(), resume(), step(frames, dt), pendingSteps(), stepDelta(), pendingEvents(), plus a read-only paused field.

Screenshots

screenshot {name? | path?} requests a capture of the next rendered frame and returns at once with an id and the path it will write. The server writes the PNG once the host has read the frame back (windowed or --offscreen). screenshot_info {id} reports ready, saved, the size, or the failure. This reuses tecs.gfx.screenshot and brings back the historical screenshot tool name and its name key.

Parked frames

send_event, the clock_* tools and the screenshot tools touch only application state, so they now answer while a frame is parked (for example, waiting on an image upload). clock_info reports parked, so a harness can wait instead of retrying refused world tools.

Compatibility

Six new tool names and their argument keys are pinned in tests/tecs/compatibilitytest.nupp. Existing names and keys are unchanged. Behaviour change: an event sent with send_event now arrives on the next frame instead of right away. Its old handling of a quit or close request is replaced by the normal quit path, so the quit takes effect on the next turn.

Tests

  • New application cases cover queued edges, ordering behind the host batch, hold/step/resume with a fixed dt, quit while paused, the held-event bound, and invalid grants.
  • New MCP cases cover a send_event key being pressed on the next frame, the clock tools, answers while parked, and screenshot write and failure paths.
  • Existing send_event tests are updated for delivery on the next frame.

nupp check --strict, the format check, nupp test (1007 tests in 86 suites), nupp task test-tools, cargo fmt/clippy -D warnings/test over the workspace, and the headless example smokes pass. I could not run the docs render locally (the lunamark rock is missing from my from-source Nupp toolchain).

End to end, against an --offscreen host through the MCP server: frames stay put while paused; a sent Space is pressed on exactly the stepped frame; elapsed grows by exactly 3 × 0.5 s after a 3-frame step at dt = 0.5; the screenshot is written as a 320×200 PNG with the expected content; resume advances the clock again.

…hots over MCP

send_event now queues its event for the next frame that runs, delivered
after the host's batch for that turn, so an injected key is pressed on
exactly that frame instead of being cleared by beginFrame.

Application gains pause, resume, step (granted frames with a fixed delta)
and queueEvent. New MCP tools: clock_pause, clock_step, clock_resume,
clock_info (with a settled flag for waiting on frames), screenshot and
screenshot_info. send_event, the clock tools and the screenshot tools run
while a frame is parked, and clock_info reports when one is.
@mtdowling
mtdowling merged commit 0a9294d into tecs-dev:main Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants