feat: frame control, input simulation, invoke enhancements, and screenshot improvements#4
feat: frame control, input simulation, invoke enhancements, and screenshot improvements#4shinomailer wants to merge 6 commits into
Conversation
Adds three new MCP tools to let LLM/agents control Unity's game loop at inference speed, addressing the 60 FPS vs LLM throughput mismatch. New internal tools (exposed via unity_editor meta-tool): - editor_step_frame: advance exactly one frame via EditorApplication.Step() - editor_set_update_frequency: get/set Time.timeScale and Time.captureFramerate; no-arg call acts as a getter. captureFramerate is the recommended knob for deterministic LLM-driven simulation (deltaTime = timeScale/captureFramerate) - editor_play_for_frames: async advance N frames then pause, with full domain reload recovery via SessionState + AssemblyReloadEvents + bridge replay Also adds EditMode unit tests covering parameter validation and non-play-mode error paths for all three tools. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds three new MCP tools to let LLM/agents control Unity's game loop at inference speed, addressing the 60 FPS vs LLM throughput mismatch. New internal tools (exposed via unity_editor meta-tool): - editor_step_frame: advance exactly one frame via EditorApplication.Step() - editor_set_update_frequency: get/set Time.timeScale and Time.captureFramerate; no-arg call acts as a getter. captureFramerate is the recommended knob for deterministic LLM-driven simulation (deltaTime = timeScale/captureFramerate) - editor_play_for_frames: async advance N frames then pause, with full domain reload recovery via SessionState + AssemblyReloadEvents + bridge replay Also adds EditMode unit tests covering parameter validation and non-play-mode error paths for all three tools. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MCP Gameplay Session — Difficulty Report
Executive SummaryAn LLM attempted to play the game entirely through MCP tools, controlling pacing with frame-control tools ( What Worked Well
Difficulties EncounteredCritical (Blocks Gameplay)D1. No Input SimulationImpact: Cannot move players, place turrets, interact with objects (F key), aim weapons.
D2. Cannot Target Specific Instances via
|
| Time | Action | Result |
|---|---|---|
| Phase 0 | Check scene | GamePlay loaded, Demo art scene |
| Phase 1 | Enter play mode | 2 players spawned, WaitingForNextWave |
| Phase 2 | Set 0.2x slow-mo, advance 30 frames | Frame control works precisely |
| Phase 2 | StartWave(1) | Combat started, zombies spawning |
| Phase 3 | set_property on Player (DontDestroyOnLoad) | FAILED — object not found |
| Phase 3 | Set Transform.position via invoke | FAILED — moved "One shot audio" instead of player |
| Phase 3 | create_gameobject (turret prefab) | Created, but self-destructed (no network spawn) |
| Phase 3 | TakeDamage(9999) on zombie | SUCCESS — killed one zombie |
| Phase 3 | AddTeamMaterials(500) | SUCCESS — 32 → 532 |
| Phase 4 | Advance 300 frames at 2x | Timeout at 299/300 |
| Phase 4 | Advance at 5x | GameOver — settlement screen flashed by |
| Phase 5 | Restart, StartWave → DebugSkipCurrentWave → ForceCompleteLevel | SUCCESS — reached Victory state |
Conclusion
MCP frame-control tools are excellent for pacing, but the current toolset creates an asymmetry: the LLM can observe and manipulate game-level singletons (wave system, economy, level progress) but cannot meaningfully interact with individual game entities (players, enemies, buildings). The three highest-impact improvements would be:
- Instance targeting for
invoke— specify which object to call methods on - DontDestroyOnLoad support in
get_treeandunity_edit - DynamicToolRegistry game-specific functions — custom tools for player movement, turret placement, and state snapshots
|
@codex extract tool level problem out from adhoc game level from this report and PR |
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
3 similar comments
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@codex extract tool level problem out from adhoc game level from this report and PR |
|
To use Codex here, create an environment for this repo. |
|
@codex extract tool level problem out from adhoc game level from this report and PR |
Summary
Testing
|
|
@codex for tool level control, we could add user input trajectory along with play with frame, but this can be another feature request. For game level issues, those are out of topic. |
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
2 similar comments
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
Howerver DontDestroyOnLoad should be resolved @shinomailer |
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
2 similar comments
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@Danielmelody The model is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it. You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
Summary
Testing
Also completed:
|
|
Disabled Copilot for github |
…erialization fixes, and member enumeration
Address four key gaps identified in the MCP gameplay report:
- D2: Add instance_id and game_object params to invoke for targeting specific
component instances instead of always getting the first FindObjectOfType match
- D3/D4: Add DontDestroyOnLoad fallback in ResolveInstance and expose DDOL roots
in SceneGetHierarchy during play mode
- D6: Use UnityJsonSerializer.Settings in WrapResult so Vector3/Quaternion/Color
etc. serialize cleanly as {x,y,z} instead of including computed properties;
expand CoerceArg to handle Vector4, Quaternion, Color, Rect, Bounds
- D9: Support wildcard resolve_method queries (Type.* or Type.) to enumerate all
public members of a type, enabling LLM self-discovery of available APIs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ecution - Add keyboard/mouse input simulation via InputSystem low-level events - Hook NativeInputRuntime.onUpdate to prevent hardware state overwrite - Track held keys/buttons and re-inject after each native update - Auto Y-flip mouse coordinates from screenshot space to screen space - Focus Game View on mouse actions to fix Screen.width/height for UGUI raycasting - Fix screenshot Y-flip for window capture mode (ReadPixels from RenderTexture) - Add multi-mode screenshot capture (auto/game_view/window/camera) with fallback chain - Add execute_menu_item action to unity_editor tool - Improve search method error messages in GameObjectLookup - Add NATIVE_MCP_HAS_INPUT_SYSTEM version define for InputSystem detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add [InitializeOnLoad] ReloadWatcher to reset static state on domain reload, preventing stale reflection hooks after recompilation - Replace Debug.Log/Warn/Error with McpLog to follow project convention - Extract ConvertScreenshotY() helper and apply Y-flip in HandleClick (was only done in HandleMouseMove, causing click coordinate mismatch) - Cache MethodInfo/target/args array in NativeInputInterceptor to avoid per-frame reflection extraction and object[] allocation - Narrow bare catch to ArgumentException in HandleReleaseAll - Add McpLog.Warn to SceneScreenshot.FocusGameView bare catch - Cache GameView type reflection lookup in static field Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Status Update — All Critical Issues ResolvedResponding to the Difficulty Report (D1-D12) and @Danielmelody's feedback. Here's where everything stands after the latest commits: Resolved in This PR
Not Addressed (Out of Scope for Tool-Level)
@Danielmelody's Points
Agreed this would be a natural extension. Current approach:
Done — Mouse Coordinate NoteMouse clicks work for UGUI (Canvas + |
8bd679f to
474e9dd
Compare
|
Most feature of this PR, may need a re-arrangent for unity-repl. maybe should be another repo for how we opperate unity. We can discuss later |
|
I have removed most manuplation tool for clean purpose. Doesn't mean those are not important, just need a better architecture |
|
Does this feature been moved to unity-agent-input? |
Motivation
When an LLM/agent drives a Unity game via MCP, three fundamental gaps block effective gameplay:
invokecan call game methods directly, but many game systems (movement, building placement, weapon firing) are driven by input polling inUpdate().invokecouldn't target specific component instances or access DontDestroyOnLoad objects.This PR addresses all three, turning the MCP bridge into a complete gameplay automation toolkit.
Changes
1. Frame Control Tools (issue #3)
Commits:
d69e79e,f325c7bAdds three new actions to the
unity_editormeta-tool:step_frameEditorApplication.Step()set_update_frequencyTime.timeScaleandTime.captureFramerate. No-arg call acts as getter.captureFramerateis the recommended knob for deterministic LLM-driven simulationplay_for_frames(N)SessionState+AssemblyReloadEvents+ bridge replay2. Invoke Enhancements
Commit:
e71fffaAddresses gaps found during MCP gameplay testing:
instance_idandgame_objectparams let you target a specific component instance instead of always getting the firstFindObjectOfTypematchResolveInstance; expose DDOL roots inSceneGetHierarchyduring Play ModeUnityJsonSerializer.SettingssoVector3/Quaternion/Colorserialize as{x,y,z}instead of including computed properties. ExpandCoerceArgto handleVector4,Quaternion,Color,Rect,Boundsresolve_methodqueries (Type.*) to list all public members, enabling LLM self-discovery of available APIs3. Input Simulation
Commits:
6ae1b75,7bc67feAdds
unity_inputtool with full keyboard and mouse support:key_down,key_up,type_text,mouse_button_down,mouse_button_up,mouse_move,mouse_scroll,click,release_allNativeInputRuntimeoverwrites injected device state with real hardware events every frame. We hookonUpdatevia reflection (same technique as Unity'sInputTestFixture) to let hardware events through, then overwrite keyboard/mouse state with our synthetic values after each updateCamera.pixelHeight, working around the known Unity Editor bug whereScreen.heightreturns the focused window's height instead of the Game View'sFocusGameView()called on mouse actions soScreen.width/heightreturn correct values for UGUIGraphicRaycasterhit testingeditorInputBehaviorInPlayMode: Set toAllDeviceInputAlwaysGoesToGameViewso input routes to the game regardless of editor focus4. Screenshot Improvements
Commit:
6ae1b75modeparameter withauto(default),game_view(coroutine-basedScreenCapture),window(Game Viewm_RenderTexturevia reflection),camera(directCamera.Render)autotries coroutine → window → camera, ensuring a screenshot always succeedsReadPixelsfrom RenderTexture returns Y-inverted data;FlipTextureVerticallycorrects this in thewindowcapture pathwindowandgame_viewmodes include Screen Space - Overlay UI (health bars, building cards, etc.), unlikecameramode which only renders the camera's view5. Misc
execute_menu_itemaction added tounity_editormeta-toolGameObjectLookup: Better error messages for unknown search methodsNATIVE_MCP_HAS_INPUT_SYSTEM: Version define in asmdef for conditional compilation when InputSystem package is present6. Refactoring (code quality)
Commit:
7bc67fe[InitializeOnLoad] ReloadWatcherresets all static state (held keys, reflection hooks, config flags) on domain reload, preventing stale hooks after recompilationDebug.Log/Warn/Error→McpLog.Info/Warn/Erroracross all new codeNativeInputInterceptorcachesMethodInfo/target/object[]to avoid per-frame reflection and allocation overheadConvertScreenshotY()helper shared byHandleMouseMoveandHandleClick(previously onlyHandleMouseMovedid the conversion)catch {}narrowed tocatch (ArgumentException)inHandleReleaseAll;McpLog.Warnadded toSceneScreenshot.FocusGameViewGameViewtype reflection lookup cached in static fieldTest plan
step_frame/play_for_frames/set_update_frequencywork in Play Modemode=window: correct orientation, includes overlay UIinvokeinstance targeting:game_objectparam targets specific player instancesplay_for_framesdomain reload recovery (full cycle)Closes #3
🤖 Generated with Claude Code