unreal-playtest-agent · git:20260826.a028b91 · 2026-08-26 · sha256 c8cf2fa9afe4b337

unreal-playtest-agent git:20260826.a028b91A

Immutable. This exact content never changes and is served at /api/v1/blob/c8cf2fa9afe4b337.

---
name: unreal-playtest-agent
description: >-
  Domain skill - run screenshot-light PIE playtest episodes with structured
  entity observations, bounded semantic actions, transition polling, and
  in-memory traces for QA and external policy or RL runners.
license: MIT
compatibility: Unreal Engine 5.0+, Python 3.9+
allowed-tools: Read Bash
metadata:
  dcc-mcp:
    dcc: unreal
    version: "0.1.0"
    layer: domain
    stage: validation
    search-hint: "unreal pie gameplay playtest episode observation semantic action transition trace qa rl"
    tags: [unreal, pie, playtest, qa, episode, observation, action, rl]
    tools: tools.yaml
    depends: ["unreal-pie"]
---

# Unreal Playtest Agent

Use this skill to drive gameplay QA from Unreal Engine state instead of treating screenshots as the primary observation channel.

## Contract

1. Start an episode with bounded entity selectors and scalar attribute names.
2. Observe the active GameMode, controller, possessed pawn, UMG widgets, and selected runtime entities from the active PIE world.
3. Execute one finite semantic action.
4. Poll the action until it completes, stalls, or times out.
5. Evaluate project-specific rewards and oracles in a project-local Skill.
6. Finish the episode and retain the returned transition trace.

## Combat Telemetry

Observations include bounded `telemetry` for the player and selected entities.
Built-in property aliases cover common health, maximum-health, magazine,
reserve-ammo, and cooldown names on actors or their components. Projects can
add canonical fields through `telemetry_aliases` without adding game-specific
code to the adapter. Canonical fields ending in `_cooldown_remaining` also
populate `action_availability`.

Completed actions report `telemetry_deltas`. Combat actions additionally
report `combat_feedback` and `damage_events` derived from observed health
changes. These are observation deltas, not engine damage-delegate events; a
target disappearing or taking shield-only damage remains inconclusive unless
the project exposes corresponding telemetry aliases.

## Project Mutation Postconditions

Project-local recovery and mutation tools must not treat successful event
dispatch as a successful gameplay effect. Capture the same bounded scalar
state before and after the operation and return
`dcc_mcp_unreal.verified_effect_result(...)` with explicit `required_fields`.
The helper returns success only when at least one named field changes. An
unchanged state returns `postcondition_not_met` and is not automatically
retryable; a missing observation returns `postcondition_unobservable`.

For asynchronous mutations, expose or use a named polling call and evaluate
the postcondition only after it reaches a terminal state. Never return success
while `verification_required` remains outstanding.

Screenshots and DCC-CUA recordings remain useful as checkpoint evidence and visual fallbacks. They are not required for each decision step.

## Boundaries

- Navigation uses Unreal's navigation system and never teleports the player.
- Actions are limited to restoring the configured playable pawn from an accidental PIE spectator start, navigation to a selected entity or explicit reachable waypoint, facing a selected entity or explicit world point, bounded camera-relative movement, conventional gameplay inputs, stopping movement, and waiting.
- Entity selection and attribute collection are bounded. Only JSON-safe scalar values are returned.
- Hidden actors are excluded by default, and class exclusions can remove debris or other non-actionable matches.
- Active UMG observation is read-only and capped at 32 `UserWidget` instances.
- The skill contains no game-specific class names, objectives, rewards, or private project knowledge.
- Reinforcement-learning trainers and policies stay outside the Unreal process and consume the episode contract.

## Typical loop

```text
playtest_episode_control(action="start", include_pawns=true, ...)
playtest_observe(episode_id=...)
playtest_execute_action(episode_id=..., action="navigate_to_entity", ...)
playtest_execute_action(episode_id=..., action="navigate_to_location", target_location={x, y, z})
playtest_poll_action(episode_id=..., action_id=...)
playtest_episode_control(action="finish", episode_id=...)
```