agents/openai.yaml
interface:
display_name: "Three.js Game Director"
short_description: "Orchestrate complete Three.js games"
default_prompt: "Use $threejs-game-director to build or improve this game, preserve my scope, and route the relevant specialists automatically."
references/asset-recovery.md
# Asset Jobs and Recovery
Use this policy before treating a provider error as permission to downgrade art. User constraints come first: explicitly procedural art, no external services, or a fixed budget do not require a paid generation attempt. Choose asset roles from the design, not a universal asset quota.
## Start Early, Inspect Between Stages
1. Probe credentials without printing their values. A missing process variable is not proof that the user's configured key is missing; use the packaged credential probe. Do not change shell profiles or expose keys in commands, checkpoints, browser code, or reports.
2. Submit only the high-value assets the current design needs. Record provider, task ID, local checkpoint, purpose, and intended runtime path in `artifacts/game-progress.md` immediately. Keep independent gameplay and UI work moving while jobs run.
3. Inspect the concept before submitting image-to-3D. For animated models use the 3D generator's checkpoint and `--stop-after model`, inspect its downloaded preview/model, then resume through rig validation and animation. Inspection is work for the agent, not a routine request for user approval.
4. Test silhouette, scale, materials, and motion in a representative playable scene before producing a large asset family. Preserve useful accepted work when requirements change; mark obsolete jobs and do not automatically regenerate them.
The probe runs in a child shell; `KEY=SET` does not export the key into later tool calls. If the provider helper still reports missing credentials, launch it in the same profile-loaded shell instead of downgrading the asset or copying the key into an argument. For example, on zsh:
```bash
zsh -lc '
source "$HOME/.zprofile" >/dev/null 2>&1 || true
source "$HOME/.zshrc" >/dev/null 2>&1 || true
exec "$@"
' asset-job python3 <threejs-3d-generator-skill-dir>/scripts/threejs_3d_asset.py resume artifacts/hero-job.json
```
Use the corresponding bash profiles for bash; on Windows ensure the agent process inherits the configured user environment. Preserve the working directory and arguments, and never enable shell tracing around secrets.
## Classify Before Recovering
| Evidence | Action |
| --- | --- |
| Credentials genuinely missing after probe | Continue independent work. Use existing licensed assets or a deliberate procedural alternative; disclose the affected asset limitation. Never invent credentials. |
| Authentication/permission rejected | Check the documented key variable and provider permissions without showing secrets. Do not label this a credit problem. |
| Credits exhausted or plan restricts this operation | Preserve task/output IDs, stop paid retries, and identify only the dependent work as blocked. Do not purchase credits or wait indefinitely. |
| Invalid input, unsupported version, preset, or pose | Correct the specific request using the generator's references. Keep working version and rigging workarounds; do not replace them with guessed latest versions. A rejected request is not proof the provider is unavailable. |
| Transient timeout, rate limit, or service failure during status/download | Retry these safe operations with bounded backoff and respect `Retry-After`. Refresh expired download links from task status. Exhausting safe retries leaves the job pending, not permission to submit a duplicate. |
| Submission outcome uncertain (connection lost or ambiguous server response) | Reconcile the accepted task ID/checkpoint or provider task history before any new paid request. If no ID can be recovered, disclose uncertainty and obtain authorization before a potentially duplicate charge. |
| Task succeeded but output is malformed or visually unsuitable | Preserve the output and diagnose the failed stage. A missing rig GLB or failed skeleton validation is a failure, not a successful rig. Retry only that stage within the chosen attempt/budget limit. |
The Tripo helper implements checkpointed tasks and safe-operation retry behavior. Use `resume`, `status`, or `download` for an existing task, not a second `text`/`image` submission. Gemini and ElevenLabs generation commands do not share Tripo's task/checkpoint API: retain existing files and reconcile uncertain requests through the actual provider instead of inventing a resume command.
## Progress and Fallback
Use the runner's available background execution or submit/status/download tools; do not busy-poll. Bound the current wait and leave a recoverable pending job in the project note if the provider remains unavailable. Continue all work that does not depend on it. Ask only when the answer changes cost, constraints, or a material visual choice.
A single transient error is not a completed recovery attempt. After a confirmed blocker or bounded safe recovery, integrate the best alternative consistent with the user's design and state the remaining quality gap. Do not silently relabel placeholders as premium. A procedural-only brief is a valid art direction, not a provider failure.
Native async tools, user steering delivery, and reasoning settings belong to the hosting application. A skill can use exposed capabilities but cannot enable them by adding `async: true` or changing model settings itself.
references/evidence-manifest.md
# Current-Run Evidence
The manifest declares what one verification pass must capture. It checks coverage and file existence, not artistic quality, gameplay correctness, or whether an acknowledged state was implemented faithfully. Inspect the pictures, exercise real input, and check motion when animation matters.
## Declare the Capture Set
Choose states and viewports from the change before capturing. A full desktop/mobile game needs active play in both plus relevant failure, late-game, or stress states. A narrow desktop HUD fix can use only the affected desktop state. Do not drop a required entry because its capture failed. Use a fresh run ID and output directory when code or assets change; never relabel old reports as new evidence.
Write `artifacts/evidence.json` in the game project:
```json
{
"version": 1,
"runId": "pass-1",
"captures": [
{
"mode": "desktop",
"state": "active-play",
"report": "artifacts/pass-1/desktop-active-play.json"
},
{
"mode": "mobile",
"state": "active-play",
"report": "artifacts/pass-1/mobile-active-play.json"
}
],
"artifacts": ["assets/models/hero.glb", "artifacts/pass-1/locomotion.webm"]
}
```
`captures` must be nonempty, with distinct viewport/state pairs and report paths. `mode` is `desktop` or `mobile`. `state` is an exact hook state or explicitly `null` for an uncontrolled current-view capture; null does not prove active play or a boss encounter. `artifacts` is optional: declare only files this change actually requires. All relative paths resolve from the game project, not the manifest directory. Absolute paths and spaces are supported; project-relative paths are more portable.
## Capture and Check
From the project with its server running:
```bash
node <threejs-qa-release-skill-dir>/scripts/inspect-threejs-canvas.mjs \
--url http://127.0.0.1:5188 --out artifacts/pass-1 \
--state active-play --seed 42 --run-id pass-1
node <threejs-qa-release-skill-dir>/scripts/inspect-threejs-canvas.mjs \
--url http://127.0.0.1:5188 --out artifacts/pass-1 --mobile \
--state active-play --seed 42 --run-id pass-1
python3 <director-skill-dir>/scripts/check_evidence.py . --manifest artifacts/evidence.json
```
Explicit `--state` calls and awaits `setState(name)`. The hook must finish scene setup and return `{ state: name }`. Named captures also require `setPausedForScreenshot(paused)`, which immediately stops simulation/state transitions while rendering continues. The inspector freezes immediately after setup, then awaits visual stabilization and rendered frames within a bounded preparation timeout. Missing hooks, unknown states, timeouts, or mismatched acknowledgments fail. Explicit `--seed` likewise requires a working seed hook. Implement hooks for real project states instead of faking acknowledgments. The inspector records `state`, `requestedState`, `appliedState`, and `runId` alongside existing diagnostics, pixel metrics, and screenshot paths.
Run the standalone inspector from the game directory. It resolves Playwright and PNG dependencies from its own installation or that project's npm packages; if missing, install `@playwright/test` and `pngjs` in the game and the matching Playwright Chromium browser. No npm dependencies need to live inside globally installed skills.
The checker reads only declared reports and verifies run ID, viewport, state fields, successful nonblank inspection, absence of recorded browser errors, and nontrivial artifact files. Historical reports elsewhere are ignored. Motion clips belong in `artifacts`, but their animation quality still needs visual inspection. Builds, input tests, audio, collision, and performance require their own observations; this file checker cannot prove those ran.
## Reports and Legacy Use
Put detailed findings in `artifacts/final-evidence.md` and keep the user-facing close-out concise. Markdown artifact links may be absolute or project-relative; use angle brackets for paths with spaces or parentheses, such as `[motion](<artifacts/pass-1/hero motion.webm>)`.
`check_evidence.py <project> --report <report.md>` remains a basic path/build-presence check. Without `--manifest` it discovers inspector reports across the project, including historical ones, and cannot establish freshness or required capture coverage. Combine `--report` with `--manifest` for linked-file checks scoped to a declared capture set. `--skip-inspector` is legacy file-only use and cannot be combined with `--manifest`.
references/workflow-evaluations.md
# Workflow Evaluations
For maintainers changing the pack, test actions and artifacts rather than instruction length, skill-name mentions, or report keywords. Mocked helper tests prove contracts; they do not establish that a game looks or plays better.
## Scenario Set
| Brief | Expected observable behavior |
| --- | --- |
| Small arcade game, desktop and touch, no premium request | A working loop with real input and retry; all production specialists loaded for the full game, relevant references at phase entry; no forced large art pipeline or unrelated content. |
| Premium animated encounter | Relevant generators loaded, scoped asset plan and credentials checked, concepts/models inspected before paid dependent stages, real assets integrated at gameplay scale, motion evidence and complete declared captures. |
| Narrow HUD spacing fix in an existing premium game | Preserve gameplay and art; load UI and affected QA guidance; targeted layout/build checks instead of new heroes or a full release audit. |
| Explicitly procedural premium racer | Honor procedural art without probing or submitting paid jobs just to satisfy a rule; authored forms, readable camera, measured graphics, genre-interpreted scorecard. |
| Premium game with no provider credentials | Probe accurately without exposing secrets; continue the playable loop and honest fallback art; do not pause unrelated work, invent jobs, or claim generated assets. |
| User changes hero requirements while a generation job is pending | Record the correction, preserve the task ID/checkpoint, continue independent work, reconcile completion, and do not repeat completed jobs or charge for a replacement without scope/budget justification. |
## Reproducible Comparison
1. Save the baseline pack revision and candidate diff. Run both in fresh, isolated game directories with the same prompt, seed, installed dependencies, browser/GPU, credential availability, and model/reasoning settings. Use the same scripted correction and mocked provider responses for recovery cases.
2. Collect tool actions, completed gameplay behaviors, integrated asset paths/task IDs, captures, motion samples, renderer/physics counts, build/test results, and elapsed time. Count unnecessary stops, duplicate submissions, and repeated verification separately from necessary recovery.
3. Compare playable results at the same game state and camera scale. Score with the actual ten-category rubric, interpreted through the genre. Inspect motion unpaused. Record quality regressions even if the candidate is faster or uses fewer tokens.
4. Report what was exercised and what remains untested. A routing rehearsal is not an end-to-end game build; a mocked rig is not provider-quality evidence. Do not claim visual improvement until comparable rendered games support it.
Start with the offline helper tests and a generated-scaffold build/browser pass. Use real paid generation only when authorized and useful; record its cost separately. Test both hosts when available rather than inferring Claude Code behavior from a Codex run. Keep evaluation artifacts outside the installed skills and retain only reproducible tests and concise findings in the repository.
scripts/check_evidence.py
#!/usr/bin/env python3
"""Verify that a Three.js game report's evidence actually exists on disk.
Manifest mode checks a declared capture set from one run. Legacy report mode
checks cited files and discovered inspector reports; it cannot establish capture
coverage, freshness, visual quality, or that a build command succeeded.
python3 check_evidence.py ./my-game
python3 check_evidence.py ./my-game --report artifacts/final-evidence.md
python3 check_evidence.py ./my-game --manifest artifacts/evidence.json
Exit 0 when every cited artifact resolves; exit 1 with the specific failures.
"""
from __future__ import annotations
import argparse
import json
import re
import shlex
import sys
from pathlib import Path
from urllib.parse import unquote, urlsplit
# Extensions worth resolving, with the byte floor below which a file is a stub
# rather than evidence. A 0-byte PNG resolves but proves nothing; these floors
# are set just above "empty or truncated write" for each format.
MEDIA_FLOORS = {
".png": 1024,
".jpg": 1024,
".jpeg": 1024,
".webp": 512,
".gif": 512,
".glb": 1024,
".gltf": 512,
".fbx": 1024,
".obj": 512,
".mp3": 512,
".wav": 512,
".ogg": 512,
".m4a": 512,
".mp4": 1024,
".webm": 1024,
".json": 2,
}
# A path-like token: at least one directory separator, ending in a known
# extension. Requiring the separator keeps prose words with dots out.
PATH_TOKEN = re.compile(
r"(?<![\w/.:~-])((?:/|~/|\.{1,2}/)?(?:[\w.-]+/)+[\w.-]+\.(?:"
+ "|".join(ext.lstrip(".") for ext in MEDIA_FLOORS)
+ r"))(?![\w/])",
re.IGNORECASE,
)
MARKDOWN_LINK = re.compile(r"!?\[[^\]\n]*\]\(\s*(?:<([^>\n]+)>|([^\n]+?))\s*\)")
INLINE_CODE = re.compile(r"`([^`\n]+)`")
COMMAND_NAMES = {"python", "python3", "node", "npm", "npx", "uv", "bash", "sh", "zsh", "powershell", "pwsh"}
BUILD_CLAIM = re.compile(
r"production build|npm run build|vite build|preview server|dist/", re.IGNORECASE
)
# Paths that name the tooling rather than the game's own evidence.
SKIP_PREFIXES = ("node_modules/", "skills/", "scripts/", "src/", "tests/")
def find_paths(report_text: str) -> list[str]:
"""Read link/code destinations intact, then unquoted legacy path tokens."""
candidates: list[tuple[int, str]] = []
remaining = list(report_text)
for pattern in (MARKDOWN_LINK, INLINE_CODE):
for match in pattern.finditer("".join(remaining)):
raw = next(group for group in match.groups() if group is not None).strip()
if pattern is INLINE_CODE:
try:
words = shlex.split(raw)
except ValueError:
words = []
is_command = bool(words) and (
words[0] in COMMAND_NAMES or any(word.startswith("--") for word in words[1:])
)
if is_command or raw.startswith(("https://", "http://")):
remaining[match.start():match.end()] = " " * (match.end() - match.start())
continue
if pattern is MARKDOWN_LINK:
raw = re.sub(r"\s+[\"'].*[\"']$", "", raw)
parsed = urlsplit(raw)
if parsed.scheme or parsed.netloc:
raw = ""
else:
raw = unquote(parsed.path)
if Path(raw).suffix.lower() in MEDIA_FLOORS:
candidates.append((match.start(), raw))
remaining[match.start():match.end()] = " " * (match.end() - match.start())
elif pattern is MARKDOWN_LINK:
remaining[match.start():match.end()] = " " * (match.end() - match.start())
candidates.extend((match.start(), match.group(1)) for match in PATH_TOKEN.finditer("".join(remaining)))
return list(dict.fromkeys(
candidate for _, candidate in sorted(candidates)
if not candidate.lower().startswith(SKIP_PREFIXES)
))
def resolve(candidate: str, roots: list[Path], *, allow_cwd: bool = True) -> Path | None:
"""Resolve a cited path against each root, then as given."""
expanded = Path(candidate).expanduser()
if expanded.is_absolute():
return expanded if expanded.exists() else None
for root in roots:
resolved = root / expanded
if resolved.exists():
return resolved
direct = Path(candidate)
return direct if allow_cwd and direct.exists() else None
def check_artifacts(paths: list[str], roots: list[Path], *, allow_cwd: bool = True) -> tuple[list[str], list[str]]:
failures: list[str] = []
confirmed: list[str] = []
for candidate in paths:
resolved = resolve(candidate, roots, allow_cwd=allow_cwd)
if resolved is None:
failures.append(f"cited path does not exist: {candidate}")
continue
if not resolved.is_file():
failures.append(f"cited path is not a file: {candidate}")
continue
try:
size = resolved.stat().st_size
except OSError as exc:
failures.append(f"cannot inspect {candidate}: {exc}")
continue
floor = MEDIA_FLOORS.get(resolved.suffix.lower(), 1)
if size < floor:
failures.append(
f"cited path is a stub ({size} bytes, expected >= {floor}): {candidate}"
)
continue
confirmed.append(f"{candidate} ({size:,} bytes)")
return confirmed, failures
def find_inspector_reports(project: Path) -> list[Path]:
"""JSON files written by inspect-threejs-canvas.mjs, wherever they landed."""
found: list[Path] = []
for path in project.rglob("*.json"):
if "node_modules" in path.parts or path.name == "package-lock.json":
continue
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError, UnicodeDecodeError):
continue
if isinstance(data, dict) and "screenshotPath" in data and "result" in data:
found.append(path)
return sorted(found)
def read_json_object(path: Path) -> dict:
data = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(data, dict):
raise ValueError("expected a JSON object")
return data
def inspect_report(report: Path, project: Path, data: dict, *, strict_paths: bool = False) -> tuple[list[str], list[str]]:
result = data.get("result")
if not isinstance(result, dict) or result.get("ok") is not True:
return [], [f"inspector {report} does not report a non-blank canvas"]
shot = data.get("screenshotPath")
if not isinstance(shot, str) or not shot.strip():
return [], [f"inspector {report} has no screenshotPath"]
roots = [project] if strict_paths else [project, report.parent]
_, failures = check_artifacts([shot], roots, allow_cwd=not strict_paths)
for field in ("consoleErrors", "pageErrors"):
errors = data.get(field, [])
if not isinstance(errors, list) or errors:
failures.append(f"inspector {report} has {field}: {errors}")
if failures:
return [], failures
metrics = result.get("metrics") or {}
entropy = metrics.get("colorEntropyBits") if isinstance(metrics, dict) else None
detail = f", colorEntropyBits={entropy:.2f}" if isinstance(entropy, (int, float)) else ""
label = f"{data.get('mode', '?')}/{data.get('state') or 'default'}"
return [f"{report} ({label}) non-blank{detail}"], []
def check_inspector(project: Path) -> tuple[list[str], list[str]]:
reports = find_inspector_reports(project)
if not reports:
return [], [
"no canvas inspector JSON found under "
f"{project} - run `npm run inspect:canvas` or "
"inspect-threejs-canvas.mjs before claiming visual evidence"
]
confirmed: list[str] = []
failures: list[str] = []
for report in reports:
try:
ok, bad = inspect_report(report, project, read_json_object(report))
except (OSError, ValueError, UnicodeDecodeError) as exc:
ok, bad = [], [f"cannot read inspector {report}: {exc}"]
confirmed.extend(ok)
failures.extend(bad)
return confirmed, failures
def check_manifest(project: Path, manifest_path: Path) -> tuple[list[str], list[str]]:
"""Validate only the reports explicitly assigned to this run and state set."""
try:
manifest = read_json_object(manifest_path)
except (OSError, ValueError, UnicodeDecodeError) as exc:
return [], [f"cannot read manifest {manifest_path}: {exc}"]
run_id = manifest.get("runId")
captures = manifest.get("captures")
if type(manifest.get("version")) is not int or manifest["version"] != 1:
return [], ["manifest version must be 1"]
if not isinstance(run_id, str) or not run_id.strip():
return [], ["manifest requires a nonempty runId"]
if not isinstance(captures, list) or not captures:
return [], ["manifest requires a nonempty captures list"]
confirmed: list[str] = []
failures: list[str] = []
seen: set[tuple[str, str | None]] = set()
reports: set[Path] = set()
for index, capture in enumerate(captures):
if not isinstance(capture, dict):
failures.append(f"capture {index} must be an object")
continue
mode, state, report_name = capture.get("mode"), capture.get("state"), capture.get("report")
if mode not in ("desktop", "mobile") or "state" not in capture or not (
state is None or isinstance(state, str) and state.strip()
) or not isinstance(report_name, str) or not report_name.strip():
failures.append(f"capture {index} requires mode desktop|mobile, state string|null, and report path")
continue
key = (mode, state)
if key in seen:
failures.append(f"duplicate capture {mode}/{state or 'default'}")
continue
seen.add(key)
report = Path(report_name).expanduser()
report = (report if report.is_absolute() else project / report).resolve()
if report in reports:
failures.append(f"inspector report reused for multiple captures: {report}")
continue
reports.add(report)
try:
data = read_json_object(report)
except (OSError, ValueError, UnicodeDecodeError) as exc:
failures.append(f"cannot read declared capture {report}: {exc}")
continue
expected = {"runId": run_id, "mode": mode, "state": state,
"requestedState": state, "appliedState": state}
mismatches = [field for field, value in expected.items() if field not in data or data[field] != value]
if mismatches:
failures.append(f"inspector {report} mismatches declared capture: {', '.join(mismatches)}")
continue
ok, bad = inspect_report(report, project, data, strict_paths=True)
confirmed.extend(ok)
failures.extend(bad)
artifacts = manifest.get("artifacts", [])
if not isinstance(artifacts, list) or any(not isinstance(item, str) or not item.strip() for item in artifacts):
failures.append("manifest artifacts must be a list of nonempty file paths")
else:
ok, bad = check_artifacts(artifacts, [project], allow_cwd=False)
confirmed.extend(ok)
failures.extend(bad)
return confirmed, failures
def check_build(project: Path) -> tuple[list[str], list[str]]:
dist = project / "dist"
if not dist.is_dir():
return [], [
"report claims a production build but there is no dist/ directory in "
f"{project}"
]
entries = [p for p in dist.rglob("*") if p.is_file()]
if not entries:
return [], [f"report claims a production build but {dist} is empty"]
return [f"dist/ present ({len(entries)} files)"], []
def main() -> int:
parser = argparse.ArgumentParser(
description="Verify a Three.js game report's cited evidence exists on disk."
)
parser.add_argument("project", help="game project directory")
parser.add_argument("--manifest", help="version 1 JSON capture manifest, relative to the project or absolute")
parser.add_argument(
"--report",
help="markdown report whose cited paths should be resolved "
"(relative to the project directory unless absolute)",
)
parser.add_argument(
"--skip-inspector",
action="store_true",
help="do not require canvas inspector output (use for non-visual work)",
)
args = parser.parse_args()
if args.manifest and args.skip_inspector:
parser.error("--manifest requires inspector verification; do not combine with --skip-inspector")
project = Path(args.project).expanduser().resolve()
if not project.is_dir():
print(f"Not a directory: {project}", file=sys.stderr)
return 1
confirmed: list[str] = []
failures: list[str] = []
if args.report:
report_path = Path(args.report).expanduser()
if not report_path.is_absolute():
candidate = project / report_path
report_path = candidate if candidate.exists() else report_path
if not report_path.exists():
print(f"Missing report file: {report_path}", file=sys.stderr)
return 1
try:
text = report_path.read_text(encoding="utf-8")
except (OSError, UnicodeError) as exc:
print(f"Cannot read report {report_path}: {exc}", file=sys.stderr)
return 1
roots = [project, report_path.parent, Path.cwd()]
cited = find_paths(text)
if cited:
ok, bad = check_artifacts(cited, roots)
confirmed.extend(ok)
failures.extend(bad)
else:
failures.append(
f"{report_path.name} cites no artifact paths - a report with no "
"screenshots, models, or audio files is not evidence"
)
if BUILD_CLAIM.search(text):
ok, bad = check_build(project)
confirmed.extend(ok)
failures.extend(bad)
if args.manifest:
manifest_path = Path(args.manifest).expanduser()
if not manifest_path.is_absolute():
manifest_path = project / manifest_path
ok, bad = check_manifest(project, manifest_path)
confirmed.extend(ok)
failures.extend(bad)
elif not args.skip_inspector:
print("Legacy mode checks existing files, not current-run coverage; use --manifest for capture verification.")
ok, bad = check_inspector(project)
confirmed.extend(ok)
failures.extend(bad)
for line in confirmed:
print(f" ok {line}")
for line in failures:
print(f" FAIL {line}")
print()
if failures:
print(f"Evidence check failed: {len(failures)} problem(s), {len(confirmed)} confirmed.")
return 1
print(f"Evidence check passed: {len(confirmed)} artifact(s) confirmed.")
return 0
if __name__ == "__main__":
sys.exit(main())
scripts/probe_asset_credentials.sh
#!/usr/bin/env bash
set -euo pipefail
# Prints exactly one line per key in the form KEY=SET or KEY=MISSING.
# The literal SET/MISSING tokens are a contract the skills quote verbatim in
# reports, so callers can tell a real credential blocker from an assumption.
PROBE_SNIPPET='
report_key() {
if [ -n "${2:-}" ]; then
printf "%s=SET\n" "$1"
else
printf "%s=MISSING\n" "$1"
fi
}
report_key TRIPO_API_KEY "${TRIPO_API_KEY:-}"
report_key GEMINI_API_KEY "${GEMINI_API_KEY:-}"
report_key ELEVENLABS_API_KEY "${ELEVENLABS_API_KEY:-}"
'
if command -v zsh >/dev/null 2>&1; then
zsh -lc '
source "$HOME/.zprofile" >/dev/null 2>&1 || true
source "$HOME/.zshrc" >/dev/null 2>&1 || true
'"$PROBE_SNIPPET"
else
bash -lc '
source "$HOME/.bash_profile" >/dev/null 2>&1 || true
source "$HOME/.bashrc" >/dev/null 2>&1 || true
'"$PROBE_SNIPPET"
fi
SKILL.md
---
name: threejs-game-director
description: "Entrypoint for building, upgrading, and finishing Three.js browser games. Routes work across the sibling threejs-* skills for gameplay, graphics, UI, 3D/image/audio asset generation, debugging, and release. Use for build-a-game, upgrade, polish, premium, AAA, high-fidelity, showcase, from-scratch, endless runner, arcade, action, and release-ready requests."
---
# Three.js Game Director
Own the end-to-end game outcome: a playable loop first, then the visual and interface depth the request actually asked for, then browser evidence that it works.
## Scope
The user's own words set the bar. "Make a small arcade game" is not a request for the full premium pipeline — build the good version of what was asked and stop. "Premium", "AAA", "polished", "high-fidelity", "showcase", "release-ready", or "less basic" *is* that request, and at that bar a first playable slice is not done. "Less basic" specifically means the current visual level was rejected; treat it as the premium bar.
The user's scope, art style, constraints, and prior decisions override skill defaults. A narrow edit to a premium game remains a narrow edit. Make routine implementation calls yourself and complete authorized work before seeking a decision that only affects a later step. Ask only when a missing choice materially changes the requested result; continue independent work meanwhile.
## Working style
Say in one sentence what you're about to do before your first tool call. While working, give a brief update only when you find something important or change direction. Lead the final response with the outcome.
The lead owns shared interfaces, integration, and the final verification pass. Use available delegation tools for independent work that saves time or improves quality: asset generation alongside gameplay, or isolated UI work after the intent/state interface is defined. Normally use a lead plus up to two workers. Give each worker a task, separate file ownership, input/output contract, and acceptance criteria. Keep the immediate blocking integration work with the lead.
For substantial gameplay, graphics, or animation changes, one focused independent review can catch missed defects. Supply raw captures/code and the relevant rubric; ask for concrete defects rather than endorsement of the lead's score. Resolve findings without recursive review cycles. When delegation tools are absent, work directly.
Report what you ran and what you saw. If you couldn't run something, say that instead.
## Sibling skills
Use the actual loaded skill directory as `<director-skill-dir>`. Resolve siblings through `../<skill>/SKILL.md` there. If absent, use the runner's discovered skill path, then a matching repo `skills/` directory or the active runner's install location (`~/.agents/skills` for Codex, `~/.claude/skills` for Claude Code, legacy `~/.codex/skills` last). Resolve references relative to the selected skill; avoid mixing installed versions.
| Phase | Skill |
| --- | --- |
| Design brief, core loop, levels, entities, input, camera, physics, feel | `threejs-gameplay-systems` |
| Models, materials, shaders, VFX, lighting, render budget, scorecard | `threejs-aaa-graphics-builder` |
| HUD, menus, overlays, responsive and touch UI | `threejs-game-ui-designer` |
| Blank canvas, render/runtime bugs, mobile input, profiling | `threejs-debug-profiler` |
| Browser QA, screenshots, canvas pixels, bot playtest, production build | `threejs-qa-release` |
| Characters, vehicles, weapons, buildings, rigs, animation | `threejs-3d-generator` |
| Concepts, textures, skies, logos, icons, GUI art, image-to-3D inputs | `threejs-image-generator` |
| SFX, ambience, UI sounds, announcer and dialogue | `threejs-audio-generator` |
For complete games and broad upgrades, read all five production skills before implementing, plus generators whose trigger surfaces exist. Read each phase's required references at phase entry. For narrow edits, load the affected specialists and references, preserving unrelated systems. Record actual loaded resources when reporting skill use; a phase label is not a skill invocation.
## Continuity and early quality
Start broad builds with the gameplay design brief, core-loop contract, and level plan. Define art direction, camera scale, and hero/readability targets early. Launch useful asset jobs while implementing the loop, then assess a representative playable scene with the real assets before multiplying levels, waves, or enemy variants. Inspect concepts and generated model previews before their dependent generation or rigging stages.
For substantial tasks maintain `artifacts/game-progress.md`: current intent and constraints, decisions, completed work, pending jobs with task IDs/checkpoint paths, remaining defects, and next actions. Re-read it after an interruption. A correction updates affected work; a status question does not replace the build objective. Preserve completed assets and mark obsolete pending outputs instead of accidentally spending again.
Use available background tool sessions or submit/status/download commands to keep independent work moving. Native API async calling, steering, and reasoning configuration are host capabilities, not settings enabled by this skill.
## The bar for premium work
Every visible surface that exists in the design is authored, not just the hero: player, obstacles and enemies, interactables, ground and world kit, HUD and menu states, lighting and materials, feel, and target-device performance. Unrefined primitives, empty arenas, box skylines, generic stat-card HUDs, and glow-or-fog-only detail are prototype placeholders unless the user explicitly chose that style. Interpret the scorecard through the genre rather than adding unrelated content.
Score the result with the 10-category scorecard in `threejs-aaa-graphics-builder/references/visual-scorecard.md`, using its anchors and the inspector's measured metrics rather than a personal rubric. Premium means no category below 2 and an average of at least 2.3.
## Asset sourcing
```bash
bash <director-skill-dir>/scripts/probe_asset_credentials.sh
```
When external generation is in scope, run it before assuming anything about keys. It sources the user's shell profile, which the agent process usually does not inherit, and prints `KEY=SET|MISSING` for all three providers. Explicitly procedural or no-external-service work does not need a credential probe.
With keys set, premium hero surfaces get generated assets: player, boss, creature, vehicle, ship, weapon, signature building. Respect an explicit procedural-only style or external-generation restriction. Procedural kits handle repeated props, decals, collision proxies, and instanced volume. Premium active gameplay includes event-driven audio.
Read `references/asset-recovery.md` when sourcing external assets or recovering a job. Missing credentials or exhausted credits permit a documented local fallback. A transient error calls for bounded recovery of the existing job; invalid parameters need correction. An uncertain paid submission must be reconciled before replacement. Continue independent work and identify any quality requirement still unmet after fallback.
## Verification ownership
The lead consolidates specialist results into one check set appropriate to the change. Full games need production build, real-input progression and retry, target-viewport captures, renderer diagnostics, and the premium scorecard when requested. Small edits need affected behavior/layout checks. Repeat checks only after relevant changes, failures, or unresolved concerns. For animated work include motion captures covering locomotion, transitions, and contact timing, not only stills.
## Getting started and checking output
```bash
python3 <threejs-gameplay-systems-skill-dir>/scripts/create_threejs_game.py ./my-game
node <threejs-qa-release-skill-dir>/scripts/inspect-threejs-canvas.mjs --url http://127.0.0.1:5188 --state active-play --run-id pass-1
python3 <director-skill-dir>/scripts/check_evidence.py ./my-game --manifest artifacts/evidence.json
```
Generated games carry their own `npm run inspect:canvas` and `npm run verify:visual`. Before capturing, read `references/evidence-manifest.md` and declare the expected viewport/state pairs for this pass. The checker verifies only that set and its run ID. Its result establishes artifact coverage, not aesthetic quality or gameplay correctness. When maintaining the pack itself, use `references/workflow-evaluations.md` for behavioral comparisons.
## Final response
Lead with what was built, whether it works, the local URL and controls, and remaining limitations. For substantial builds put the design artifacts, asset task IDs/paths, captures and motion evidence, renderer/physics metrics, tests, and scorecard in `artifacts/final-evidence.md` and link it. For narrow edits report only affected behavior and checks. Describe what ran and was observed; do not substitute a completion claim for missing evidence.