Skip to content

feat: synthetic input simulation layer (MCP + AltTester) - #9889

Draft
pravusjif wants to merge 13 commits into
devfrom
feat/synthetic-input-simulation
Draft

feat: synthetic input simulation layer (MCP + AltTester)#9889
pravusjif wants to merge 13 commits into
devfrom
feat/synthetic-input-simulation

Conversation

@pravusjif

@pravusjif pravusjif commented Aug 27, 2026

Copy link
Copy Markdown
Member

Issue: #9393

Synthetic Input Simulation Layer

A driver-agnostic input simulation layer (Explorer/Assets/DCL/SyntheticInput/, new DCL.SyntheticInput assembly) that lets automation — the embedded MCP server and AltTester tests — execute every input a human can, through the production input pipelines (collisions, occlusion, distance gates, scene input locks and CRDT write-back are all the real ones). Full architecture: docs/synthetic-input-simulation.md.

What it covers

  • Movement: held walk/jog/run + jump through the real locomotion pipeline
  • Pointer: press/release/click on scene entities, at world points or screen points; hover-only holds (real PetHoverEnter/PetHoverLeave)
  • Global input events: any SDK InputAction (IA_PRIMARY, IA_SECONDARY, IA_ACTION_3…) with the real-key fan-out — entity-bound when hovering a qualified entity, scene-root broadcast otherwise
  • Camera: relative mouse-look holds and absolute look-at
  • UI: semantic clicks/text/scroll/drags on uGUI (by path/id/AltId) and SDK scene UI (by CRDT id) behind an occlusion pre-check, plus a virtual mouse/keyboard path for real hit-testing. SDK scene-UI events are delivered one per drain window — the scene's pointer-event slot holds a single event, so each of enter → down → up → leave is awaited until the scene consumes it, and an unconsumed press/release fails the action instead of reporting an undelivered success

