summer-engine 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +59 -0
- package/.codex-plugin/plugin.json +38 -0
- package/.cursor-plugin/plugin.json +42 -0
- package/.mcp.json +8 -0
- package/.opencode/plugins/summer.js +49 -0
- package/README.md +13 -0
- package/dist/lib/skills-registry.d.ts +96 -0
- package/dist/lib/skills-registry.js +205 -0
- package/gemini-extension.json +21 -0
- package/hooks/hooks-cursor.json +21 -0
- package/hooks/hooks.json +31 -0
- package/hooks/pre-commit-doctor.sh +123 -0
- package/hooks/run-hook.cmd +23 -0
- package/hooks/session-start.sh +120 -0
- package/marketplace.json +24 -0
- package/package.json +9 -1
- package/skills/_tests/specs/design-npc.md +43 -12
- package/skills/_tests/specs/fps-controller.md +33 -20
- package/skills/_tests/specs/host-authoritative-state.md +104 -0
- package/skills/_tests/specs/peer-to-peer-multiplayer.md +99 -0
- package/skills/_tests/specs/vfx.md +56 -38
- package/skills/ai-and-npcs/design-npc/SKILL.md +264 -122
- package/skills/asset-pipeline/asset-strategy/SKILL.md +8 -0
- package/skills/catalog.yaml +84 -0
- package/skills/character-controllers/fps-controller/SKILL.md +277 -69
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +332 -0
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +378 -0
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +20 -0
- package/skills/scene-and-project/make-game/SKILL.md +10 -0
- package/skills/scene-and-project/scene-composition/SKILL.md +8 -0
- package/skills/ui-and-ux/ui-basics/SKILL.md +28 -0
- package/skills/visual-effects/vfx/SKILL.md +233 -134
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Summer Engine — Pre-commit doctor hook (OPT-IN)
|
|
3
|
+
#
|
|
4
|
+
# Event: PreToolUse (matcher: Bash, if: "Bash(git commit*)")
|
|
5
|
+
# Purpose: Run `summer doctor` before any git commit Claude is about to make.
|
|
6
|
+
# Block the commit on FAIL. Allow with warnings on WARN.
|
|
7
|
+
#
|
|
8
|
+
# Status: OPT-IN. Not registered in the Summer plugin manifest by default.
|
|
9
|
+
#
|
|
10
|
+
# To enable, add to ~/.claude/settings.json (or .claude/settings.json):
|
|
11
|
+
#
|
|
12
|
+
# {
|
|
13
|
+
# "hooks": {
|
|
14
|
+
# "PreToolUse": [
|
|
15
|
+
# {
|
|
16
|
+
# "matcher": "Bash",
|
|
17
|
+
# "hooks": [
|
|
18
|
+
# {
|
|
19
|
+
# "type": "command",
|
|
20
|
+
# "command": "${CLAUDE_PLUGIN_ROOT}/hooks/pre-commit-doctor.sh",
|
|
21
|
+
# "if": "Bash(git commit*)",
|
|
22
|
+
# "timeout": 15
|
|
23
|
+
# }
|
|
24
|
+
# ]
|
|
25
|
+
# }
|
|
26
|
+
# ]
|
|
27
|
+
# }
|
|
28
|
+
# }
|
|
29
|
+
#
|
|
30
|
+
# Then: chmod +x ${CLAUDE_PLUGIN_ROOT}/hooks/pre-commit-doctor.sh
|
|
31
|
+
#
|
|
32
|
+
# Input: JSON on stdin: { tool_name, tool_input: { command, ... }, ... }
|
|
33
|
+
# Output: On FAIL: stderr explanation, exit 2 (blocks commit).
|
|
34
|
+
# On WARN: stderr warnings, exit 0 (allows commit).
|
|
35
|
+
# On PASS or no `summer` CLI: silent, exit 0.
|
|
36
|
+
#
|
|
37
|
+
# Portability: bash 3.2+, POSIX-friendly. jq optional.
|
|
38
|
+
|
|
39
|
+
set +e
|
|
40
|
+
|
|
41
|
+
INPUT=$(cat)
|
|
42
|
+
|
|
43
|
+
# ---------- Parse the command ----------
|
|
44
|
+
COMMAND=""
|
|
45
|
+
if command -v jq >/dev/null 2>&1; then
|
|
46
|
+
COMMAND=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
47
|
+
fi
|
|
48
|
+
if [ -z "$COMMAND" ]; then
|
|
49
|
+
COMMAND=$(printf '%s' "$INPUT" \
|
|
50
|
+
| grep -oE '"command"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
51
|
+
| head -1 \
|
|
52
|
+
| sed 's/^"command"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# ---------- Bail early if not a git commit ----------
|
|
56
|
+
if ! printf '%s' "$COMMAND" | grep -qE '^[[:space:]]*git[[:space:]]+commit'; then
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# ---------- Bail if `summer` CLI missing ----------
|
|
61
|
+
if ! command -v summer >/dev/null 2>&1; then
|
|
62
|
+
# Don't block — user may have installed via plugin only.
|
|
63
|
+
exit 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# ---------- Run doctor ----------
|
|
67
|
+
DOCTOR_OUT=$(summer doctor --json 2>/dev/null)
|
|
68
|
+
if [ -z "$DOCTOR_OUT" ]; then
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# ---------- Parse status ----------
|
|
73
|
+
HAS_FAIL=0
|
|
74
|
+
HAS_WARN=0
|
|
75
|
+
FAIL_MSGS=""
|
|
76
|
+
WARN_MSGS=""
|
|
77
|
+
|
|
78
|
+
if command -v jq >/dev/null 2>&1; then
|
|
79
|
+
FAIL_MSGS=$(printf '%s' "$DOCTOR_OUT" \
|
|
80
|
+
| jq -r '.checks[]? | select(.status == "FAIL") | " - " + (.name // "?") + ": " + (.message // "")' 2>/dev/null)
|
|
81
|
+
WARN_MSGS=$(printf '%s' "$DOCTOR_OUT" \
|
|
82
|
+
| jq -r '.checks[]? | select(.status == "WARN") | " - " + (.name // "?") + ": " + (.message // "")' 2>/dev/null)
|
|
83
|
+
[ -n "$FAIL_MSGS" ] && HAS_FAIL=1
|
|
84
|
+
[ -n "$WARN_MSGS" ] && HAS_WARN=1
|
|
85
|
+
else
|
|
86
|
+
# Fallback: substring sniffing
|
|
87
|
+
if printf '%s' "$DOCTOR_OUT" | grep -q '"status"[[:space:]]*:[[:space:]]*"FAIL"'; then
|
|
88
|
+
HAS_FAIL=1
|
|
89
|
+
FAIL_MSGS=$(printf '%s' "$DOCTOR_OUT" \
|
|
90
|
+
| grep -oE '"status"[[:space:]]*:[[:space:]]*"FAIL"[^}]*"message"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
91
|
+
| sed -E 's/.*"message"[[:space:]]*:[[:space:]]*"([^"]*)"/ - \1/')
|
|
92
|
+
fi
|
|
93
|
+
if printf '%s' "$DOCTOR_OUT" | grep -q '"status"[[:space:]]*:[[:space:]]*"WARN"'; then
|
|
94
|
+
HAS_WARN=1
|
|
95
|
+
WARN_MSGS=$(printf '%s' "$DOCTOR_OUT" \
|
|
96
|
+
| grep -oE '"status"[[:space:]]*:[[:space:]]*"WARN"[^}]*"message"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
97
|
+
| sed -E 's/.*"message"[[:space:]]*:[[:space:]]*"([^"]*)"/ - \1/')
|
|
98
|
+
fi
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# ---------- FAIL: block ----------
|
|
102
|
+
if [ "$HAS_FAIL" -eq 1 ]; then
|
|
103
|
+
{
|
|
104
|
+
echo "=== Summer doctor: FAIL — commit blocked ==="
|
|
105
|
+
echo "$FAIL_MSGS"
|
|
106
|
+
echo ""
|
|
107
|
+
echo "Fix the failures above, or run: summer doctor"
|
|
108
|
+
echo "To bypass once: disable the pre-commit-doctor hook in settings.json."
|
|
109
|
+
echo "============================================"
|
|
110
|
+
} >&2
|
|
111
|
+
exit 2
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# ---------- WARN: allow with notice ----------
|
|
115
|
+
if [ "$HAS_WARN" -eq 1 ]; then
|
|
116
|
+
{
|
|
117
|
+
echo "=== Summer doctor: WARN — commit allowed ==="
|
|
118
|
+
echo "$WARN_MSGS"
|
|
119
|
+
echo "============================================"
|
|
120
|
+
} >&2
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
exit 0
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Cross-platform hook dispatcher.
|
|
3
|
+
# Usage: run-hook.cmd <hook-name>
|
|
4
|
+
# Reads stdin, forwards to <plugin-root>/hooks/<hook-name>.sh
|
|
5
|
+
# On Windows, Claude Code / Cursor / Codex invoke this via Git-Bash.
|
|
6
|
+
|
|
7
|
+
HOOK_NAME="$1"
|
|
8
|
+
shift || true
|
|
9
|
+
|
|
10
|
+
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
HOOK_SCRIPT="$HOOK_DIR/$HOOK_NAME.sh"
|
|
12
|
+
|
|
13
|
+
if [ ! -f "$HOOK_SCRIPT" ]; then
|
|
14
|
+
# Missing hook = no-op (don't break the host).
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [ ! -x "$HOOK_SCRIPT" ]; then
|
|
19
|
+
# Not executable yet (likely fresh checkout). Fall back to bash invocation.
|
|
20
|
+
exec bash "$HOOK_SCRIPT" "$@"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
exec "$HOOK_SCRIPT" "$@"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Summer Engine — SessionStart hook
|
|
3
|
+
#
|
|
4
|
+
# Event: SessionStart (matcher: startup|clear|compact)
|
|
5
|
+
# Purpose: Orient Claude on every fresh Summer-enabled session.
|
|
6
|
+
# Detect Godot project + GameSoul + doctor health, surface a one-line
|
|
7
|
+
# Bob-style greeting as additional context.
|
|
8
|
+
#
|
|
9
|
+
# Input: JSON on stdin with { session_id, source, model, cwd, ... }
|
|
10
|
+
# Common Claude Code env vars: $CLAUDE_PROJECT_DIR, $CLAUDE_PLUGIN_ROOT
|
|
11
|
+
# Output: JSON on stdout with hookSpecificOutput.additionalContext
|
|
12
|
+
# (also emits Cursor / Copilot CLI variants based on host env vars)
|
|
13
|
+
# Exit: Always 0 — never block session start.
|
|
14
|
+
#
|
|
15
|
+
# Portability: bash 3.2+ (macOS default), POSIX-friendly, works on Git Bash.
|
|
16
|
+
# Uses `grep -E` not `grep -P`. Falls back gracefully when jq
|
|
17
|
+
# and `summer` are missing.
|
|
18
|
+
|
|
19
|
+
set +e # Hooks must never kill the session.
|
|
20
|
+
|
|
21
|
+
# ---------- Resolve project dir ----------
|
|
22
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
23
|
+
|
|
24
|
+
# ---------- Build the orientation lines (max 3) ----------
|
|
25
|
+
LINES=()
|
|
26
|
+
|
|
27
|
+
# Line 1: Summer ready + skill count
|
|
28
|
+
SKILL_COUNT=""
|
|
29
|
+
if command -v summer >/dev/null 2>&1; then
|
|
30
|
+
# `summer skills count` is best-effort; ignore failure
|
|
31
|
+
SKILL_COUNT=$(summer skills count 2>/dev/null | grep -oE '[0-9]+' | head -1)
|
|
32
|
+
fi
|
|
33
|
+
if [ -n "$SKILL_COUNT" ]; then
|
|
34
|
+
LINES+=("Summer ready. ${SKILL_COUNT} skills.")
|
|
35
|
+
else
|
|
36
|
+
LINES+=("Summer ready.")
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Line 2: Godot project + GameSoul one-liner (only if project.godot exists)
|
|
40
|
+
if [ -f "${PROJECT_DIR}/project.godot" ]; then
|
|
41
|
+
PROJECT_NAME=$(grep -E '^config/name=' "${PROJECT_DIR}/project.godot" 2>/dev/null \
|
|
42
|
+
| head -1 | sed 's/^config\/name=//;s/^"//;s/"$//')
|
|
43
|
+
[ -z "$PROJECT_NAME" ] && PROJECT_NAME=$(basename "$PROJECT_DIR")
|
|
44
|
+
|
|
45
|
+
SOUL_FILE="${PROJECT_DIR}/.summer/GameSoul.md"
|
|
46
|
+
if [ -f "$SOUL_FILE" ]; then
|
|
47
|
+
# First non-empty, non-heading paragraph (one line)
|
|
48
|
+
SOUL_LINE=$(awk 'BEGIN{p=""} /^[^#[:space:]]/ && NF>0 {p=$0; exit} END{print p}' \
|
|
49
|
+
"$SOUL_FILE" 2>/dev/null | cut -c1-120)
|
|
50
|
+
if [ -n "$SOUL_LINE" ]; then
|
|
51
|
+
LINES+=("Godot project: ${PROJECT_NAME}. GameSoul: ${SOUL_LINE}")
|
|
52
|
+
else
|
|
53
|
+
LINES+=("Godot project: ${PROJECT_NAME}.")
|
|
54
|
+
fi
|
|
55
|
+
else
|
|
56
|
+
LINES+=("Godot project: ${PROJECT_NAME}.")
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Line 3: doctor FAIL one-liner (only on FAIL — silent on PASS/WARN)
|
|
61
|
+
if command -v summer >/dev/null 2>&1; then
|
|
62
|
+
DOCTOR_OUT=$(summer doctor --json 2>/dev/null)
|
|
63
|
+
if [ -n "$DOCTOR_OUT" ]; then
|
|
64
|
+
FAIL_REASON=""
|
|
65
|
+
if command -v jq >/dev/null 2>&1; then
|
|
66
|
+
FAIL_REASON=$(printf '%s' "$DOCTOR_OUT" \
|
|
67
|
+
| jq -r 'first(.checks[]? | select(.status == "FAIL") | .message) // empty' 2>/dev/null)
|
|
68
|
+
else
|
|
69
|
+
# Crude fallback: first "message" near a "FAIL"
|
|
70
|
+
FAIL_REASON=$(printf '%s' "$DOCTOR_OUT" \
|
|
71
|
+
| grep -oE '"status"[[:space:]]*:[[:space:]]*"FAIL"[^}]*"message"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
72
|
+
| head -1 \
|
|
73
|
+
| grep -oE '"message"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
74
|
+
| sed 's/.*"message"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
|
75
|
+
fi
|
|
76
|
+
if [ -n "$FAIL_REASON" ]; then
|
|
77
|
+
LINES+=("Summer setup needs attention: ${FAIL_REASON}. Run: summer doctor")
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# ---------- Compose context ----------
|
|
83
|
+
CONTEXT=""
|
|
84
|
+
for line in "${LINES[@]}"; do
|
|
85
|
+
if [ -z "$CONTEXT" ]; then CONTEXT="$line"
|
|
86
|
+
else CONTEXT="${CONTEXT}"$'\n'"$line"
|
|
87
|
+
fi
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
# Bail with empty JSON if we somehow have no lines
|
|
91
|
+
if [ -z "$CONTEXT" ]; then
|
|
92
|
+
printf '{}\n'
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# ---------- JSON-escape for embedding ----------
|
|
97
|
+
escape_for_json() {
|
|
98
|
+
local s="$1"
|
|
99
|
+
s="${s//\\/\\\\}"
|
|
100
|
+
s="${s//\"/\\\"}"
|
|
101
|
+
s="${s//$'\n'/\\n}"
|
|
102
|
+
s="${s//$'\r'/\\r}"
|
|
103
|
+
s="${s//$'\t'/\\t}"
|
|
104
|
+
printf '%s' "$s"
|
|
105
|
+
}
|
|
106
|
+
CONTEXT_ESC=$(escape_for_json "$CONTEXT")
|
|
107
|
+
|
|
108
|
+
# ---------- Emit per-host shape ----------
|
|
109
|
+
# Cursor: top-level additional_context (snake_case)
|
|
110
|
+
# Claude Code: nested hookSpecificOutput.additionalContext
|
|
111
|
+
# Copilot CLI / SDK standard: top-level additionalContext (camelCase)
|
|
112
|
+
if [ -n "${CURSOR_PLUGIN_ROOT:-}" ]; then
|
|
113
|
+
printf '{"additional_context":"%s"}\n' "$CONTEXT_ESC"
|
|
114
|
+
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -z "${COPILOT_CLI:-}" ]; then
|
|
115
|
+
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$CONTEXT_ESC"
|
|
116
|
+
else
|
|
117
|
+
printf '{"additionalContext":"%s"}\n' "$CONTEXT_ESC"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
exit 0
|
package/marketplace.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-marketplace.json",
|
|
3
|
+
"name": "summer",
|
|
4
|
+
"description": "Summer Engine plugins for Claude Code",
|
|
5
|
+
"owner": { "name": "Summer Engine", "email": "founders@summerengine.com" },
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "summer",
|
|
9
|
+
"source": "./",
|
|
10
|
+
"version": "1.4.0",
|
|
11
|
+
"description": "AI game-dev studio: 19 skills under summer: namespace, MCP bridge, session-start + pre-commit hooks.",
|
|
12
|
+
"author": { "name": "Summer Engine", "email": "founders@summerengine.com" },
|
|
13
|
+
"category": "game-development",
|
|
14
|
+
"tags": ["game-dev", "godot", "ai-game-engine"],
|
|
15
|
+
"keywords": ["game-dev", "godot", "ai-game-engine", "skills"],
|
|
16
|
+
"brandColor": "#FF8F17",
|
|
17
|
+
"capabilities": ["Interactive", "Read", "Write"],
|
|
18
|
+
"defaultPrompt": [
|
|
19
|
+
"I want to make a game.",
|
|
20
|
+
"/summer:brainstorm-game"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "summer-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "CLI and MCP tools for Summer Engine — the AI-native game engine",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"game-engine",
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist/",
|
|
27
27
|
"skills/",
|
|
28
|
+
"hooks/",
|
|
29
|
+
".claude-plugin/",
|
|
30
|
+
".cursor-plugin/",
|
|
31
|
+
".codex-plugin/",
|
|
32
|
+
".opencode/",
|
|
33
|
+
"gemini-extension.json",
|
|
34
|
+
"marketplace.json",
|
|
35
|
+
".mcp.json",
|
|
28
36
|
"README.md",
|
|
29
37
|
"LICENSE",
|
|
30
38
|
"CHANGELOG.md"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- Summer MCP tools available.
|
|
7
7
|
- Host file tools available (Read, Edit, Write).
|
|
8
8
|
|
|
9
|
-
## Case 1: Happy Path — basic patrol-and-chase enemy
|
|
9
|
+
## Case 1: Happy Path — basic patrol-and-chase enemy with personality + intent
|
|
10
10
|
|
|
11
11
|
**Input:** "Add a basic enemy that chases the player when it sees them."
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
2. User confirms "basic enemy".
|
|
17
17
|
3. `summer_get_scene_tree`
|
|
18
18
|
4. `summer_inspect_node "./World/Player"` — confirms it's in `player` group
|
|
19
|
-
5. (Skill proposes the bundle: scene tree + script +
|
|
19
|
+
5. (Skill proposes the bundle: scene tree + script + personality knobs + intent enum + action SM + telegraph + loot hook + "May I create?")
|
|
20
20
|
6. After user OK:
|
|
21
21
|
- `summer_add_node(parent="./World/Enemies", type="CharacterBody3D", name="Enemy")`
|
|
22
22
|
- `summer_add_node(parent="./World/Enemies/Enemy", type="CollisionShape3D", name="Body")`
|
|
@@ -36,9 +36,14 @@
|
|
|
36
36
|
- [ ] Skill asks the archetype question before any tool call.
|
|
37
37
|
- [ ] Scene has BOTH a Vision Area3D AND a Sight RayCast3D (the two-step perception pattern).
|
|
38
38
|
- [ ] Vision shape is saved as a standalone `.tres`, NOT inline sub_resource (silent-fail trap).
|
|
39
|
-
- [ ] Script
|
|
40
|
-
- [ ] Script
|
|
41
|
-
- [ ] Script
|
|
39
|
+
- [ ] Script declares four `@export_range(0.0, 1.0)` personality knobs: `aggression`, `patience`, `caution`, `punishment`.
|
|
40
|
+
- [ ] Script jitters personality in `_ready` via `randf_range` clamped to `[0.0, 1.0]`.
|
|
41
|
+
- [ ] Script defines an `enum Intent` with at least 5 values from {IDLE, PATROL, INVESTIGATE, HUNT, KILL, RETREAT, RESCUE_ALLY}.
|
|
42
|
+
- [ ] Script defines an `enum State` with the 4 action states (CALM / ALERT / AGGRESSIVE / DEFEATED).
|
|
43
|
+
- [ ] Intent layer (`_update_intent` / `_decide_intent`) computes `current_intent` from perception + personality.
|
|
44
|
+
- [ ] Action SM (`_update_action`) reads `current_intent`; it does NOT write it.
|
|
45
|
+
- [ ] Script has an `attack_telegraph` parameter in 0.3–0.7 sec range, scaled by aggression at attack time.
|
|
46
|
+
- [ ] Script emits a `died` signal AND an `ally_defeated` signal.
|
|
42
47
|
- [ ] Script uses `distance_squared_to` (NOT `distance_to`) for range comparisons.
|
|
43
48
|
- [ ] Script uses typed GDScript per `_shared/gd-style.md`.
|
|
44
49
|
- [ ] Skill asks "May I create / write …" before each user-visible mutation.
|
|
@@ -53,7 +58,7 @@
|
|
|
53
58
|
**Expected behavior:**
|
|
54
59
|
|
|
55
60
|
- Skill recognizes the scale conflict.
|
|
56
|
-
- Says: "100 enemies with full
|
|
61
|
+
- Says: "100 enemies with full intent SM + LOS perception will eat your frame budget. For wave mobs at this scale, the right archetype is the wave-mob (single KILL intent, 2 action states, no LOS). Want me to build that instead?"
|
|
57
62
|
- Does NOT silently build 100 expensive enemies.
|
|
58
63
|
- Offers the trade-off explicitly.
|
|
59
64
|
|
|
@@ -73,8 +78,9 @@
|
|
|
73
78
|
**Expected behavior:**
|
|
74
79
|
|
|
75
80
|
- Skill picks the **boss** archetype (different complexity budget).
|
|
76
|
-
- Calls out: "Boss complexity (6–10 states, scripted phases) is at the threshold where a behavior tree might pay off, but I recommend a hand-coded FSM with phase enum first since you're a single dev."
|
|
77
|
-
-
|
|
81
|
+
- Calls out: "Boss complexity (6–10 action states, 5–7 intents incl. RESCUE_ALLY, scripted phases) is at the threshold where a behavior tree might pay off, but I recommend a hand-coded FSM with phase enum first since you're a single dev."
|
|
82
|
+
- Recommends boss personality bias: aggression 0.7–0.9, patience 0.7–0.9, punishment 0.8–1.0 (from the tunable matrix).
|
|
83
|
+
- Walks all six pillars at boss scale: more tells per state, telegraph 0.5–0.7 sec, distinct mechanics per phase (cap at 4–6 total mechanics).
|
|
78
84
|
- Defers to `ai-and-npcs/boss-patterns/SKILL.md` for the deeper phase-transition patterns.
|
|
79
85
|
- Does NOT just scale up the basic-enemy stub.
|
|
80
86
|
|
|
@@ -83,6 +89,7 @@
|
|
|
83
89
|
- [ ] Skill recognizes "boss" requires a different design.
|
|
84
90
|
- [ ] Skill caps at 4–6 distinct mechanics across phases.
|
|
85
91
|
- [ ] Skill mentions phase enum as the FSM extension.
|
|
92
|
+
- [ ] Skill cites boss-archetype personality ranges from the tunable matrix.
|
|
86
93
|
- [ ] Skill defers to boss-patterns for deeper detail rather than trying to one-shot it.
|
|
87
94
|
|
|
88
95
|
## Case 4: No Summer MCP — fallback path
|
|
@@ -94,19 +101,43 @@
|
|
|
94
101
|
**Expected behavior:**
|
|
95
102
|
|
|
96
103
|
- Skill detects MCP unavailable.
|
|
97
|
-
- Asks user to paste the existing scene tree (or
|
|
104
|
+
- Asks user to paste the existing scene tree (or relevant `.tscn` snippet) so node paths are right.
|
|
98
105
|
- Generates a `.tscn` snippet (Enemy + Vision + Sight + Mesh + CollisionShape) with valid Godot 4 scene syntax.
|
|
99
|
-
- Writes `scripts/enemy_ai.gd` directly.
|
|
100
|
-
- Provides manual editor instructions for connecting the `body_entered` signal
|
|
106
|
+
- Writes `scripts/enemy_ai.gd` directly with the personality + intent + action layers intact.
|
|
107
|
+
- Provides manual editor instructions for connecting the `body_entered` signal.
|
|
101
108
|
- Still asks "May I write …" before each file write.
|
|
102
109
|
|
|
103
110
|
**Assertions:**
|
|
104
111
|
|
|
105
112
|
- [ ] Skill does not blindly call `summer_*` tools and fail.
|
|
106
113
|
- [ ] Generated `.tscn` is valid Godot 4 syntax (parseable).
|
|
107
|
-
- [ ] Generated GDScript has
|
|
114
|
+
- [ ] Generated GDScript has personality knobs, intent enum, action SM (same structure as MCP path).
|
|
108
115
|
- [ ] Skill still asks "May I" on writes.
|
|
109
116
|
|
|
117
|
+
## Case 5: Group emergence — squad of 4 with varied aggression
|
|
118
|
+
|
|
119
|
+
**Fixture:** Same as Case 1. User wants a small squad, not a wave.
|
|
120
|
+
|
|
121
|
+
**Input:** "Spawn a patrol squad of 4 basic_grunt enemies near the gate."
|
|
122
|
+
|
|
123
|
+
**Expected behavior:**
|
|
124
|
+
|
|
125
|
+
- Skill picks the **basic enemy** archetype with squad framing.
|
|
126
|
+
- Confirms the squad uses the **same `enemy_ai.gd` script and same archetype `.tscn`** — variation comes from per-instance personality jitter, NOT 4 different scripts.
|
|
127
|
+
- Shows / verifies that `personality_jitter` (default 0.15) is set on each instance, with base aggression in the basic_grunt range (0.5–0.7).
|
|
128
|
+
- Predicts the emergent roles: with jitter, post-spawn aggression values land roughly across `0.35 / 0.5 / 0.65 / 0.85` and produce a sniper / anchor / flanker / berserker mix.
|
|
129
|
+
- Does NOT author per-instance roles by hand.
|
|
130
|
+
- Does NOT duplicate the script per role.
|
|
131
|
+
|
|
132
|
+
**Assertions:**
|
|
133
|
+
|
|
134
|
+
- [ ] All 4 spawned NPCs use the same script file and same archetype scene.
|
|
135
|
+
- [ ] Each instance's `_ready` jitters all 4 personality knobs via `randf_range` clamped to `[0.0, 1.0]`.
|
|
136
|
+
- [ ] Skill explicitly cites group emergence (varied aggression -> varied roles) instead of authoring roles centrally.
|
|
137
|
+
- [ ] Skill does NOT recommend creating `sniper_ai.gd`, `flanker_ai.gd`, etc. as separate scripts.
|
|
138
|
+
- [ ] Skill calls out that two NPCs with the same base archetype will still feel different post-jitter.
|
|
139
|
+
- [ ] If the user pushes back ("I want one to definitely be a sniper"), skill suggests narrowing that *instance's* base values (e.g., aggression 0.2, caution 0.8) rather than forking the script.
|
|
140
|
+
|
|
110
141
|
---
|
|
111
142
|
|
|
112
143
|
This spec runs via `/skill-test design-npc spec` (see `_meta/skill-test/SKILL.md`).
|
|
@@ -16,29 +16,41 @@
|
|
|
16
16
|
1. `summer_get_scene_tree` — verify empty World exists.
|
|
17
17
|
2. `summer_add_node(parent="./World", type="CharacterBody3D", name="Player")`
|
|
18
18
|
3. `summer_add_node(parent="./World/Player", type="CollisionShape3D", name="Collision")`
|
|
19
|
-
4. `
|
|
20
|
-
5. `
|
|
21
|
-
6. `summer_set_resource_property("./World/Player/Collision", "shape
|
|
22
|
-
7. `
|
|
23
|
-
8. `
|
|
24
|
-
9. `
|
|
25
|
-
10. `summer_input_map_bind(name="
|
|
26
|
-
11. `summer_input_map_bind(name="
|
|
27
|
-
12. `summer_input_map_bind(name="
|
|
28
|
-
13. (
|
|
29
|
-
14. `
|
|
30
|
-
15. `
|
|
19
|
+
4. `summer_set_prop("./World/Player/Collision", "shape", "CapsuleShape3D")` (creates standalone, NOT inline sub-resource)
|
|
20
|
+
5. `summer_set_resource_property("./World/Player/Collision", "shape", "radius", "0.4")`
|
|
21
|
+
6. `summer_set_resource_property("./World/Player/Collision", "shape", "height", "1.8")`
|
|
22
|
+
7. `summer_add_node(parent="./World/Player", type="Node3D", name="Head")`
|
|
23
|
+
8. `summer_set_prop("./World/Player/Head", "position", "Vector3(0, 1.6, 0)")`
|
|
24
|
+
9. `summer_add_node(parent="./World/Player/Head", type="Camera3D", name="Camera")`
|
|
25
|
+
10. `summer_input_map_bind(name="move_forward", events=[{type:"key", key:"W"}])`
|
|
26
|
+
11. `summer_input_map_bind(name="move_back", events=[{type:"key", key:"S"}])`
|
|
27
|
+
12. `summer_input_map_bind(name="move_left", events=[{type:"key", key:"A"}])`
|
|
28
|
+
13. `summer_input_map_bind(name="move_right", events=[{type:"key", key:"D"}])`
|
|
29
|
+
14. `summer_input_map_bind(name="jump", events=[{type:"key", key:"Space"}])`
|
|
30
|
+
15. `summer_input_map_bind(name="sprint", events=[{type:"key", key:"Shift"}])`
|
|
31
|
+
16. (User-side write) `Write` script file `scripts/player_controller.gd` after the agent asks "May I create the controller script and attach it to ./World/Player?"
|
|
32
|
+
17. `summer_save_scene`
|
|
33
|
+
18. `summer_get_script_errors`
|
|
31
34
|
|
|
32
35
|
**Assertions:**
|
|
33
36
|
|
|
34
37
|
- [ ] CharacterBody3D node exists at `./World/Player`.
|
|
35
38
|
- [ ] CollisionShape3D is a child of Player with a CapsuleShape3D (NOT BoxShape, NOT SphereShape).
|
|
36
|
-
- [ ]
|
|
37
|
-
- [ ] All
|
|
39
|
+
- [ ] A `Head` Node3D pivots at `y ≈ 1.6` and contains the Camera3D as a child (yaw on body, pitch on head — never both on the body).
|
|
40
|
+
- [ ] All 6 input actions bound: `move_forward`, `move_back`, `move_left`, `move_right`, `jump`, `sprint`.
|
|
38
41
|
- [ ] Skill asks "May I create / attach …" before the script write.
|
|
39
42
|
- [ ] Skill never calls `summer_set_resource_property` on an inline `sub_resource` (the silent-fail trap from `_shared/mcp-tools-reference.md`).
|
|
40
43
|
- [ ] Final step is `summer_save_scene` followed by `summer_get_script_errors`.
|
|
41
44
|
- [ ] Script uses typed GDScript per `_shared/gd-style.md` (`@export`, `:=`, `class_name`).
|
|
45
|
+
- [ ] Script defines `coyote_time` and `jump_buffer_time` as `@export` floats with sensible defaults (0.05–0.2).
|
|
46
|
+
- [ ] Script defines separate `ground_accel`, `ground_friction`, `air_accel`, `air_friction` exports — NOT a single `move_speed` snap-to-target.
|
|
47
|
+
- [ ] Script implements `air_accel < ground_accel` (reduced air control, not zero, not full).
|
|
48
|
+
- [ ] Script defines an `external_velocity: Vector3` accumulator AND a public `add_external_velocity(impulse: Vector3)` method.
|
|
49
|
+
- [ ] Script subtracts the previous frame's external contribution at the top of `_physics_process` BEFORE applying gravity (prevents knockback compounding).
|
|
50
|
+
- [ ] Script uses `move_toward` for horizontal acceleration/deceleration — not `velocity.x = direction.x * speed` snap.
|
|
51
|
+
- [ ] Script applies asymmetric gravity (`fall_gravity_multiplier` when `velocity.y < 0`).
|
|
52
|
+
- [ ] Mouse look pitches `Head`, not `Player`. Pitch is clamped to `[pitch_min, pitch_max]`.
|
|
53
|
+
- [ ] `Input.mouse_mode = Input.MOUSE_MODE_CAPTURED` set in `_ready`.
|
|
42
54
|
|
|
43
55
|
## Case 2: Failure Path — existing PlayerOld in scene
|
|
44
56
|
|
|
@@ -83,16 +95,17 @@
|
|
|
83
95
|
|
|
84
96
|
**Expected behavior:**
|
|
85
97
|
|
|
86
|
-
- Skill detects no MCP and falls back to direct file edits on `level.tscn
|
|
87
|
-
- Writes a `.tscn` snippet with CharacterBody3D + CollisionShape3D + Camera3D following the format in the skill's "Fallback (no MCP)"
|
|
88
|
-
- Writes `scripts/player_controller.gd` directly via host file-write tools.
|
|
89
|
-
- Edits `project.godot` to add the InputMap actions.
|
|
98
|
+
- Skill detects no MCP and falls back to direct file edits on `level.tscn` (or whatever main scene exists).
|
|
99
|
+
- Writes a `.tscn` snippet with CharacterBody3D + CollisionShape3D + Head Node3D + Camera3D following the format in the skill's "Fallback (no MCP)" section.
|
|
100
|
+
- Writes `scripts/player_controller.gd` directly via host file-write tools — same skeleton as the MCP path (coyote + buffer + air-accel + external-velocity + move_toward).
|
|
101
|
+
- Edits `project.godot` to add the InputMap actions (all 6: WASD + jump + sprint).
|
|
90
102
|
|
|
91
103
|
**Assertions:**
|
|
92
104
|
|
|
93
105
|
- [ ] Skill identifies that MCP is unavailable (does not blindly call `summer_*` tools and fail).
|
|
94
|
-
- [ ] Generated `.tscn` is syntactically valid Godot 4 scene format (parseable by Godot).
|
|
95
|
-
- [ ] Generated `project.godot` patch follows Godot's input action format (`input/move_forward = { ... }`).
|
|
106
|
+
- [ ] Generated `.tscn` is syntactically valid Godot 4 scene format (parseable by Godot, includes `[gd_scene ... format=3]` header and a CapsuleShape3D sub-resource with `radius` and `height`).
|
|
107
|
+
- [ ] Generated `project.godot` patch follows Godot's input action format (`input/move_forward = { "deadzone": ..., "events": [...] }`).
|
|
108
|
+
- [ ] Generated script still includes coyote_time, jump_buffer_time, external_velocity, move_toward acceleration model (the fallback path is NOT a downgraded skeleton).
|
|
96
109
|
- [ ] Skill still asks "May I write …" before each file write.
|
|
97
110
|
|
|
98
111
|
---
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Skill Spec: /host-authoritative-state
|
|
2
|
+
|
|
3
|
+
## Fixture
|
|
4
|
+
|
|
5
|
+
- A Godot 4.5 / Summer Engine project with `/peer-to-peer-multiplayer` Layer 1 already done — `NetworkManager` autoload exists, exposes `peer_joined` / `peer_left` signals.
|
|
6
|
+
- Project may have a partial player scene; no Managers yet.
|
|
7
|
+
- Summer MCP tools available; engine on localhost:6550.
|
|
8
|
+
|
|
9
|
+
## Case 1: Happy Path — design HealthManager from scratch with full ownership audit
|
|
10
|
+
|
|
11
|
+
**Input:** "I want to add health, damage, and a kill counter to my multiplayer game. Help me set it up correctly."
|
|
12
|
+
|
|
13
|
+
**Expected behavior:**
|
|
14
|
+
|
|
15
|
+
1. Skill confirms `NetworkManager` already exists; if not, defers to `/peer-to-peer-multiplayer`.
|
|
16
|
+
2. Skill walks the user through the fundamental question on each new field: `health`, `max_health`, `score`, `kills`. All four → host-owned.
|
|
17
|
+
3. Skill proposes one Manager per domain — `HealthManager` for health/max_health, `ScoreManager` for score/kills. Asks before creating each.
|
|
18
|
+
4. For `HealthManager`, skill walks the canonical shape:
|
|
19
|
+
- Data dict keyed by `peer_id`.
|
|
20
|
+
- `_host_apply_damage()` mutator with `if not NetworkManager.is_host: return` first line.
|
|
21
|
+
- `_client_request_damage()` RPC with `@rpc("any_peer", "call_remote", "reliable")` and validation (range, line-of-sight, cooldown, magnitude).
|
|
22
|
+
- `_broadcast_health()` and `_broadcast_death()` with `@rpc("authority", "call_remote", "reliable")`.
|
|
23
|
+
- `peer_joined` registers, `peer_left` cleans up.
|
|
24
|
+
5. After file write, `summer_get_script_errors` to verify clean compile.
|
|
25
|
+
|
|
26
|
+
**Expected MCP tool sequence (partial):**
|
|
27
|
+
|
|
28
|
+
1. `summer_get_scene_tree` — confirm NetworkManager autoload presence.
|
|
29
|
+
2. `Read autoloads/network_manager.gd` — confirm signals match expected shape.
|
|
30
|
+
3. (User approves HealthManager) `Write autoloads/HealthManager.gd`.
|
|
31
|
+
4. `summer_get_script_errors` to verify.
|
|
32
|
+
5. (User approves ScoreManager) repeat.
|
|
33
|
+
|
|
34
|
+
**Assertions:**
|
|
35
|
+
|
|
36
|
+
- [ ] Skill applies the fundamental question explicitly to each field — does not silently dump generic state.
|
|
37
|
+
- [ ] Skill proposes one Manager per domain, not one mega-Manager for all state.
|
|
38
|
+
- [ ] Every `_host_apply_*` function's first line is `if not NetworkManager.is_host: return`.
|
|
39
|
+
- [ ] Every `_client_request_*` is `@rpc("any_peer", "call_remote", "reliable")` and has at least three validators.
|
|
40
|
+
- [ ] Every `_broadcast_*` is `@rpc("authority", "call_remote", "reliable")`.
|
|
41
|
+
- [ ] `peer_joined` and `peer_left` lifecycle is wired in `_ready()`.
|
|
42
|
+
- [ ] Skill does NOT use `MultiplayerSynchronizer` for any health/score field.
|
|
43
|
+
- [ ] Skill asks "May I…" before each new file creation.
|
|
44
|
+
|
|
45
|
+
## Case 2: Late-join state sync
|
|
46
|
+
|
|
47
|
+
**Input:** "I have HealthManager working but new players who join mid-match see 100 HP for everyone, even players who are at 30 HP. Fix it."
|
|
48
|
+
|
|
49
|
+
**Expected behavior:**
|
|
50
|
+
|
|
51
|
+
- Skill identifies the bug: no late-join state replay.
|
|
52
|
+
- Skill explains the pattern: `_send_full_state(peer_id)` method on each Manager, called from `peer_joined`, uses `rpc_id` (flavor 4) to target only the joining peer.
|
|
53
|
+
- Skill audits all existing Managers for missing `_send_full_state` and adds it.
|
|
54
|
+
- Skill verifies idempotency — receiving a broadcast twice for the same peer must be a no-op.
|
|
55
|
+
|
|
56
|
+
**Assertions:**
|
|
57
|
+
|
|
58
|
+
- [ ] Skill diagnoses the missing late-join replay before writing code.
|
|
59
|
+
- [ ] Skill uses `rpc_id(target_peer, ...)` (flavor 4), NOT a broadcast to all peers.
|
|
60
|
+
- [ ] Skill audits ALL existing Managers, not just HealthManager — score, inventory, cooldowns all need the same fix.
|
|
61
|
+
- [ ] Skill checks that broadcast handlers are idempotent before shipping.
|
|
62
|
+
- [ ] `summer_get_script_errors` runs after each file edit.
|
|
63
|
+
|
|
64
|
+
## Case 3: Edge — client wants to lie about position (client-predicted)
|
|
65
|
+
|
|
66
|
+
**Input:** "A peer reports another player is teleporting around the map. I added validation to `_client_request_position()` and it blocks legitimate movement. What do I do?"
|
|
67
|
+
|
|
68
|
+
**Expected behavior:**
|
|
69
|
+
|
|
70
|
+
- Skill identifies the user has put `position` into the wrong column. Position is **host-authoritative with client prediction**, not pure host-owned.
|
|
71
|
+
- Skill explains: clients send their input/intent (movement direction + jump), not their final position. Host runs the same physics function authoritatively. If host's computed position diverges from client's reported position by more than a tolerance, host snaps the client (reconciliation).
|
|
72
|
+
- Skill removes the `_client_request_position` validator and points to `/peer-to-peer-multiplayer` Layer 4 for the prediction/reconciliation pattern.
|
|
73
|
+
- Skill notes that "blocking" position requests defeats the point of prediction — it would make movement feel terrible. The right response to a divergent client is reconciliation, not rejection.
|
|
74
|
+
|
|
75
|
+
**Assertions:**
|
|
76
|
+
|
|
77
|
+
- [ ] Skill recognizes position is the "Sometimes" category from the fundamental question, not pure host-owned.
|
|
78
|
+
- [ ] Skill defers reconciliation logic to `/peer-to-peer-multiplayer` Layer 4 instead of inventing a new pattern.
|
|
79
|
+
- [ ] Skill does NOT propose validating each position update with range/distance checks — that's the wrong tool.
|
|
80
|
+
- [ ] Skill explains why teleport-cheating is caught by reconciliation, not rejection.
|
|
81
|
+
|
|
82
|
+
## Case 4: No Summer MCP — fallback path
|
|
83
|
+
|
|
84
|
+
**Fixture:** Same as Case 1, but Summer MCP unavailable.
|
|
85
|
+
|
|
86
|
+
**Input:** "Add a HealthManager autoload to my multiplayer game."
|
|
87
|
+
|
|
88
|
+
**Expected behavior:**
|
|
89
|
+
|
|
90
|
+
- Skill detects MCP unavailable.
|
|
91
|
+
- Walks the same Manager shape (data, mutators, requests, broadcasts, lifecycle).
|
|
92
|
+
- Instead of `summer_get_script_errors`, asks the user to save and check errors via the Godot editor.
|
|
93
|
+
- Provides exact `[autoload]` lines for `project.godot` to paste.
|
|
94
|
+
- File write still goes through `Write` host tool with "May I…" approval.
|
|
95
|
+
|
|
96
|
+
**Assertions:**
|
|
97
|
+
|
|
98
|
+
- [ ] Skill detects MCP unavailable; doesn't blindly call `summer_*` tools and fail.
|
|
99
|
+
- [ ] `project.godot` autoload lines are provided as exact paste-able text.
|
|
100
|
+
- [ ] Manager shape (5-part: data / mutators / requests / broadcasts / lifecycle) is identical regardless of MCP availability.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
This spec runs via `/skill-test host-authoritative-state spec` (see `_meta/skill-test/SKILL.md`).
|