Release 1.1.2 — the default path works end to end

Fix release for 1.1.1: the minimal path end to end, engine commands that
run on Godot, Unity and Unreal, hook warnings you can see, and verdicts
that say when they could not check. Details in CHANGELOG.md; upgrade
steps in UPGRADING.md (v1.1.1 → v1.1.2).

Fixes #67
Fixes #131

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WuJn2FdynEybfL5KC697nJ
This commit is contained in:
Donchitos
2026-09-29 21:32:19 +10:00
co-authored by Claude Opus 5.5
parent 7ed2c3e9c4
commit b21fa0f7f2
383 changed files with 22888 additions and 10618 deletions
@@ -3,7 +3,6 @@
## Agent Summary
Domain: C# patterns in Godot 4, .NET idioms applied to Godot, [Export] attribute usage, signal delegates, and async/await patterns.
Does NOT own: GDScript code (gdscript-specialist), GDExtension C/C++ bindings (gdextension-specialist).
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references C# in Godot 4 / .NET patterns / signal delegates)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over GDScript or GDExtension code
---
@@ -34,7 +33,7 @@ No gate IDs assigned.
- Does NOT produce GDScript code
- Explicitly states that GDScript authoring belongs to `godot-gdscript-specialist`
- Redirects the request to `godot-gdscript-specialist`
- May note that the C# interface can be described so the gdscript-specialist knows the expected API shape
- Any hand-off it offers describes the existing C# system's API (its signals and public methods) for godot-gdscript-specialist to port — it contains no GDScript
### Case 3: Async signal awaiting
**Input:** "Wait for an animation to finish before transitioning game state using C# async."
@@ -43,7 +42,8 @@ No gate IDs assigned.
- Uses `await ToSignal(animationPlayer, AnimationPlayer.SignalName.AnimationFinished)`
- Does NOT use `Thread.Sleep()` or `Task.Delay()` as a polling substitute
- Notes that the calling method must be `async` and that fire-and-forget `async void` is only acceptable for event handlers
- Handles cancellation or timeout if the animation could fail to fire
- Checks `IsInstanceValid(this)` after the `await` before transitioning state — the node may have been freed while waiting
- If the animation might never finish (interrupted, replaced), raises that as an unspecified edge case for the user to decide; any timeout it adds uses `GetTree().CreateTimer()`, never `Task.Delay()`
### Case 4: Threading model conflict
**Input:** "This C# code accesses a Godot Node from a background Task thread to update its position."
@@ -51,7 +51,7 @@ No gate IDs assigned.
- Flags this as a race condition risk: Godot nodes are not thread-safe and must only be accessed from the main thread
- Does NOT approve or implement the multi-threaded node access pattern
- Provides the correct pattern: use `CallDeferred()`, `Callable.From().CallDeferred()`, or marshal back to the main thread via a thread-safe queue
- Explains the distinction between Godot's main thread requirement and .NET's thread-agnostic types
- Explains why the code compiles yet is unsafe: the `Task` body runs on a .NET thread-pool thread and nothing in C# stops it touching a Node — the main-thread rule belongs to Godot's scene tree
### Case 5: Context pass — Godot 4.6 API correctness
**Input:** Engine version context: Godot 4.6. Request: "Connect a signal using the new typed signal delegate pattern."
@@ -72,6 +72,7 @@ No gate IDs assigned.
- [ ] Flags multi-threaded Godot node access as unsafe and provides the correct pattern
- [ ] Uses typed signal delegates — not deprecated string-based Connect() calls
- [ ] Checks engine version reference for API changes before producing code
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
@@ -3,7 +3,6 @@
## Agent Summary
Domain: GDExtension API, godot-cpp C++ bindings, godot-rust bindings, native library integration, and native performance optimization.
Does NOT own: GDScript code (gdscript-specialist), shader code (godot-shader-specialist).
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references GDExtension / godot-cpp / native bindings)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over GDScript or shader authoring
---
@@ -26,7 +25,7 @@ No gate IDs assigned.
- Class inheriting from `godot::Object` or an appropriate Godot base class
- `GDCLASS` macro registration
- `_bind_methods()` implementation exposing the physics API to GDScript
- `GDExtension` entry point (`gdextension_init`) setup
- Module entry point: `initialize_module` in `register_types.cpp` registers the class with `ClassDB::register_class<T>()` at `MODULE_INITIALIZATION_LEVEL_SCENE`, reached from the exported init function named by the manifest's `entry_symbol`
- Notes the `.gdextension` manifest file format required
- Does NOT produce the GDScript usage code (that belongs to gdscript-specialist)
@@ -36,16 +35,16 @@ No gate IDs assigned.
- Does NOT produce GDScript code
- Explicitly states that GDScript authoring belongs to `godot-gdscript-specialist`
- Redirects to `godot-gdscript-specialist`
- May describe the API surface the GDScript should call (method names, parameter types) as a handoff spec
- Any hand-off it offers is the extension's bound API surface (method names, parameter and return types) — not GDScript that calls it
### Case 3: ABI compatibility risk — minor version update
**Input:** "We're upgrading from Godot 4.5 to 4.6. Will our existing GDExtension still work?"
**Expected behavior:**
- Flags the ABI compatibility concern: GDExtension binaries may not be ABI-compatible across minor versions
- States the compatibility direction: an extension built for 4.5 should load in 4.6, but not the reverse — so it may keep working, yet it must be re-tested, and rebuilt against 4.6 to use newer APIs
- Directs to check the 4.5→4.6 migration guide for GDExtension API changes
- Recommends recompiling the extension against the 4.6 godot-cpp headers rather than assuming binary compatibility
- Recommends rebuilding against the 4.6 godot-cpp headers rather than treating "should load" as tested
- Notes that the `.gdextension` manifest may need a `compatibility_minimum` version update
- Provides the recompilation checklist
- Lays out the rebuild as a checklist: rebuild debug and release (`scons ... target=template_debug` / `template_release`, or `cargo build` / `cargo build --release`) for every target platform, update `compatibility_minimum`, then re-test in the 4.6 editor before shipping
### Case 4: Memory management — RAII for Godot objects
**Input:** "How should we manage the lifecycle of Godot objects created inside C++ GDExtension code?"
@@ -55,14 +54,14 @@ No gate IDs assigned.
- `memnew()` / `memdelete()` for non-reference-counted objects
- Warning: do NOT use `new`/`delete` for Godot objects — undefined behavior
- Notes object ownership rules: who is responsible for freeing a node added to the scene tree
- Provides a concrete example managing a `CollisionShape3D` created in C++
- Its concrete example (e.g., a `CollisionShape3D` created in C++) applies both rules: the node is created with `memnew()` and handed to the scene tree with `add_child()`, after which the tree frees it (no `memdelete()`); its shape resource is held in a `Ref<>`; `memdelete()` is only for a node that never enters the tree
### Case 5: Context pass — Godot 4.6 GDExtension API check
**Input:** Engine version context: Godot 4.6 (upgrading from 4.5). Request: "Check if any GDExtension APIs changed from 4.5 to 4.6."
**Expected behavior:**
- References the 4.5→4.6 migration guide from the VERSION.md verified sources list
- Reports on any documented GDExtension API changes in the 4.6 release
- If no breaking changes are documented for GDExtension in 4.6, states that explicitly with the caveat to verify against the official changelog
- Reads the 4.5 → 4.6 table in `docs/engine-reference/godot/breaking-changes.md` and states explicitly that it has no GDExtension-specific row, with the caveat to verify against the official changelog
- Still surfaces the listed 4.6 changes that reach native code: `Quaternion` now initializes to identity (it was zero)
- Flags the D3D12 default on Windows (4.6 change) as potentially relevant for GDExtension rendering code
- Provides a checklist of what to verify after upgrading
@@ -74,9 +73,10 @@ No gate IDs assigned.
- [ ] Redirects GDScript authoring to godot-gdscript-specialist
- [ ] Redirects shader authoring to godot-shader-specialist
- [ ] Returns structured output (binding patterns, RAII examples, ABI checklists)
- [ ] Flags ABI compatibility risks on minor version upgrades — never assumes binary compatibility
- [ ] States that compatibility runs forward only (older build → newer Godot) and requires a re-test after an upgrade — never treats "should load" as tested
- [ ] Uses Godot-specific memory management (`memnew`/`memdelete`, `Ref<T>`) not raw C++ new/delete
- [ ] Checks engine version reference for GDExtension API changes before confirming compatibility
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
@@ -3,7 +3,6 @@
## Agent Summary
Domain: GDScript static typing, design patterns in GDScript, signal architecture, coroutine/await patterns, and GDScript performance.
Does NOT own: shader code (godot-shader-specialist), GDExtension bindings (godot-gdextension-specialist).
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references GDScript / static typing / signals / coroutines)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over shader code or GDExtension
---
@@ -34,33 +33,34 @@ No gate IDs assigned.
- Does NOT produce shader code in GDScript or in Godot's shading language
- Explicitly states that shader authoring belongs to `godot-shader-specialist`
- Redirects the request to `godot-shader-specialist`
- May note that the GDScript side (passing uniforms to a shader, setting shader parameters) is within its domain
- Any help it offers stays on the script side its redirect line says it owns (`set_shader_parameter()`, material assignment) — it writes no shader code
### Case 3: Async loading with coroutines
**Input:** "Load a scene asynchronously and wait for it to finish before spawning it."
**Expected behavior:**
- Produces an `await` + `ResourceLoader.load_threaded_request` pattern for Godot 4
- Uses static typing throughout (`var scene: PackedScene`)
- Handles the completion check with `ResourceLoader.load_threaded_get_status()`
- Handles the completion check with `ResourceLoader.load_threaded_get_status()`, awaiting between polls rather than blocking the frame
- Checks `is_instance_valid(self)` after the await before spawning — the node may have been freed while the scene loaded
- Notes error handling for failed loads
- Does NOT use deprecated Godot 3 `yield()` syntax
### Case 4: Performance issue — typed array recommendation
**Input:** "The entity update loop is slow; it iterates an untyped Array of 1,000 nodes every frame."
**Expected behavior:**
- Identifies that an untyped `Array` foregoes compiler optimization in GDScript
- Recommends converting to a typed array (`Array[Node]` or the specific type) to enable JIT hints
- Notes that if this is still insufficient, escalates the hot path to C# migration recommendation
- Identifies that an untyped `Array` foregoes GDScript compiler optimizations
- Recommends converting to a typed array (`Array[Node]` or the specific type) so the compiler can use typed code paths
- Produces the typed array refactor as the immediate fix
- Does NOT recommend migrating the entire codebase to C# without profiling evidence
- Notes that if profiling still shows the loop as a bottleneck, the next step is moving the hot path to GDExtension (C++/Rust) with godot-gdextension-specialist, per its GDScript/GDExtension boundary
- Does NOT recommend moving the loop, or the codebase, out of GDScript without profiler evidence
### Case 5: Context pass — Godot 4.6 with post-cutoff features
**Input:** Engine version context provided: Godot 4.6. Request: "Create an abstract base class for all enemy types using @abstract."
**Expected behavior:**
- Identifies `@abstract` as a Godot 4.5+ feature (post-cutoff)
- Notes this in the output: feature introduced in 4.5, verified against VERSION.md migration notes
- Produces the GDScript class using `@abstract` with correct syntax as documented in migration notes
- Marks the output as requiring verification against the official 4.5 release notes due to post-cutoff status
- Notes this in the output: feature introduced in 4.5, confirmed against `docs/engine-reference/godot/breaking-changes.md` (4.4 → 4.5 table)
- Produces the GDScript class using `@abstract` on the class and its abstract methods, following the reference docs (`current-best-practices.md`) rather than training data
- Flags that `VERSION.md` records `Installed at pin time` as NOT DETERMINED — the installed editor must be 4.5 or later for `@abstract` to parse
- Uses static typing for all method signatures in the abstract class
---
@@ -73,6 +73,7 @@ No gate IDs assigned.
- [ ] Returns structured GDScript output with full static typing
- [ ] Uses Godot 4 API only — no deprecated Godot 3 patterns (yield, connect with strings, etc.)
- [ ] Flags post-cutoff features (4.4, 4.5, 4.6) and marks them as requiring doc verification
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
@@ -3,7 +3,6 @@
## Agent Summary
Domain: Godot shading language (GLSL-derivative), visual shaders (VisualShader graph), material setup, particle shaders, and post-processing effects.
Does NOT own: gameplay code, art style direction.
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references Godot shading language / materials / post-processing)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Glob, Grep
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition references `docs/engine-reference/godot/VERSION.md` as the authoritative source for Godot shader API changes
---
@@ -34,26 +33,26 @@ No gate IDs assigned.
**Input:** "Write an HLSL compute shader for this dissolve effect."
**Expected behavior:**
- Does NOT produce HLSL code
- Clearly states: "Godot does not use HLSL directly; it uses its own shading language (a GLSL derivative)"
- Translates the HLSL intent to the equivalent Godot shader approach
- Notes that RenderingDevice compute shaders are available in Godot 4 but are a low-level API and flags it appropriately if that was the intent
- States that Godot materials are written in Godot's own shading language (`.gdshader`, a GLSL derivative), not HLSL
- Translates the HLSL intent into the equivalent `.gdshader` approach (noise threshold, `discard`, emission at the dissolve edge)
- If a compute shader was really the intent, flags compute as a separate low-level path (RenderingDevice; unavailable on the Compatibility renderer) and brings in godot-gdextension-specialist for compute-shader offloading
### Case 3: Post-cutoff API change — texture sampling (Godot 4.4)
### Case 3: Post-cutoff API change — shader texture types (Godot 4.4)
**Input:** "Use `texture()` with a sampler2D to sample the noise texture in the shader."
**Expected behavior:**
- Checks the version reference: Godot 4.4 changed texture sampler type declarations
- Flags the potential API change: `sampler2D` syntax and `texture()` call behavior may differ from pre-4.4
- Provides the correct syntax for the project's pinned version (4.6) as documented in migration notes
- Does NOT use pre-4.4 texture sampling syntax without flagging the version risk
- Checks the version reference before writing the shader (`VERSION.md`, then `breaking-changes.md` and `modules/rendering.md`)
- Identifies what the 4.4 change covers: engine-side shader texture parameter/return types moved from `Texture2D` to `Texture` — script and engine API code that handles shader textures, not shading-language syntax
- Uses `uniform sampler2D noise_texture;` with `texture(noise_texture, UV)` — the form its own patterns use; the reference records no change to shading-language sampling syntax
- Does NOT invent a changed sampling syntax for 4.4–4.6, or rewrite `sampler2D` / `texture()` because of the 4.4 row
### Case 4: Fragment shader LOD strategy
**Input:** "The fragment shader for the water surface has 8 texture samples and is causing GPU bottlenecks on mid-range hardware."
**Expected behavior:**
- Identifies the per-fragment texture sample count as the primary cost driver
- Proposes an LOD strategy:
- Reduce sample count at distance (distance-based shader variant or LOD level)
- Pre-bake some texture combinations offline
- Use lower-resolution noise textures for distant samples
- Reduce sample count at distance with a simplified LOD material for distant water, not a per-pixel distance branch in the fragment shader
- Move UV/scroll math that does not need per-pixel evaluation into the vertex shader and pass it through a `varying`
- Make sure the water textures are mipmapped (e.g. `filter_linear_mipmap`) so distant samples read smaller mip levels
- Provides the shader code modification implementing the LOD approach
- Does NOT change gameplay behavior of the water system
@@ -61,8 +60,10 @@ No gate IDs assigned.
**Input:** Engine version context: Godot 4.6. Request: "Add a bloom/glow post-processing effect to the scene."
**Expected behavior:**
- References the VERSION.md note: Godot 4.6 includes a glow rework
- Produces glow configuration guidance using the 4.6 WorldEnvironment approach, not the pre-4.6 API
- Explicitly notes which properties or parameters changed in the 4.6 glow rework
- Because `VERSION.md` records `Installed at pin time` as NOT DETERMINED, asks which editor version is installed before tuning for the 4.6 glow behavior
- Produces glow configuration guidance on the `WorldEnvironment` node's `Environment` resource
- States the documented 4.6 change: glow now processes before tonemapping (it was after), with screen blending — so glow intensity/blend tuned on an earlier version will look different and may need re-tuning
- Does NOT invent renamed or removed glow properties that `breaking-changes.md` and `modules/rendering.md` do not document
- Flags any properties that the LLM's training data may have incorrect information about due to the post-cutoff timing
---
@@ -71,14 +72,15 @@ No gate IDs assigned.
- [ ] Stays within declared domain (Godot shading language, materials, VFX shaders, post-processing)
- [ ] Redirects gameplay code requests to gameplay-programmer
- [ ] Produces valid Godot shading language — never HLSL or raw GLSL without a Godot wrapper
- [ ] Writes material and post-process shaders as `.gdshader` (Godot shading language) — never HLSL
- [ ] Checks engine version reference for post-cutoff shader API changes (4.4 texture types, 4.6 glow rework)
- [ ] Returns structured output (shader code with uniforms documented, LOD strategies with performance rationale)
- [ ] Flags any post-cutoff API usage as requiring verification
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
## Coverage Notes
- Dissolve shader (Case 1) should be paired with a visual test screenshot in `production/qa/evidence/`
- Texture API flag (Case 3) confirms the agent checks VERSION.md before using APIs that changed post-4.3
- Texture-type case (Case 3) confirms the agent reads what a post-cutoff change actually covers before applying it, instead of rewriting syntax the change does not touch
- Glow rework (Case 5) is a Godot 4.6-specific test — verifies the agent applies the most recent migration notes
@@ -3,7 +3,6 @@
## Agent Summary
Domain: Godot-specific patterns, node/scene architecture, signals, resources, and GDScript vs C# vs GDExtension decisions.
Does NOT own: actual code authoring in a specific language (delegates to language sub-specialists).
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references Godot architecture / node patterns / engine decisions)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is `inherit` — frontmatter `model:` reads exactly `inherit` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition references `docs/engine-reference/godot/VERSION.md` as the authoritative API source
---
@@ -25,25 +24,28 @@ No gate IDs assigned.
- Produces a pattern decision guide with rationale:
- Signals: decoupled communication, parent-to-child ignorance, event-driven UI updates, one-to-many notification
- Direct calls: tightly-coupled systems where the caller needs a return value, or performance-critical hot paths
- Provides concrete examples of each pattern in the project's context
- Illustrates each side with a concrete Godot case (e.g., a child health component emitting a typed `health_changed` signal that its parent or the HUD listens to; a parent calling a method on a child it owns)
- Does NOT produce raw code for both patterns — refers to gdscript-specialist or csharp-specialist for implementation
- Notes the "no upward signals" convention (child does not call parent methods directly — uses signals instead)
- Notes the "call down, signal up" convention: a child does not call its parent's methods directly — it emits a signal — so each scene stays self-contained with no implicit dependency on its parent
### Case 2: Wrong-engine redirect
**Input:** "Write a MonoBehaviour that runs on Start() and subscribes to a UnityEvent."
**Expected behavior:**
- Does NOT produce Unity MonoBehaviour code
- Clearly identifies that this is a Unity pattern, not a Godot pattern
- Provides the Godot equivalent: a Node script using `_ready()` instead of `Start()`, and Godot signals instead of UnityEvent
- Confirms the project is Godot-based and redirects the conceptual mapping
- Maps the concepts to Godot: `_ready()` instead of `Start()`, and a typed Godot `signal` connected in `_ready()` instead of a UnityEvent
- Grounds the answer in the project's engine pin (`docs/engine-reference/godot/VERSION.md`, Godot 4.6) rather than asking which engine the project uses
- Routes the actual node script to godot-gdscript-specialist or godot-csharp-specialist rather than writing it itself
- Variant: with `engine.name: Unity` in `project.yaml`, it says it is the wrong specialist for this project instead of mapping the concepts
### Case 3: Post-cutoff API risk
**Input:** "Use the new Godot 4.5 @abstract annotation to define an abstract base class."
**Expected behavior:**
- Identifies that `@abstract` is a post-cutoff feature (introduced in Godot 4.5, after LLM knowledge cutoff)
- Flags the version risk: LLM knowledge of this annotation may be incomplete or incorrect
- Directs the user to verify against `docs/engine-reference/godot/VERSION.md` and the official 4.5 migration guide
- Provides best-effort guidance based on the migration notes in the version reference while clearly marking it as unverified
- Flags the version risk: LLM knowledge of this annotation may be incomplete or incorrect, so the reference docs take precedence
- Confirms the feature against the version reference before answering — `docs/engine-reference/godot/breaking-changes.md` lists the `@abstract` decorator in its 4.4 → 4.5 table
- Flags the installed-version gap: `VERSION.md` records `Installed at pin time` as NOT DETERMINED, so the installed editor must be confirmed as 4.5 or later before `@abstract` code will parse
- Routes the class authoring to godot-gdscript-specialist rather than writing the class itself
### Case 4: Language selection for a hot path
**Input:** "The physics query loop runs every frame for 500 objects. Should we use GDScript or C# for this?"
@@ -51,17 +53,17 @@ No gate IDs assigned.
- Provides a balanced analysis:
- GDScript: simpler, team familiar, but slower for tight loops
- C#: faster for CPU-intensive loops, requires .NET runtime, team needs C# knowledge
- Does NOT make the final decision unilaterally
- Defers the decision to `lead-programmer` with the analysis as input
- Does NOT make the final decision unilaterally — presents the trade-off for the user to decide
- Flags that bringing C#/.NET (or a GDExtension module) into the project is a major tech choice that needs `technical-director` sign-off, not a call it makes alone
- Notes that GDExtension (C++) is a third option for extreme performance cases and recommends escalating if C# is insufficient
### Case 5: Context pass — engine version 4.6
**Input:** Engine version context provided: Godot 4.6, Jolt as default physics. Request: "Set up a RigidBody3D for the player character."
**Expected behavior:**
- Reads the 4.6 context and applies the Jolt-default knowledge (from VERSION.md migration notes)
- Recommends RigidBody3D configuration choices that are Jolt-compatible (e.g., notes any GodotPhysics-specific settings that behave differently under Jolt)
- References the 4.6 migration note about Jolt becoming default rather than relying on LLM training data alone
- Flags any RigidBody3D properties that changed behavior between GodotPhysics and Jolt
- Reads the 4.6 context and the physics module reference (`docs/engine-reference/godot/modules/physics.md`) rather than relying on LLM training data alone
- States what the reference documents: Jolt is the default 3D engine for new projects, but existing projects keep their setting — so it checks Project Settings → Physics → 3D → Physics Engine before assuming Jolt
- Carries over the documented Jolt differences that apply: collision margins may behave differently, and Jolt emits runtime warnings for properties it does not support (e.g. HingeJoint3D `damp`)
- Does NOT invent RigidBody3D property changes between GodotPhysics and Jolt — the reference documents none, and it says so
---
@@ -72,7 +74,8 @@ No gate IDs assigned.
- [ ] Returns structured findings (decision trees, pattern recommendations with rationale)
- [ ] Treats `docs/engine-reference/godot/VERSION.md` as authoritative over LLM training data
- [ ] Flags post-cutoff API usage (4.4, 4.5, 4.6) with verification requirements
- [ ] Defers language-selection decisions to lead-programmer when trade-offs exist
- [ ] Leaves language-selection decisions to the user and routes adoption of a new language or runtime to technical-director
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
@@ -3,7 +3,6 @@
## Agent Summary
Domain: Addressable Asset System — groups, async loading/unloading, handle lifecycle management, memory budgeting, content catalogs, and remote content delivery.
Does NOT own: rendering systems (engine-programmer), game logic that uses the loaded assets (gameplay-programmer).
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references Addressables / asset loading / content catalogs / remote delivery)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over rendering systems or gameplay using the loaded assets
---
@@ -26,8 +25,8 @@ No gate IDs assigned.
- Stores the returned `AsyncOperationHandle<Texture2D>` in the requesting object
- On character destruction (`OnDestroy()`), calls `Addressables.Release(handle)` with the stored handle
- Does NOT use `Resources.Load()` as the loading mechanism
- Notes that releasing with a null or uninitialized handle causes errors — includes a validity check
- Notes the difference between releasing the handle vs. releasing the asset (handle release is correct)
- Checks that the load succeeded (`handle.Status`) before using `Result`, and never releases a handle it does not hold (e.g., a character destroyed before its load was started)
- Uses `Addressables.Release(handle)` for loaded assets and reserves `Addressables.ReleaseInstance()` for `InstantiateAsync` results — does not mix the two
### Case 2: Out-of-domain redirect
**Input:** "Implement the rendering system that applies the loaded texture to the character mesh."
@@ -35,37 +34,39 @@ No gate IDs assigned.
- Does NOT produce rendering or mesh material assignment code
- Explicitly states that rendering system implementation belongs to `engine-programmer`
- Redirects the request to `engine-programmer`
- May describe the asset type and API surface it will provide (e.g., `Texture2D` reference once the handle completes) as a handoff spec
- Hands off the asset contract instead: the `Texture2D` comes from the handle's `Result` once the load succeeds, and stays valid only until the owning handle is released, so the consumer must not hold it past that point
### Case 3: Memory leak — un-released handle
**Input:** "Memory usage keeps climbing after each level load. We use Addressables to load level assets."
**Expected behavior:**
- Diagnoses the likely cause: `AsyncOperationHandle` objects not being released after use
- Identifies the handle leak pattern: loading assets into a local variable, losing reference, never calling `Addressables.Release()`
- Produces an auditing approach: search for all `LoadAssetAsync` / `LoadSceneAsync` calls and verify matching `Release()` calls
- Provides a corrected pattern using a tracked handle list (`List<AsyncOperationHandle>`) with a `ReleaseAll()` cleanup method
- Does NOT assume the leak is elsewhere without evidence
- Produces an auditing approach: pair every `LoadAssetAsync` with `Release()`, every `InstantiateAsync` with `ReleaseInstance()`, and every `LoadSceneAsync` with `UnloadSceneAsync()`
- Provides a corrected pattern: every active handle kept in a tracked collection and released when the level unloads, so nothing accumulates across level transitions
- Confirms the diagnosis with the Addressables Event Viewer (per-asset reference counts) or the Memory Profiler before concluding — does not assume the leak is elsewhere without evidence
### Case 4: Remote content delivery — catalog versioning
**Input:** "We need to support downloadable content updates without requiring a full app re-install."
**Expected behavior:**
- Produces the remote catalog update pattern:
- Produces the remote catalog update pattern documented in `docs/engine-reference/unity/plugins/addressables.md`:
- `Addressables.CheckForCatalogUpdates()` on startup
- `Addressables.UpdateCatalogs()` for detected updates
- `Addressables.DownloadDependenciesAsync()` to pre-warm the updated content
- Notes catalog hash checking for change detection
- Addresses the edge case: what happens if a player starts a session, the catalog updates mid-session — defines behavior (complete current session on old catalog, reload on next launch)
- `Addressables.GetDownloadSizeAsync()` before downloading, then `Addressables.DownloadDependenciesAsync()` to pre-download the updated content
- Re-downloads only changed bundles; catalogs are versioned so clients fall back to cached content, and download failures retry with exponential backoff while showing progress
- Raises the mid-session case (a catalog update lands while a session is running) and asks the user which behavior they want, rather than silently picking one
- Plans the update-path tests: fresh install, V1 to V2, and V1 to V3 skipping V2
- Does NOT design the server-side CDN infrastructure (defers to devops-engineer)
### Case 5: Context pass — platform memory constraints
**Input:** Platform context: Nintendo Switch target, 4GB RAM, practical asset memory ceiling 512MB. Request: "Design the Addressables loading strategy for a large open-world level."
**Expected behavior:**
- References the 512MB memory ceiling from the provided context
- References the 512MB memory ceiling from the provided context, and uses it in place of its own default console budget
- Designs a streaming strategy:
- Divide the world into addressable zones loaded/unloaded based on player proximity
- Defines a memory budget per active zone (e.g., 128MB, max 4 zones active)
- Specifies async pre-load trigger distance and unload distance (hysteresis)
- Notes Switch-specific constraints: slower load times from SD card, recommend pre-warming adjacent zones
- Additive scene streaming through `Addressables.LoadSceneAsync()` / `Addressables.UnloadSceneAsync()`, with groups organized per zone by loading context
- A memory budget per active zone whose total stays under the 512MB ceiling, with zones unloaded on transition rather than accumulated
- Essential zone content loaded first, optional content streamed after
- Defers where zone boundaries fall to `level-designer`
- Flags storage I/O speed on the target hardware as a load-time risk to verify on device, without quoting platform load speeds from memory
- Does NOT produce a loading strategy that would exceed the stated 512MB ceiling without flagging it
---
@@ -76,8 +77,9 @@ No gate IDs assigned.
- [ ] Redirects rendering and gameplay asset-use code to engine-programmer and gameplay-programmer
- [ ] Returns structured output (loading patterns, handle lifecycle code, streaming zone designs)
- [ ] Always pairs `LoadAssetAsync` with a corresponding `Release()` — flags handle leaks as a memory bug
- [ ] Designs loading strategies against provided memory ceilings
- [ ] Does not design CDN/server infrastructure — defers to devops-engineer for server side
- [ ] Designs loading strategies against provided memory ceilings, which override its default per-platform budgets
- [ ] Specifies only the client-side contract (remote load path / URL structure, catalog versioning, cache-header requirement) and defers CDN hosting and the delivery pipeline to devops-engineer
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
@@ -3,7 +3,6 @@
## Agent Summary
Domain: ECS architecture (IComponentData, ISystem, SystemAPI), Jobs system (IJob, IJobEntity, Burst), Burst compiler constraints, DOTS gameplay systems, and hybrid renderer.
Does NOT own: MonoBehaviour gameplay code (gameplay-programmer), UI implementation (unity-ui-specialist).
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references ECS / Jobs / Burst / IComponentData)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over MonoBehaviour gameplay or UI systems
---
@@ -25,10 +24,10 @@ No gate IDs assigned.
- Produces:
- `PlayerMovementData : IComponentData` struct with velocity, speed, and input vector fields
- `PlayerMovementSystem : ISystem` with `OnUpdate()` using `SystemAPI.Query<>` or `IJobEntity`
- Bakes the player's initial state from an authoring MonoBehaviour via `IBaker`
- Bakes the player's initial state from an authoring MonoBehaviour via a `Baker<T>` subclass (the pattern in `docs/engine-reference/unity/plugins/dots-entities.md`)
- Uses `RefRW<LocalTransform>` for position updates (not deprecated `Translation`)
- Marks the job `[BurstCompile]` and notes what must be unmanaged for Burst compatibility
- Does NOT modify the input polling system — reads from an existing `PlayerInputData` component
- Keeps one concern per system: the movement system reads input from a component (e.g. `PlayerInputData`) that a separate input system writes, rather than polling input itself, and asks before creating or changing that input side
### Case 2: MonoBehaviour push-back
**Input:** "Just use MonoBehaviour for the player movement — it's simpler."
@@ -36,7 +35,7 @@ No gate IDs assigned.
- Acknowledges the simplicity argument
- Explains the DOTS trade-off: more setup upfront, but the ECS/Burst approach provides the performance characteristics documented in the project's ADR or requirements
- Does NOT implement a MonoBehaviour version if the project has committed to DOTS
- If no commitment exists, flags the architecture decision to `lead-programmer` / `technical-director` for resolution
- If no commitment exists, routes the MonoBehaviour-vs-DOTS decision to `unity-specialist` (its named contact for overall Unity architecture), with `technical-director` as the escalation for an unresolved technical conflict
- Does not make the MonoBehaviour vs. DOTS decision unilaterally
### Case 3: Burst-incompatible managed memory
@@ -45,27 +44,26 @@ No gate IDs assigned.
- Flags `List<T>` as a managed type that is incompatible with Burst compilation
- Does NOT approve the Burst job with managed memory access
- Provides the correct replacement: `NativeArray<EnemyData>`, `NativeList<EnemyData>`, or `NativeHashMap<>` depending on the use case
- Notes that `NativeArray` must be disposed explicitly or via `[DeallocateOnJobCompletion]`
- Notes that the native container must be disposed, with the allocator chosen by lifetime (`Allocator.TempJob` for frame-scoped, `Allocator.Persistent` for long-lived) and capacity pre-allocated when the size is known
- Produces the corrected job using unmanaged native containers
### Case 4: Hybrid access — DOTS system needs MonoBehaviour data
**Input:** "The DOTS movement system needs to read the camera transform managed by a MonoBehaviour CameraController."
**Expected behavior:**
- Identifies this as a hybrid access scenario
- Provides the correct hybrid pattern: store the camera transform in a singleton `IComponentData` (updated from the MonoBehaviour side each frame via `EntityManager.SetComponentData`)
- Alternatively suggests the `CompanionComponent` / managed component approach
- Does NOT access the MonoBehaviour from inside a Burst job — flags that as unsafe
- Provides the bridge code on both the MonoBehaviour side (writing to ECS) and the DOTS system side (reading from ECS)
- Does NOT put the `Transform` or the MonoBehaviour itself into a component — components hold no references to managed objects
- Carries the camera data across the boundary as unmanaged values (`Unity.Mathematics` types) in an `IComponentData`, written at one explicit hand-off point on the MonoBehaviour side and read by the system — not reached for per entity
- Does NOT access the MonoBehaviour from inside a Burst job — flags managed access in Burst code as a compile error
### Case 5: Context pass — performance targets
**Input:** Technical preferences from context: 60fps target, max 2ms CPU script budget per frame. Request: "Design the ECS chunk layout for 10,000 enemy entities."
**Expected behavior:**
- References the 2ms CPU budget explicitly in the design rationale
- Designs the `IComponentData` chunk layout for cache efficiency:
- Groups frequently-queried together components in the same archetype
- Separates rarely-used data into separate components to keep hot data compact
- Estimates entity iteration time against the 2ms budget
- Provides memory layout analysis (bytes per entity, entities per chunk at 16KB chunk size)
- Splits components by system access pattern, not by game concept — no "god component"
- Keeps hot components small, with rarely-used data in separate components
- Uses tag components for filtering, avoids `ISharedComponentData` that would fragment archetypes, and uses `IEnableableComponent` instead of structural changes for toggled state
- Names how the budget will be verified — Entities Profiler system timings and per-archetype memory — instead of presenting unmeasured timings as fact
- Does NOT design a layout that will obviously exceed the stated 2ms budget without flagging it
---
@@ -74,14 +72,15 @@ No gate IDs assigned.
- [ ] Stays within declared domain (ECS, Jobs, Burst, DOTS gameplay systems)
- [ ] Redirects MonoBehaviour-only gameplay to gameplay-programmer
- [ ] Returns structured output (IComponentData structs, ISystem implementations, IBaker authoring classes)
- [ ] Returns structured output (IComponentData structs, ISystem implementations, `Baker<T>` authoring classes)
- [ ] Flags managed memory access in Burst jobs as a compile error and provides unmanaged alternatives
- [ ] Provides hybrid access patterns when DOTS systems need to interact with MonoBehaviour systems
- [ ] Designs chunk layouts against provided performance budgets
- [ ] Designs chunk layouts against provided performance budgets, and names the profiling step that verifies them
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
## Coverage Notes
- ECS conversion (Case 1) must include a unit test using the ECS test framework (`World`, `EntityManager`)
- ECS conversion (Case 1) must include a unit test using the ECS test framework (`World`, `EntityManager`) under `Assets/Tests/EditMode/`
- Burst incompatibility (Case 3) is safety-critical — the agent must catch this before the code is written
- Chunk layout (Case 5) verifies the agent applies quantitative performance reasoning to architecture decisions
- Chunk layout (Case 5) verifies the agent designs against a stated budget and says how it will be measured, rather than asserting unmeasured numbers
@@ -3,7 +3,6 @@
## Agent Summary
Domain: Unity Shader Graph, custom HLSL, VFX Graph, URP/HDRP pipeline customization, and post-processing effects.
Does NOT own: gameplay code, art style direction.
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references Shader Graph / HLSL / VFX Graph / URP / HDRP)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Glob, Grep
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over gameplay code or art direction
---
@@ -22,11 +21,9 @@ No gate IDs assigned.
### Case 1: In-domain request — appropriate output
**Input:** "Create an outline effect for characters using Shader Graph in URP."
**Expected behavior:**
- Produces a Shader Graph node setup description:
- Inverted hull method: Scale Normal → Vertex offset in vertex stage, Cull Front
- OR screen-space post-process outline using depth/normal edge detection
- Recommends the appropriate method based on URP capabilities (inverted hull for URP compatibility, post-process for HDRP)
- Notes URP limitations: no geometry shader support (rules out geometry-shader outline approach)
- Produces a Shader Graph node setup description (inverted hull via vertex offset, or a screen-space depth/normal edge detection), named to its convention (e.g., `SG_Char_Outline`) with reusable logic in a Sub Graph
- Stays in Shader Graph and drops to custom HLSL only if Shader Graph cannot achieve the effect; any HLSL keeps SRP Batcher compatibility (`UnityPerMaterial` CBUFFER)
- For a screen-space variant, uses a URP `ScriptableRendererFeature` / `ScriptableRenderPass` recorded through the RenderGraph API (`RecordRenderGraph`), not the deprecated `Execute` / CommandBuffer path — per `docs/engine-reference/unity/modules/rendering.md`
- Does NOT produce HDRP-specific nodes without confirming the render pipeline
### Case 2: Out-of-domain redirect
@@ -35,34 +32,33 @@ No gate IDs assigned.
- Does NOT produce UI implementation code
- Explicitly states that UI implementation belongs to `ui-programmer` (or `unity-ui-specialist`)
- Redirects the request appropriately
- May note that a shader-based fill effect for a health bar (e.g., a dissolve/fill gradient) is within its domain if the visual effect itself is shader-driven
- Any part it keeps is the shader-driven effect only (e.g., a dissolve or fill gradient on the bar) — it writes no health-bar UI code
### Case 3: HDRP custom pass for outline
**Input:** "We're on HDRP and want the outline as a post-process effect."
**Expected behavior:**
- Produces the HDRP `CustomPassVolume` pattern:
- C# class inheriting `CustomPass`
- `Execute()` method using `CoreUtils.SetRenderTarget()` and a full-screen shader blit
- Depth/normal buffer sampling for edge detection
- Produces the HDRP custom pass pattern its own standards name: a C# class inheriting `CustomPass`, run from a Custom Pass Volume, drawing a full-screen edge-detection shader that samples the depth/normal buffers
- Uses the HDRP overrides `docs/engine-reference/unity/current-best-practices.md` documents — `Setup(ScriptableRenderContext, CommandBuffer)`, `Execute(CustomPassContext ctx)`, `Cleanup()` — never URP's `RecordRenderGraph`, and marks any other HDRP member it uses (render-target helpers, `CustomPassContext` fields) unverified — nothing beyond those three is asserted from memory
- Notes that CustomPass requires HDRP package and does not work in URP
- Confirms the project is on HDRP before providing HDRP-specific code
### Case 4: VFX Graph performance — GPU event batching
### Case 4: VFX Graph performance — particle budget
**Input:** "The explosion VFX Graph has 10,000 particles per event and spawning 20 simultaneous explosions is causing GPU frame spikes."
**Expected behavior:**
- Identifies GPU particle spawn as the cost driver (200,000 simultaneous particles)
- Proposes GPU event batching: spawn events deferred over multiple frames, stagger initialization
- Recommends a particle budget cap per active explosion (e.g., 3,000 per explosion, queue excess)
- Notes the VFX Graph Event Batcher pattern and Output Event API for cross-frame distribution
- Sets a particle capacity limit per effect and sizes it against the < 2ms total VFX GPU budget
- Pools explosion VFX instances and triggers them through events, rather than creating a graph per explosion
- Reduces cost for distant or off-screen explosions (particle LOD, bounds-based culling) and scales counts by quality tier
- Avoids any fix that reads GPU particle data back to the CPU
- Does NOT change the gameplay event system — proposes a VFX-side budgeting solution
### Case 5: Context pass — render pipeline (URP or HDRP)
**Input:** Project context: URP render pipeline, Unity 2022.3. Request: "Add depth of field post-processing."
**Input:** Project context: URP render pipeline, targets PC and Nintendo Switch. Request: "Add depth of field post-processing."
**Expected behavior:**
- Uses URP Volume framework: `DepthOfField` Volume Override component
- Uses URP Volume framework: a `DepthOfField` override in a Volume Profile, with script access through `profile.TryGet<>()` as `docs/engine-reference/unity/modules/rendering.md` shows
- Does NOT use HDRP Volume components (e.g., HDRP's `DepthOfField` with different parameter names)
- Notes URP-specific DOF limitations vs HDRP (e.g., Bokeh quality differences)
- Produces C# Volume profile setup code compatible with Unity 2022.3 URP package version
- Places it on the Global Volume for the baseline look or a local Volume for an area, with priority and blend distance set
- Gates the effect per platform and quality tier — reduced or disabled on Switch — and keeps post-processing inside its 1-2ms frame budget
---
@@ -72,8 +68,9 @@ No gate IDs assigned.
- [ ] Redirects gameplay and UI code to appropriate agents
- [ ] Returns structured output (node graph descriptions, HLSL code, CustomPass patterns)
- [ ] Distinguishes between URP and HDRP approaches — never cross-contaminates pipeline-specific APIs
- [ ] Flags geometry shader approaches as URP-incompatible when relevant
- [ ] Uses the RenderGraph API for custom URP passes, not the deprecated `Execute` / CommandBuffer path
- [ ] Produces VFX optimizations that do not change gameplay behavior
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
@@ -3,7 +3,6 @@
## Agent Summary
Domain: Unity-specific architecture patterns, MonoBehaviour vs DOTS decisions, and subsystem selection (Addressables, New Input System, UI Toolkit, Cinemachine, etc.).
Does NOT own: language-specific deep dives (delegates to unity-dots-specialist, unity-ui-specialist, etc.).
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references Unity patterns / MonoBehaviour / subsystem decisions)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is `inherit` — frontmatter `model:` reads exactly `inherit` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition acknowledges the sub-specialist routing table (DOTS, UI, Shader, Addressables)
---
@@ -33,51 +32,54 @@ No gate IDs assigned.
**Input:** "Set up a Node scene tree with signals for this enemy system."
**Expected behavior:**
- Does NOT produce Godot Node/signal code
- Identifies this as a Godot pattern
- States that in Unity the equivalent is GameObject hierarchy + UnityEvent or C# events
- Maps the concepts: Godot Node → Unity MonoBehaviour, Godot Signal → C# event / UnityEvent
- Confirms the project is Unity-based before proceeding
- Identifies node trees and signals as Godot concepts
- Confirms the project's engine from `engine.name` in `project.yaml` rather than guessing; if it is not Unity, says it is the wrong specialist for this project instead of translating
- If `engine.name` is unset, says no engine is configured and asks which engine the project uses — it does not assume Unity
- In a Unity project, maps the concepts: node tree → GameObject hierarchy with composed MonoBehaviours; signal → C# event or UnityEvent (consistent with its own rule to use events instead of `SendMessage()` / `Find()`)
- Does not hand the request to a Godot specialist — its Agent grant covers only the four Unity sub-specialists
### Case 3: Unity version API flag
**Input:** "Use the new Unity 6 GPU resident drawer for batch rendering."
**Expected behavior:**
- Identifies the Unity 6 feature (GPU Resident Drawer)
- Flags that this API may not be available in earlier Unity versions
- Asks for or checks the project's Unity version before providing implementation guidance
- Directs to verify against official Unity 6 documentation
- Does NOT assume the project is on Unity 6 without confirmation
- Checks `docs/engine-reference/unity/VERSION.md` and `docs/engine-reference/unity/modules/rendering.md` before answering, not training data (the version file places Unity 6 past the model's knowledge cutoff)
- States what the reference documents: GPU Resident Drawer is a Unity 6+ feature enabled in the URP Asset (Rendering > GPU Resident Drawer), so it requires an SRP project
- Flags the installed-version gap: when `Installed at pin time` in the version file is not determined, asks which editor version is installed before recommending the feature
- Does NOT assume the installed editor is Unity 6 because the reference is pinned to it
- Proposes the URP Asset change and asks before editing it; routes render pipeline customization beyond the setting to `unity-shader-specialist`
### Case 4: DOTS vs. MonoBehaviour conflict
**Input:** "The combat system uses MonoBehaviour for state management, but we want to add a DOTS-based projectile system. Can they coexist?"
**Expected behavior:**
- Recognizes this as a hybrid architecture scenario
- Explains the hybrid approach: MonoBehaviour can interface with DOTS via SystemAPI, IComponentData, and managed components
- Explains the coexistence mechanism at architecture level, as `docs/engine-reference/unity/plugins/dots-entities.md` documents it: authoring MonoBehaviours baked into entities by a `Baker<T>`, projectile data held in `IComponentData`, processed by an `ISystem`
- Notes the performance and complexity trade-offs of mixing the two patterns
- Recommends escalating the architecture decision to `lead-programmer` or `technical-director`
- Defers to `unity-dots-specialist` for the DOTS-side implementation details
### Case 5: Context pass — Unity version
**Input:** Project context provided: Unity 2023.3 LTS. Request: "Configure the new Input System for this project."
**Input:** Project context provided: Unity 6.3 LTS, keyboard/mouse and gamepad targets. Request: "Configure the new Input System for this project."
**Expected behavior:**
- Applies Unity 2023.3 LTS context: uses the New Input System (com.unity.inputsystem) package
- Does NOT produce legacy Input Manager code (`Input.GetKeyDown()`, `Input.GetAxis()`)
- Notes any 2023.3-specific Input System behaviors or package version constraints
- References the project version to confirm Burst/Jobs compatibility if the Input System interacts with DOTS
- Applies the Unity 6.3 LTS context from `docs/engine-reference/unity/modules/input.md`: the Input System package (`com.unity.inputsystem`) with Active Input Handling set to the new system
- Does NOT produce legacy Input Manager code (`Input.GetKeyDown()`, `Input.GetAxis()`) — the engine reference lists these as deprecated
- Defines actions in an `.inputactions` asset, uses the Player Input component or a generated C# class, and prefers action callbacks (`performed`, `canceled`) over polling in `Update()`
- Sets up keyboard+mouse and gamepad control schemes with automatic switching
- Routes UI navigation and input-prompt work to `unity-ui-specialist`
- Treats adding the package as a dependency decision needing `technical-director` sign-off, and asks before writing any settings or asset
---
## Protocol Compliance
- [ ] Stays within declared domain (Unity architecture decisions, pattern selection, subsystem routing)
- [ ] Redirects Godot patterns to appropriate Godot specialists or flags them as wrong-engine
- [ ] Treats Godot patterns as wrong-engine: checks `engine.name`, answers with the Unity equivalent, never emits Godot code
- [ ] Redirects DOTS implementation to unity-dots-specialist
- [ ] Redirects UI implementation to unity-ui-specialist
- [ ] Flags Unity version-gated APIs and requires version confirmation before suggesting them
- [ ] Checks `docs/engine-reference/unity/` (pinned version and `Installed at pin time`) before suggesting a version-gated API, and confirms the installed version when it is not recorded
- [ ] Returns structured pattern decision guides, not freeform opinions
---
## Coverage Notes
- MonoBehaviour vs. ScriptableObject (Case 1) should be documented as an ADR if it results in a project-level decision
- Version flag (Case 3) confirms the agent does not assume the latest Unity version without context
- Version flag (Case 3) confirms the agent checks the pinned and installed Unity version rather than assuming the latest
- DOTS hybrid (Case 4) verifies the agent escalates architecture conflicts rather than resolving them unilaterally
@@ -3,7 +3,6 @@
## Agent Summary
Domain: Unity UI Toolkit (UXML/USS), UGUI (Canvas), data binding, runtime UI performance, and UI input event handling.
Does NOT own: UX flow design (ux-designer), visual art style (art-director).
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references UI Toolkit / UGUI / Canvas / data binding)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over UX flow design or visual art direction
---
@@ -24,7 +23,7 @@ No gate IDs assigned.
**Expected behavior:**
- Produces a UXML document defining the inventory panel structure (ListView, item templates, detail panel)
- Produces USS styles for the inventory layout and item states (default, hover, selected)
- Provides C# code binding the inventory data model to the UI via `INotifyValueChanged` or `IBindable`
- Provides C# code binding the inventory data model to the UI through the runtime binding system: a ViewModel implementing `INotifyBindablePropertyChanged`, with the UI reading through bindings and never writing game state
- Uses `ListView` with `makeItem` / `bindItem` callbacks for the scrollable item list
- Does NOT produce the UX flow design — implements from a provided spec
@@ -39,28 +38,29 @@ No gate IDs assigned.
### Case 3: UI Toolkit data binding for dynamic list
**Input:** "The inventory list needs to update in real time as items are added or removed from the player's bag."
**Expected behavior:**
- Produces the `ListView` pattern with a bound `ObservableList<T>` or event-driven refresh approach
- Uses `ListView.Rebuild()` or `ListView.RefreshItems()` on the backing collection change event
- Notes the performance considerations for large lists (virtualization via `makeItem`/`bindItem` pattern)
- Does NOT use `QuerySelector` loops to update individual elements as a list refresh strategy — flags that as a performance antipattern
- Produces the `ListView` pattern over the inventory's backing collection, virtualized with `makeItem` / `bindItem` so only visible rows exist
- Drives the refresh from the inventory's change event (game state → ViewModel → binding), not from per-frame polling
- Does NOT refresh the list by querying the visual tree for each element — flags per-frame visual-tree queries as an antipattern and caches references instead
- Does NOT create and destroy row elements on each change — reuses rows through the virtualized list
### Case 4: Canvas performance — overdraw
**Input:** "The main menu canvas is causing GPU overdraw warnings; there are many overlapping panels."
**Expected behavior:**
- Identifies overdraw causes: multiple stacked canvases, full-screen overlay panels not culled when inactive
- Recommends:
- Separate canvases for world-space, screen-space-overlay, and screen-space-camera layers
- Disable/deactivate panels instead of setting alpha to 0 (invisible alpha-0 panels still draw)
- Canvas Group + alpha for fade effects, not individual Image alpha
- Notes UI Toolkit alternative if the project is in a migration position
- Confirms the hotspot with a profiler before changing structure (Frame Debugger, Profiler UI module)
- Recommends its documented Canvas remediation:
- One Canvas per logical UI layer, with frequently changing and static content on separate Canvases
- Explicit `Canvas.sortingOrder` rather than hierarchy order
- Shared sprite atlases so panels batch, and Raycast Target disabled on non-interactive elements
- A `CanvasGroup` to fade or hide a group of elements, not per-Image alpha changes
- Notes that screen-space menus are UI Toolkit territory under its own selection rules (the engine reference also marks UGUI Canvas as superseded for new projects), offered as an option rather than a unilateral rewrite
### Case 5: Context pass — Unity version
**Input:** Project context: Unity 2022.3 LTS. Request: "Implement the settings panel with data binding."
**Expected behavior:**
- Uses UI Toolkit with the 2022.3 LTS version of the runtime binding system
- Notes that Unity 2022.3 introduced runtime data binding (as opposed to editor-only binding in earlier versions)
- Does NOT use the Unity 6 enhanced binding API features if they are not available in 2022.3
- Produces code compatible with the stated Unity version, with version-specific API notes
- Checks the stated version against `docs/engine-reference/unity/VERSION.md`, which pins Unity 6.3 LTS, and asks which editor is actually installed before choosing a binding API
- Does NOT state from memory which Unity version introduced runtime data binding — the engine reference does not document it, so the claim is flagged as unverified
- Does NOT emit the runtime binding system (`INotifyBindablePropertyChanged`) for a 2022.3 editor until that API is confirmed available there
- Keeps the version-independent parts of its pattern regardless: UI reads state, user actions dispatch commands, the panel never writes game state directly
---
@@ -69,13 +69,14 @@ No gate IDs assigned.
- [ ] Stays within declared domain (UI Toolkit, UGUI, data binding, UI performance)
- [ ] Redirects UX flow design to ux-designer
- [ ] Returns structured output (UXML, USS, C# binding code)
- [ ] Uses the correct Unity UI framework version for the project's Unity version
- [ ] Flags Canvas overdraw as a performance antipattern and provides specific remediation
- [ ] Does not use alpha-0 as a hide/show pattern — uses SetActive() or VisualElement.style.display
- [ ] Checks the project's Unity version against `docs/engine-reference/unity/VERSION.md` before using version-dependent UI APIs, and asks when the stated version and the pin disagree
- [ ] Flags Canvas overdraw as a performance antipattern and provides specific remediation (Canvas split by layer and update rate, atlasing, Raycast Target off)
- [ ] Hides elements with its documented mechanism — `VisualElement.visible = false` in UI Toolkit, a `CanvasGroup` in UGUI — never by setting each element's alpha to 0
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
## Coverage Notes
- Inventory UI (Case 1) should have a manual walkthrough doc in `production/qa/evidence/`
- Inventory UI (Case 1) should have a retained screenshot of each screen touched in `production/qa/evidence/`
- Dynamic list binding (Case 3) should have an integration test or automated interaction test
- Canvas overdraw (Case 4) verifies the agent knows the correct Unity UI performance patterns
@@ -2,17 +2,16 @@
## Agent Summary
- **Domain**: Blueprint architecture, the Blueprint/C++ boundary, Blueprint graph quality, Blueprint performance optimization, Blueprint Function Library design
- **Does NOT own**: C++ implementation (engine-programmer or gameplay-programmer), art assets or shaders, UI/UX flow design (ux-designer)
- **Model tier**: Sonnet
- **Gate IDs**: None; defers to unreal-specialist or lead-programmer for cross-domain rulings
- **Does NOT own**: C++ implementation (gameplay-programmer exposes the C++ hooks Blueprint calls), art assets or shaders, UI/UX flow design (ux-designer)
- **Gate IDs**: None; defers C++/Blueprint boundary architecture rulings to unreal-specialist
---
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references Blueprint architecture and optimization)
- [ ] `allowed-tools:` list matches the agent's role (Read for Blueprint project files; no server or deployment tools)
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list matches the agent's role (Read/Write/Edit for Blueprint-facing files; no server or deployment tools, and Bash withheld via `disallowedTools:`)
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over C++ implementation decisions
---
@@ -23,26 +22,25 @@
**Input**: "Review our AI behavior Blueprint. It has tick-based logic running every frame that checks line-of-sight for 30 NPCs simultaneously."
**Expected behavior**:
- Identifies tick-heavy logic as a performance problem
- Recommends switching from EventTick to event-driven patterns (perception system events, timers, or polling on a reduced interval)
- Flags the per-NPC cost of simultaneous line-of-sight checks
- Suggests alternatives: AIPerception component events, staggered tick groups, or moving the system to C++ if Blueprint overhead is measured to be significant
- Output is structured: problem identified, impact estimated, alternatives listed
- Recommends switching from EventTick to event-driven patterns (perception system events, timers, or polling on a reduced interval) — never polling every frame when an event would suffice
- Flags the per-NPC cost of simultaneous line-of-sight checks, including any loop over the NPC array inside Tick
- Recommends profiling first (`stat game`, Blueprint profiler), and treats moving the system to C++ as warranted when the measured Blueprint overhead is significant — 30 NPCs is below its automatic C++ threshold (tick logic with more than 100 instances)
- Output names, for each finding, the Performance Rule or Review Checklist item it breaks and the replacement pattern
### Case 2: Out-of-domain request — C++ implementation
**Input**: "Write the C++ implementation for this ability cooldown system."
**Expected behavior**:
- Does not produce C++ implementation code
- Provides the Blueprint equivalent of the cooldown logic (e.g., using a Timeline or GameplayEffect if GAS is in use)
- States clearly: "C++ implementation is handled by engine-programmer or gameplay-programmer; I can show the Blueprint approach or describe the boundary where Blueprint calls into C++"
- Optionally notes when the cooldown complexity warrants a C++ backend
- Classifies the cooldown system with its boundary rules: ability-system logic is on its "Must Be C++" list, so it does not offer a Blueprint reimplementation of the system
- Describes the boundary instead: C++ defines the framework and exposes hooks (`BlueprintCallable`, `BlueprintNativeEvent`, `BlueprintImplementableEvent`); Blueprint supplies tuning values (`EditAnywhere` / `BlueprintReadWrite`) and simple event responses such as cooldown feedback
- Routes the C++ implementation to gameplay-programmer (its named partner for exposing C++ hooks to Blueprint) and the boundary decision to unreal-specialist, rather than writing the C++ system itself
### Case 3: Domain boundary — unsafe raw pointer access in Blueprint
**Input**: "Our Blueprint calls GetOwner() and then immediately accesses a component on the result without checking if it's valid."
**Expected behavior**:
- Flags this as a runtime crash risk: GetOwner() can return null in some lifecycle states
- Provides the correct Blueprint pattern: IsValid() node before any property/component access
- Flags this as a runtime crash risk under its Review Checklist item "Error/failure paths are handled (not just the happy path)": GetOwner() can return null in some lifecycle states
- Provides the correct Blueprint pattern: IsValid() node before any property/component access, with the invalid branch handled
- Notes that Blueprint's null checks are not optional on Actor-derived references
- Does NOT silently fix the code without explaining why the original was unsafe
- Does NOT silently fix the code — explains why the original was unsafe and asks before editing the Blueprint
### Case 4: Blueprint graph complexity — readiness for Function Library refactor
**Input**: "Our main GameMode Blueprint has 600+ nodes in a single graph with duplicated damage calculation logic in 8 places."
@@ -57,24 +55,25 @@
**Input context**: Project conventions specify a maximum of 100 nodes per Blueprint event graph before a mandatory Function Library extraction.
**Input**: "Here is our inventory Blueprint graph [150 nodes shown]. Is it ready to ship?"
**Expected behavior**:
- References the stated 150-node count against the 100-node budget from project conventions
- References the stated 150-node count against the 100-node event-graph budget from project conventions
- Flags the graph as exceeding the complexity threshold
- Does NOT approve it as-is
- Produces a list of candidate subgraphs for Function Library extraction to bring the main graph within budget
- Applies its own standard (maximum 20 nodes per function graph) to the extracted functions, keeping it distinct from the project's event-graph budget rather than substituting one number for the other
---
## Protocol Compliance
- [ ] Stays within declared domain (Blueprint architecture, performance, graph quality)
- [ ] Redirects C++ implementation requests to engine-programmer or gameplay-programmer
- [ ] Returns structured findings (problem/impact/alternatives format) rather than freeform opinions
- [ ] Routes C++ implementation requests to gameplay-programmer and boundary rulings to unreal-specialist
- [ ] Grounds findings in its own Performance Rules and Blueprint Review Checklist, naming the rule each finding breaks, rather than freeform opinions
- [ ] Enforces Blueprint safety patterns (null checks, IsValid) proactively
- [ ] References project conventions when evaluating graph complexity
- [ ] References project conventions when evaluating graph complexity, alongside its own per-function node limit
---
## Coverage Notes
- Case 3 (null pointer safety) is a safety-critical test — this is a common source of shipping crashes
- Case 5 requires that project conventions include a stated node budget; if none is configured, the agent should note the absence and recommend setting one
- Case 5 requires that project conventions include a stated node budget; if none is configured, the agent falls back to its own standard (maximum 20 nodes per function graph) and says so
- No automated runner; review manually or via `/skill-test`
@@ -2,8 +2,7 @@
## Agent Summary
- **Domain**: Gameplay Ability System (GAS) — abilities (UGameplayAbility), gameplay effects (UGameplayEffect), attribute sets (UAttributeSet), gameplay tags, ability tasks (UAbilityTask), ability specs (FGameplayAbilitySpec), GAS prediction and latency compensation
- **Does NOT own**: UI display of ability state (ue-umg-specialist), net replication of GAS data beyond built-in GAS prediction (ue-replication-specialist), art or VFX for ability feedback (vfx-artist)
- **Model tier**: Sonnet
- **Does NOT own**: UI display of ability state (ue-umg-specialist), net replication of GAS data beyond built-in GAS prediction (ue-replication-specialist), art or VFX for ability feedback (technical-artist)
- **Gate IDs**: None; defers cross-domain calls to the appropriate specialist
---
@@ -11,8 +10,8 @@
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references GAS, abilities, GameplayEffects, AttributeSets)
- [ ] `allowed-tools:` list matches the agent's role (Read/Write for GAS source files; no deployment or server tools)
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` reads Read, Glob, Grep, Write, Edit, Bash — no `Agent` tool
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over UI implementation or low-level net serialization
---
@@ -22,18 +21,18 @@
### Case 1: In-domain request — dash ability with cooldown
**Input**: "Implement a dash ability that moves the player forward 500 units and has a 1.5 second cooldown."
**Expected behavior**:
- Produces a GAS AbilitySpec structure or outline: UGameplayAbility subclass with ActivateAbility logic, an AbilityTask for movement (e.g., AbilityTask_ApplyRootMotionMoveToForce or custom root motion), and a UGameplayEffect for the cooldown
- Produces a GAS AbilitySpec structure or outline: an ability deriving from the project's base ability class (not raw UGameplayAbility) with ActivateAbility logic, an AbilityTask for movement (e.g., AbilityTask_ApplyRootMotionMoveToForce or custom root motion), and a UGameplayEffect for the cooldown
- Cooldown GameplayEffect uses Duration policy with the 1.5s duration and a GameplayTag to block re-activation
- Tags clearly named following a hierarchy convention (e.g., Ability.Dash, Cooldown.Ability.Dash)
- Output includes both the ability class outline and the GameplayEffect definition
### Case 2: Out-of-domain request — GAS state replication
### Case 2: Mixed-domain request — cooldown replication feeding the UI
**Input**: "How do I replicate the player's ability cooldown state to all clients so the UI updates correctly?"
**Expected behavior**:
- Clarifies that GAS has built-in replication for AbilitySpecs and GameplayEffects via the AbilitySystemComponent's replication mode
- Explains the three ASC replication modes (Full, Mixed, Minimal) and when to use each
- For custom replication needs beyond GAS built-ins, explicitly states: "For custom net serialization of GAS data, coordinate with ue-replication-specialist"
- Does NOT attempt to write custom replication code outside GAS's own systems without flagging the domain boundary
- Answers the replication half from GAS built-ins: the cooldown is a Duration Gameplay Effect granting a cooldown tag, replicated through the AbilitySystemComponent
- Explains the three ASC replication modes (Full, Mixed, Minimal) and which clients each one delivers cooldown state to
- Does NOT write custom replication (RepNotify properties or RPCs) for state GAS already replicates — effect-driven attribute changes replicate automatically and must not be double-replicated
- Hands the cooldown-indicator UI to ue-umg-specialist, and names ue-replication-specialist for multiplayer prediction concerns beyond the ASC's built-in handling
### Case 3: Domain boundary — incorrect GameplayTag hierarchy
**Input**: "We have an ability that applies a tag called 'Stunned' and another that checks for 'Status.Stunned'. They're not matching."
@@ -47,19 +46,20 @@
### Case 4: Conflict — attribute set conflict between two abilities
**Input**: "Our Shield ability and our Armor ability both modify a 'DefenseValue' attribute. They're stacking in ways that aren't intended — after both are active, defense goes well above maximum."
**Expected behavior**:
- Identifies this as a GameplayEffect stacking and magnitude calculation problem
- Proposes a resolution using Execution Calculations (UGameplayEffectExecutionCalculation) or Modifier Aggregators to cap the combined result
- Alternatively recommends using Gameplay Effect Stacking policies (Aggregate, None) to prevent unintended additive stacking
- Produces a concrete resolution: either an Execution Calculation class outline or a change to the Modifier Op (Override instead of Additive for the cap)
- Identifies the cause: two different Gameplay Effects both modify DefenseValue, and nothing bounds their combined result
- Resolves it with its own Attribute Set rules: DefenseValue gets a defined min/max, and `PreAttributeChange()` clamps the current value to that range — with `PreAttributeBaseChange()` or `PostGameplayEffectExecute()` for base-value changes from instant effects, as the pinned GAS reference's "Clamping Attributes" says — so no combination of effects can push it past the maximum
- Anything else it proposes (an Execution Calculation, changing how the two effects combine) comes on top of the clamp, not instead of it; it does not offer a stacking policy as the fix — stacking policies govern re-applications of the same effect, not two different effects — and it does not present an Override modifier as a cap
- Does NOT propose removing one of the abilities as the solution
### Case 5: Context pass — designing against an existing attribute set
**Input context**: Project has an existing AttributeSet with attributes: Health, MaxHealth, Stamina, MaxStamina, Defense, AttackPower.
**Input**: "Design a Berserker ability that increases AttackPower by 50% when Health drops below 30%."
**Expected behavior**:
- Uses the existing Health, MaxHealth, and AttackPower attributes — does NOT invent new attributes
- Designs a Passive GameplayAbility (or triggered Effect) that fires on Health change, checks Health/MaxHealth ratio via a GameplayEffectExecutionCalculation or Attribute-Based magnitude
- Uses a Gameplay Cue or Gameplay Tag to track the Berserker active state
- Uses the existing Health, MaxHealth, and AttackPower attributes — does NOT add an attribute without flagging it as a change to the existing AttributeSet and asking first
- Applies the +50% as an Infinite Gameplay Effect with a Modifier on AttackPower — never by writing AttackPower directly
- Reacts to Health changes (e.g. in `PostGameplayEffectExecute()` or an Ability Task waiting on an event) to apply the effect below 30% of MaxHealth and remove it above
- Documents the effect as its standards require: what it modifies, stacking behavior, duration, and removal condition
- Tracks the active state with a hierarchical Gameplay Tag (e.g. `Effect.Buff.Berserk`), not a boolean
- References the actual attribute names from the provided AttributeSet (AttackPower, not "Damage" or "Strength")
---
@@ -67,15 +67,16 @@
## Protocol Compliance
- [ ] Stays within declared domain (GAS: abilities, effects, attributes, tags, ability tasks)
- [ ] Redirects custom replication requests to ue-replication-specialist with clear explanation of boundary
- [ ] Keeps GAS state replication inside the ASC (no double-replication); routes ability UI to ue-umg-specialist and multiplayer prediction concerns to ue-replication-specialist
- [ ] Returns structured findings (ability outline + GameplayEffect definition) rather than vague descriptions
- [ ] Enforces tag hierarchy naming conventions proactively
- [ ] Uses only attributes and tags present in the provided context; does not invent new ones without noting it
- [ ] Uses only attributes and tags present in the provided context; flags any new one as a change to the existing set before adding it
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
## Coverage Notes
- Case 3 (tag hierarchy) is a frequent source of subtle bugs; test whenever tag naming conventions change
- Case 4 requires knowledge of GAS stacking policies — verify this case if the GAS integration depth changes
- Case 4 tests the agent's own clamping rule (`PreAttributeChange()` against a defined min/max, and the base-value hooks for instant effects); stacking policies and Override modifiers do not cap two different effects, so they are accepted only as additions
- Case 5 is the most important context-awareness test; failing it means the agent ignores project state
- No automated runner; review manually or via `/skill-test`
@@ -2,17 +2,16 @@
## Agent Summary
- **Domain**: Property replication (UPROPERTY Replicated/ReplicatedUsing), RPCs (Server/Client/NetMulticast), client prediction and reconciliation, net relevancy and always-relevant settings, net serialization (FArchive/NetSerialize), bandwidth optimization and replication frequency tuning
- **Does NOT own**: Gameplay logic being replicated (gameplay-programmer), server infrastructure and hosting (devops-engineer), GAS-specific prediction (ue-gas-specialist handles GAS net prediction)
- **Model tier**: Sonnet
- **Gate IDs**: None; escalates security-relevant replication concerns to lead-programmer
- **Does NOT own**: Gameplay logic being replicated (gameplay-programmer), transport-layer networking and matchmaking (network-programmer, who coordinates server hosting with devops-engineer), GAS-specific prediction (ue-gas-specialist handles GAS net prediction)
- **Gate IDs**: None; takes security-relevant replication concerns to security-engineer
---
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references replication, RPCs, client prediction, bandwidth)
- [ ] `allowed-tools:` list matches the agent's role (Read/Write for C++ and Blueprint source files; no infrastructure or deployment tools)
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` reads Read, Glob, Grep, Write, Edit, Bash — no `Agent` tool
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over server infrastructure, game server architecture, or gameplay logic correctness
---
@@ -32,7 +31,7 @@
**Input**: "Design our game server infrastructure — how many dedicated servers we need, regional deployment, and matchmaking architecture."
**Expected behavior**:
- Does not produce server infrastructure architecture, hosting recommendations, or matchmaking design
- States clearly: "Server infrastructure and deployment architecture is owned by devops-engineer; I handle the Unreal replication layer within a running game session"
- States that its scope is the Unreal replication layer within a running game session, and names network-programmer (its coordination partner for networking below that layer) as the route for matchmaking and server architecture
- Does not conflate in-game replication with server hosting concerns
### Case 3: Domain boundary — RPC without server authority validation
@@ -40,8 +39,8 @@
**Expected behavior**:
- Flags this as a critical security vulnerability: unvalidated server RPCs are exploitable by cheaters sending arbitrary RPC calls
- Provides the required fix: server-side validation before the deduct — check that the player actually has the currency, verify the transaction is valid, reject and log if not
- Uses the pattern: `if (!HasAuthority()) return;` guard plus explicit state validation before mutation
- Notes this should be reviewed by lead-programmer given the economy implications
- Validation covers the three checks every client RPC needs: can this player perform the action now, are the parameters within valid ranges, and is the request rate within limits (rate-limiting the RPC)
- Notes this should be reviewed by security-engineer, its named partner for network security validation, given the economy implications
- Does NOT produce the "fixed" code without explaining why the original was dangerous
### Case 4: Bandwidth optimization — high-frequency movement replication
@@ -49,18 +48,19 @@
**Expected behavior**:
- Identifies tick-rate replication of full-precision Vector3 as bandwidth-expensive
- Proposes quantized replication: use FVector_NetQuantize or FVector_NetQuantize100 instead of raw FVector to reduce bytes per update
- Recommends reducing replication frequency via SetNetUpdateFrequency() for non-owning clients
- Recommends lowering how often the movement actor replicates — a per-actor net update frequency (`NetUpdateFrequency` in its standards), not a per-client setting — naming the API it uses and marking it unverified unless `docs/engine-reference/unreal/` documents it
- Notes that Unreal's built-in Character Movement Component already has optimized movement replication — recommends using or extending it rather than rolling a custom system
- Produces a concrete bandwidth estimate comparison if possible, or explains the tradeoff
- Measures the result against its per-client target (under 10 KB/s for action games) and names the tools to confirm the saving (`stat net`, Network Profiler)
### Case 5: Context pass — designing within a network budget
**Input context**: Project network budget is 64 KB/s per player, with 32 players = 2 MB/s total server outbound. Current movement replication already uses 40 KB/s per player.
**Input**: "We want to add real-time inventory replication so all clients can see other players' equipment changes immediately."
**Expected behavior**:
- Acknowledges the existing 40 KB/s movement cost leaves only 24 KB/s for everything else per player
- Notes that these project figures sit well above its own default target (under 10 KB/s per client for action games) and works to the project's stated budget while flagging the gap
- Does NOT design a naive full-inventory replication approach (would exceed budget)
- Recommends a delta-only or event-driven approach: replicate only changed slots rather than the full inventory array
- Uses FGameplayItemSlot or equivalent with ReplicatedUsing to trigger targeted updates
- Separates what other players see (equipped items, replicated to all) from the full inventory (`COND_OwnerOnly`)
- Recommends a delta-only or event-driven approach: replicate only changed slots rather than the full inventory array, with `ReplicatedUsing` to trigger targeted updates
- Explicitly states the proposed approach's bandwidth estimate relative to the remaining 24 KB/s budget
---
@@ -68,10 +68,11 @@
## Protocol Compliance
- [ ] Stays within declared domain (property replication, RPCs, client prediction, bandwidth)
- [ ] Redirects server infrastructure requests to devops-engineer without producing infrastructure design
- [ ] Flags unvalidated server RPCs as security issues and recommends lead-programmer review
- [ ] Routes server infrastructure and matchmaking requests to network-programmer without producing infrastructure design
- [ ] Flags unvalidated server RPCs as security issues and recommends security-engineer review
- [ ] Returns structured findings (property declarations, bandwidth estimates, optimization options) not freeform advice
- [ ] Uses project-provided bandwidth budget numbers when evaluating replication design choices
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
@@ -1,9 +1,8 @@
# Agent Test Spec: ue-umg-specialist
## Agent Summary
- **Domain**: UMG widget hierarchy design, data binding patterns, CommonUI input routing and action tags, widget styling (WidgetStyle assets), UI optimization (widget pooling, ListView, invalidation)
- **Domain**: UMG widget hierarchy design, data binding patterns, CommonUI input routing and input action data, widget styling (WidgetStyle assets), UI optimization (widget pooling, ListView, invalidation)
- **Does NOT own**: UX flow and screen navigation design (ux-designer), gameplay logic (gameplay-programmer), backend data sources (game code), server communication
- **Model tier**: Sonnet
- **Gate IDs**: None; defers UX flow decisions to ux-designer
---
@@ -11,8 +10,8 @@
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references UMG, widget hierarchy, CommonUI)
- [ ] `allowed-tools:` list matches the agent's role (Read/Write for UI assets and Blueprint files; no server or gameplay source tools)
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` reads Read, Glob, Grep, Write, Edit, Bash — no `Agent` tool
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over UX flow, navigation architecture, or gameplay data logic
---
@@ -23,7 +22,7 @@
**Input**: "Create an inventory widget that shows a grid of item slots. Each slot should display item icon, quantity, and rarity color. It needs to update when the inventory changes."
**Expected behavior**:
- Produces a UMG widget structure: a parent WBP_Inventory containing a UniformGridPanel or TileView, with a child WBP_InventorySlot widget per item
- Describes data binding approach: either Event Dispatchers on an Inventory Component triggering a refresh, or a ListView with a UObject item data class implementing IUserObjectListEntry
- Describes data binding approach: either Event Dispatchers on an Inventory Component triggering a refresh, or a TileView fed UObject-based item data (not raw structs); it names the entry widget's list-entry interface (`IUserObjectListEntry`) and marks it unverified against `docs/engine-reference/unreal/`, which does not document it
- Specifies how rarity color is driven: a WidgetStyle asset or a data table lookup, not hardcoded color values
- Output includes the widget hierarchy, binding pattern, and the refresh trigger mechanism
@@ -31,15 +30,15 @@
**Input**: "Design the full navigation flow for our inventory system — how the player opens it, transitions to character stats, and exits to the pause menu."
**Expected behavior**:
- Does not produce a navigation flow or screen transition architecture
- States clearly: "Navigation flow and screen transition design is owned by ux-designer; I can implement the UMG widget structure once the flow is defined"
- Does not make UX decisions (back button behavior, transition animations, modal vs. fullscreen) without a UX spec
- Names ux-designer, its coordination partner for interaction design, as the owner of the flow, and offers to build the screens on its layered architecture (Menu Layer, `UCommonActivatableWidgetStack`) once the flow is defined
- Does not make UX decisions (back button behavior, transition animations, modal vs. fullscreen) without a UX spec — it asks for the spec or lists these as open questions instead
### Case 3: Domain boundary — CommonUI input action mismatch
**Input**: "Our inventory widget isn't responding to the controller Back button. We're using CommonUI."
**Expected behavior**:
- Identifies the likely cause: the widget's Back input action tag does not match the project's registered CommonUI InputAction data asset
- Explains the CommonUI input routing model: widgets declare input actions via `CommonUI_InputAction` tags; the CommonActivatableWidget handles routing
- Provides the fix: verify that the widget's Back action tag matches the registered tag in the project's CommonUI input action data table
- Identifies the likely causes: the widget's Back binding does not point at the Back row of the project's `CommonInputActionDataBase` data table, or the widget is not the activated, focused screen on its stack
- Explains the CommonUI input routing model: screens derive from `UCommonActivatableWidget` and bind UI actions to data-table rows; only the focused, activated widget consumes input and unfocused widgets ignore it
- Provides the fix: verify the Back binding's data-table row and that the widget is pushed onto (and active on) its activatable-widget stack
- Distinguishes this from a hardware input binding issue (which would be Enhanced Input territory)
### Case 4: Widget performance issue — many widget instances per frame
@@ -47,28 +46,29 @@
**Expected behavior**:
- Identifies the root cause: 500 widget instantiations in a single frame causes a construction hitch
- Recommends switching to ListView or TileView with virtualization — only visible rows are constructed
- Explains the IUserObjectListEntry interface requirement for ListView data objects
- Explains the ListView data requirement: each row's data is a UObject-based entry item, not a raw struct; it names the row widget's list-entry interface (`IUserObjectListEntry`) and marks it unverified against `docs/engine-reference/unreal/`
- If ListView is not appropriate, recommends pooling: pre-instantiate a fixed number of rows and recycle them with new data
- Output is a concrete recommendation with the specific UMG component to use, not a vague "optimize it"
### Case 5: Context pass — CommonUI setup already configured
**Input context**: Project uses CommonUI with the following registered InputAction tags: UI.Action.Confirm, UI.Action.Back, UI.Action.Pause, UI.Action.Secondary.
**Input context**: Project uses CommonUI; its `CommonInputActionDataBase` data table defines these input action rows: Confirm, Back, Pause, Secondary.
**Input**: "Add a 'Sort Inventory' button to the inventory widget that works with CommonUI."
**Expected behavior**:
- Uses UI.Action.Secondary (or recommends registering a new tag like UI.Action.Sort if Secondary is already allocated)
- Does NOT invent a new InputAction tag without noting that it must be registered in the CommonUI data table
- Does NOT use a non-CommonUI input binding approach (e.g., raw key press in Event Graph) when CommonUI is the established pattern
- References the provided tag list explicitly in the recommendation
- Uses the existing Secondary row (or recommends adding a new Sort row if Secondary is already allocated on this screen)
- Does NOT invent a new input action without noting that it must be added as a row to the project's CommonUI input action data table
- Uses a `UCommonButtonBase` for the button and CommonUI's input routing — not a non-CommonUI binding (e.g., raw key press in Event Graph or `APlayerController::InputComponent`)
- References the provided action rows explicitly in the recommendation
---
## Protocol Compliance
- [ ] Stays within declared domain (UMG structure, data binding, CommonUI, widget performance)
- [ ] Redirects UX flow and navigation design requests to ux-designer
- [ ] Redirects UX flow and navigation design requests to ux-designer, making no binding UX decisions itself
- [ ] Returns structured findings (widget hierarchy + binding pattern) rather than freeform opinions
- [ ] Uses existing CommonUI InputAction tags from context; does not invent new ones without flagging registration requirement
- [ ] Uses existing CommonUI input action rows from context; does not invent new ones without flagging that they must be added to the data table
- [ ] Recommends virtualized lists (ListView/TileView) before widget pooling for large collections
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
@@ -3,7 +3,6 @@
## Agent Summary
- **Domain**: Unreal Engine patterns and architecture — Blueprint vs C++ decisions, UE subsystems (GAS, Enhanced Input, Niagara), UE project structure, plugin integration, and engine-level configuration
- **Does NOT own**: Art style and visual direction (art-director), server infrastructure and deployment (devops-engineer), UI/UX flow design (ux-designer)
- **Model tier**: Sonnet
- **Gate IDs**: None; defers gate verdicts to technical-director
---
@@ -11,8 +10,8 @@
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references Unreal Engine)
- [ ] `allowed-tools:` list matches the agent's role (Read, Write for UE project files; no deployment tools)
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` reads Read, Glob, Grep, Write, Edit, Bash and `Agent(ue-gas-specialist, ue-blueprint-specialist, ue-replication-specialist, ue-umg-specialist)` — it may spawn only its four sub-specialists
- [ ] Model tier is `inherit` — frontmatter `model:` reads exactly `inherit` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority outside its declared domain (no art, no server infra)
---
@@ -22,26 +21,27 @@
### Case 1: In-domain request — Blueprint vs C++ decision criteria
**Input**: "Should I implement our combo attack system in Blueprint or C++?"
**Expected behavior**:
- Provides structured decision criteria: complexity, reuse frequency, team skill, and performance requirements
- Recommends C++ for systems called every frame or shared across 5+ ability types
- Recommends Blueprint for designer-tunable values and one-off logic
- Applies its stated default: C++ for the combo system's framework, Blueprint for content and prototyping
- Routes the combat logic through GAS (combo attacks as Gameplay Abilities, combo state as Gameplay Tags, montage flow via Ability Tasks) and names ue-gas-specialist for the GAS design rather than implementing it itself
- Recommends Blueprint for designer-tunable values (exposed with `EditAnywhere` / `BlueprintReadWrite`, data-only Blueprints for combo variations, `BlueprintNativeEvent` where designers override behavior)
- Cites its ~20-nodes-per-function threshold as the point where Blueprint combo logic belongs in C++
- Does NOT render a final verdict without knowing project context — asks clarifying questions if context is absent
- Output is structured (criteria table or bullet list), not a freeform opinion
- Presents the split as a proposed class structure with its trade-offs, not a freeform opinion
### Case 2: Out-of-domain request — Unity C# code
**Input**: "Write me a C# MonoBehaviour that handles player health and fires a Unity event on death."
**Expected behavior**:
- Does not produce Unity C# code
- States clearly: "This project uses Unreal Engine; the Unity equivalent would be an Actor Component in UE C++ or a Blueprint Actor Component"
- Optionally offers to provide the UE equivalent if requested
- Does not redirect to a Unity specialist (none exists in the framework)
- Confirms the project's engine from `engine.name` in `project.yaml` before answering: with Unreal configured, states that the project is built in Unreal Engine 5; with it unset, asks which engine the project uses rather than assuming
- With Unreal configured, maps the request to the Unreal equivalent its own standards prescribe: health as an Attribute Set attribute changed only through Gameplay Effects when GAS is in use, otherwise a C++ `UActorComponent` exposing a death event to Blueprint
- Does not redirect to unity-specialist — that agent serves Unity projects only, and this agent's `Agent` grant covers only its four Unreal sub-specialists
### Case 3: Domain boundary — UE5.4 API requirement
**Input**: "I need to use the new Motion Matching API introduced in UE5.4."
**Expected behavior**:
- Flags that UE5.4 is a specific version with potentially limited LLM training coverage
- Recommends cross-referencing official Unreal docs or the project's engine-reference directory before trusting any API suggestions
- Provides best-effort API guidance with explicit uncertainty markers (e.g., "Verify this against UE5.4 release notes")
- Flags that UE5.4 is past the model's training coverage (`docs/engine-reference/unreal/VERSION.md` lists 5.4 as a post-cutoff, HIGH-risk version)
- Checks `docs/engine-reference/unreal/` before trusting any API suggestion
- States that no file under `docs/engine-reference/unreal/` documents Motion Matching, and labels any class or node names it offers as unverified against the pinned version
- Does NOT silently produce stale or incorrect API signatures without a caveat
### Case 4: Conflict — Blueprint spaghetti in a core system
@@ -67,14 +67,16 @@
## Protocol Compliance
- [ ] Stays within declared domain (Unreal patterns, Blueprint/C++, UE subsystems)
- [ ] Redirects Unity or other-engine requests without producing wrong-engine code
- [ ] Returns structured findings (criteria tables, decision trees, migration plans) rather than freeform opinions
- [ ] Flags version uncertainty explicitly before producing API suggestions
- [ ] Answers Unity or other-engine requests with the Unreal equivalent, never wrong-engine code
- [ ] Proposes class structure, data flow and trade-offs before implementing (Implementation Workflow step 3) rather than freeform opinions
- [ ] Flags version uncertainty explicitly before producing API suggestions, citing `docs/engine-reference/unreal/`
- [ ] Coordinates with lead-programmer for architecture-scale refactors rather than deciding unilaterally
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---
## Coverage Notes
- No automated runner exists for agent behavior tests — these are reviewed manually or via `/skill-test`
- Version-awareness (Case 3, Case 5) is the highest-risk failure mode for this agent; test regularly when engine version changes
- Cases 3 and 5 pass only if the agent is directed to `docs/engine-reference/unreal/`; `CLAUDE.md` imports the Unreal `VERSION.md` only after `/setup-engine` selects Unreal, so the agent's own file must carry that direction
- Case 4 integration with lead-programmer is a coordination test, not a technical correctness test