Repository navigation
Deterministic harness tools over MCP: queued events, pause/step clock, screenshots - #6
Merged
Merged
Conversation
…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
approved these changes
Oct 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
External harnesses driving a Tecs game through the MCP server had to work around three gaps. This closes them.
Queued synthetic input
send_eventused to fold its event into input state as soon as the tool ran, which is between frames. The next frame'sbeginFramethen 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, andframe/elapsedstand 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 fixeddt(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 forsettledbefore reading the world.New API:
Application:pause(),resume(),step(frames, dt),pendingSteps(),stepDelta(),pendingEvents(), plus a read-onlypausedfield.Screenshots
screenshot {name? | path?}requests a capture of the next rendered frame and returns at once with anidand thepathit will write. The server writes the PNG once the host has read the frame back (windowed or--offscreen).screenshot_info {id}reportsready,saved, the size, or the failure. This reusestecs.gfx.screenshotand brings back the historicalscreenshottool name and itsnamekey.Parked frames
send_event, theclock_*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_inforeportsparked, 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 withsend_eventnow 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
dt, quit while paused, the held-event bound, and invalid grants.send_eventkey being pressed on the next frame, the clock tools, answers while parked, and screenshot write and failure paths.send_eventtests 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/testover the workspace, and the headless example smokes pass. I could not run the docs render locally (thelunamarkrock is missing from my from-source Nupp toolchain).End to end, against an
--offscreenhost through the MCP server: frames stay put while paused; a sent Space is pressed on exactly the stepped frame;elapsedgrows by exactly 3 × 0.5 s after a 3-frame step atdt = 0.5; the screenshot is written as a 320×200 PNG with the expected content; resume advances the clock again.