Structure

  • The MCP-private input plumbing migrated in (McpEcsRequestEcsRequest, McpMovementOverrideSyntheticMovementIntent, McpPointerEventSystemSyntheticPointerEventSystem; its test suite migrated rename-only). MCP tools are now thin front-ends over the SyntheticInputAgent / UiAutomationServices facades.
  • 9 new MCP tools (28 total): camera_look, click_at, hover_entity, press_input, ui_list, ui_click, ui_set_text, ui_scroll, ui_drag.
  • AltTester probes (WorldAutomationProbe, UiAutomationProbe, #if ALTTESTER, assembly DCL.SyntheticInput): tests drive the exact same code path via CallStaticMethod, with a start/poll API for multi-frame gestures. Preserved against IL2CPP stripping in link.xml (the two pre-existing probes backfilled).
  • Enabled by --mcp/--mcp-port (any build) or --alttester (ALTTESTER builds); nothing is constructed in retail without flags.

Behavior changes to review

  1. Synthetic movement now respects scene InputModifier locks by default (like WASD); the walk tool gains an ignoreInputModifiers escape.
  2. PrepareGlobalInputEventsSystem now accepts synthetic button edges (deduped against real same-frame presses) and is pinned [UpdateAfter(PlayerOriginatedRaycastSystem)] / [UpdateBefore(ProcessPointerEventsSystem)] — two previously-unordered systems; behavior-neutral for real input.
  3. Scene-root broadcast suppression moved from consumption time to production time (affects real input too). The old mechanism skipped all global entries whenever the same scene update wrote any entity-bound event — all-or-nothing (a press bound to an entity also suppressed an unrelated action pressed the same frame) and dependent on the scene draining both buffers in one update. ProcessPointerEventsSystem now removes each action edge from the GlobalInputEvents buffer the moment it lands entity-bound on a hovered, qualified entity — per action, same frame — and WritePointerEventResultsSystem always writes the (pre-suppressed) buffer. Verified in-world with buffer instrumentation: an aimed press produces the entity result and zero root writes; an unaimed press broadcasts. New ProcessPointerEventsSystemShould covers the synthetic edge, a real key press, and the no-hover control. (Three live runs first misdiagnosed suppression as broken — the scene-side measurement was at fault: isTriggered(action, type, engine.RootEntity) never reads the root because RootEntity is 0 and the SDK's if (entity) guard is falsy for it; an upstream js-sdk-toolchain fix is worth filing.)
  4. Hover leave for tight-maxDistance targets (HoverFeedbackUtils): the leave is gated on the ending hover's own qualification instead of being re-qualified against the ray of the frame the hover ended on — real users also never saw PET_HOVER_LEAVE on tight-range targets before this.

pravusjif and others added 3 commits August 27, 2026 16:21
Stage 1 — pure refactor: the MCP-private input plumbing moves into a new
DCL.SyntheticInput assembly (McpEcsRequest→EcsRequest, McpMovementOverride→
SyntheticMovementIntent, McpPointerEventIntent/System→Synthetic*), with a
SyntheticInputPlugin registering the delivering systems and a
SyntheticInputAgent facade both MCP tools and future AltTester probes call.
walk/click_entity/look_at become thin front-ends over it.

Stage 2 — new capabilities through the same production pipelines:
- global SDK input events: PrepareGlobalInputEventsSystem appends synthetic
  button edges (deduped against real ones) and is pinned between the raycast
  and pointer-events processing
- hover-only aim holds, aimless (global) button gestures, screen-point aims
- camera look: held Cinemachine delta + look-at via CameraLookAtIntent
- synthetic movement now respects scene InputModifier locks by default
  (ignoreInputModifiers escape hatch on the walk tool)
- new MCP tools: hover_entity, press_input, click_at, camera_look

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… tools

Layered UI simulation in DCL.SyntheticInput/UiSimulation:
- semantic path (primary): UiDiscovery lists/resolves uGUI elements by
  path/id/AltId, SdkUiResolver addresses SDK scene UI by CRDT id, and
  UiInteractionSimulator synthesizes their events directly (ExecuteEvents
  for uGUI, UI Toolkit SendEvent for scene UI — two-frame SDK clicks so the
  single pointer-event slot never loses the press), all behind an occlusion
  pre-check so covered elements can't be clicked through
- device path (fidelity): always-enabled DclAutomation mouse+keyboard reach
  both InputActionAsset graphs; UiVirtualDeviceGestureSystem replays
  move/click/drag/key gestures one state per frame, suppressing OS-cursor
  warps via the frame-stamped SyntheticCursorState while it steers
- new MCP tools: ui_list, ui_click (semantic or device), ui_set_text
  (inputs + SDK dropdowns), ui_scroll, ui_drag
- one UiAutomationServices session constructed in DynamicWorldContainer,
  owned by SyntheticInputPlugin, shared by the MCP front-end

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- WorldAutomationProbe / UiAutomationProbe (#if ALTTESTER, assembly
  DCL.SyntheticInput — the CallStaticMethod wire contract): tests drive the
  exact same SyntheticInputAgent/UiAutomationServices the MCP tools drive.
  Multi-frame gestures use start/poll via AltOperationRegistry (CallStaticMethod
  is synchronous on the main thread); nothing throws towards the test.
- SyntheticInputPlugin installs the probes (static-latch pattern); the layer
  now also enables on --alttester alone in ALTTESTER builds (no MCP server).
- link.xml preserves the reflection-only probe types against IL2CPP High
  stripping, backfilling the two existing AltTester probes.
- New docs/synthetic-input-simulation.md (architecture + usage from both
  front-ends); mcp-automation.md tool catalog + implementation map updated;
  automation-testing.md probe table fixed (PerfSampler assembly is DCL.Plugins)
  and extended; mcp-scene-iteration skill + sdk-skills creator copy updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pravusjif pravusjif self-assigned this Aug 27, 2026
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Build skipped — no changes detected under Explorer/.

Lint

No C# files changed — lint ratchet skipped.

Tests

Waiting for tests to start…

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.

1 participant