summer-engine 1.4.0 → 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/dist/lib/skills-registry.d.ts +16 -0
- package/dist/lib/skills-registry.js +33 -0
- package/package.json +1 -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/vfx.md +56 -38
- package/skills/ai-and-npcs/design-npc/SKILL.md +264 -122
- package/skills/catalog.yaml +4 -2
- package/skills/character-controllers/fps-controller/SKILL.md +260 -79
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +332 -0
- package/skills/visual-effects/vfx/SKILL.md +233 -134
|
@@ -160,6 +160,22 @@ export declare const SKILL_REGISTRY: readonly [{
|
|
|
160
160
|
readonly recommended: true;
|
|
161
161
|
readonly requiresMcpTools: readonly ["summer_get_scene_tree", "summer_inspect_node", "summer_add_node", "summer_set_prop", "summer_connect_signal", "summer_save_scene", "summer_get_script_errors", "summer_project_setting", "summer_play", "summer_stop"];
|
|
162
162
|
readonly testScenario: "Add co-op LAN multiplayer (2-4 players) using MultiplayerAPI + MultiplayerSpawner + MultiplayerSynchronizer + ENet; lock authority model up front.";
|
|
163
|
+
}, {
|
|
164
|
+
readonly name: "peer-to-peer-multiplayer";
|
|
165
|
+
readonly category: "multiplayer-and-networking";
|
|
166
|
+
readonly public: true;
|
|
167
|
+
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
168
|
+
readonly recommended: true;
|
|
169
|
+
readonly requiresMcpTools: readonly ["summer_get_scene_tree", "summer_inspect_node", "summer_create_scene", "summer_add_node", "summer_set_prop", "summer_connect_signal", "summer_save_scene", "summer_get_script_errors", "summer_project_setting"];
|
|
170
|
+
readonly testScenario: "Build the four-layer P2P architecture from the ground up (NetworkManager + GameState + RPC patterns + prediction/interpolation) for a 2-player co-op game.";
|
|
171
|
+
}, {
|
|
172
|
+
readonly name: "host-authoritative-state";
|
|
173
|
+
readonly category: "multiplayer-and-networking";
|
|
174
|
+
readonly public: true;
|
|
175
|
+
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
176
|
+
readonly recommended: true;
|
|
177
|
+
readonly requiresMcpTools: readonly ["summer_get_scene_tree", "summer_inspect_node", "summer_save_scene", "summer_get_script_errors"];
|
|
178
|
+
readonly testScenario: "Walk a 17-field state ownership audit, design a HealthManager + ScoreManager with the canonical Manager pattern, and verify late-join state replay works.";
|
|
163
179
|
}, {
|
|
164
180
|
readonly name: "export-and-ship";
|
|
165
181
|
readonly category: "deployment";
|
|
@@ -332,6 +332,39 @@ export const SKILL_REGISTRY = [
|
|
|
332
332
|
],
|
|
333
333
|
testScenario: "Add co-op LAN multiplayer (2-4 players) using MultiplayerAPI + MultiplayerSpawner + MultiplayerSynchronizer + ENet; lock authority model up front.",
|
|
334
334
|
},
|
|
335
|
+
{
|
|
336
|
+
name: "peer-to-peer-multiplayer",
|
|
337
|
+
category: "multiplayer-and-networking",
|
|
338
|
+
public: true,
|
|
339
|
+
clients: ALL_CLIENTS,
|
|
340
|
+
recommended: true,
|
|
341
|
+
requiresMcpTools: [
|
|
342
|
+
"summer_get_scene_tree",
|
|
343
|
+
"summer_inspect_node",
|
|
344
|
+
"summer_create_scene",
|
|
345
|
+
"summer_add_node",
|
|
346
|
+
"summer_set_prop",
|
|
347
|
+
"summer_connect_signal",
|
|
348
|
+
"summer_save_scene",
|
|
349
|
+
"summer_get_script_errors",
|
|
350
|
+
"summer_project_setting",
|
|
351
|
+
],
|
|
352
|
+
testScenario: "Build the four-layer P2P architecture from the ground up (NetworkManager + GameState + RPC patterns + prediction/interpolation) for a 2-player co-op game.",
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: "host-authoritative-state",
|
|
356
|
+
category: "multiplayer-and-networking",
|
|
357
|
+
public: true,
|
|
358
|
+
clients: ALL_CLIENTS,
|
|
359
|
+
recommended: true,
|
|
360
|
+
requiresMcpTools: [
|
|
361
|
+
"summer_get_scene_tree",
|
|
362
|
+
"summer_inspect_node",
|
|
363
|
+
"summer_save_scene",
|
|
364
|
+
"summer_get_script_errors",
|
|
365
|
+
],
|
|
366
|
+
testScenario: "Walk a 17-field state ownership audit, design a HealthManager + ScoreManager with the canonical Manager pattern, and verify late-join state replay works.",
|
|
367
|
+
},
|
|
335
368
|
{
|
|
336
369
|
name: "export-and-ship",
|
|
337
370
|
category: "deployment",
|
package/package.json
CHANGED
|
@@ -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`).
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Fixture
|
|
4
4
|
|
|
5
|
-
- Godot 4.5 / Summer Engine project, main scene with `World/Player` (CharacterBody3D + Camera3D) and `World/Enemy` (CharacterBody3D + MeshInstance3D + a `damaged` signal
|
|
5
|
+
- Godot 4.5 / Summer Engine project, main scene with `World/Player` (CharacterBody3D + Camera3D) and `World/Enemy` (CharacterBody3D + MeshInstance3D + a `damaged(amount)` signal).
|
|
6
|
+
- `default_bus_layout.tres` defines `Master`, `Music`, `Ambient`, `SFX` buses (at minimum).
|
|
6
7
|
- Summer MCP tools available (engine running on localhost:6550).
|
|
7
8
|
- Host file tools available (Read, Edit, Write).
|
|
8
9
|
|
|
@@ -12,14 +13,16 @@
|
|
|
12
13
|
|
|
13
14
|
**Expected behavior:**
|
|
14
15
|
|
|
15
|
-
1. Skill asks one focused question: "What feels flat? combat hits /
|
|
16
|
+
1. Skill asks one focused question: "What feels flat? combat hits / pickups / explosions / spell casts / something else."
|
|
16
17
|
2. User says "combat hits".
|
|
17
|
-
3. Skill calls `summer_get_scene_tree`,
|
|
18
|
-
4. Skill proposes
|
|
18
|
+
3. Skill calls `summer_get_scene_tree`, `summer_inspect_node "./World/Enemy"`, `summer_inspect_node "./World/Player/Camera3D"`, and confirms the audio bus list.
|
|
19
|
+
4. Skill proposes the full trio bundle: "I'm about to add HitFlash on `./World/Enemy`, CameraShake script on `./World/Player/Camera3D`, AudioDucker autoload, and wire the `damaged` signal so one hit fires all three. OK?"
|
|
19
20
|
5. After user OK:
|
|
20
21
|
- `summer_add_node(parent="./World/Enemy", type="Node", name="HitFlash")`
|
|
21
|
-
- `Write scripts/hit_flash.gd`
|
|
22
|
-
- `Write scripts/camera_shake.gd`
|
|
22
|
+
- `Write scripts/vfx/hit_flash.gd`
|
|
23
|
+
- `Write scripts/vfx/camera_shake.gd`
|
|
24
|
+
- `Write scripts/vfx/audio_ducker.gd`
|
|
25
|
+
- Register `AudioDucker` as an autoload (project settings or `.cfg` edit).
|
|
23
26
|
- `summer_connect_signal(from="./World/Enemy", signal="damaged", to="./World/Enemy/HitFlash", method="flash")`
|
|
24
27
|
- `summer_save_scene`
|
|
25
28
|
- `summer_get_script_errors`
|
|
@@ -27,71 +30,86 @@
|
|
|
27
30
|
**Assertions:**
|
|
28
31
|
|
|
29
32
|
- [ ] Skill asks the "what feels flat?" question before any tool call.
|
|
30
|
-
- [ ] Skill reads
|
|
33
|
+
- [ ] Skill reads scene tree AND inspects both Enemy mesh and Player Camera3D before mutation.
|
|
34
|
+
- [ ] Skill confirms audio bus names exist before installing AudioDucker.
|
|
31
35
|
- [ ] Skill explicitly states the bundle of changes and asks "May I" / "OK?" before mutation.
|
|
32
|
-
- [ ] HitFlash
|
|
36
|
+
- [ ] HitFlash uses `create_tween()` + `tween_callback`, NOT `await create_timer().timeout`.
|
|
37
|
+
- [ ] HitFlash kills any in-flight tween on re-entry (rapid hits).
|
|
33
38
|
- [ ] CameraShake uses quadratic falloff (`_trauma * _trauma`), NOT linear.
|
|
34
|
-
- [ ]
|
|
39
|
+
- [ ] CameraShake subtracts last frame's `_shake_offset` before applying the new one.
|
|
40
|
+
- [ ] AudioDucker captures `_initial_volumes` once in `_ready()` and applies ducking as an offset (relative dB drop), NOT absolute writes.
|
|
41
|
+
- [ ] AudioDucker kills any in-flight tween before starting a new duck.
|
|
42
|
+
- [ ] Final hit flash duration is 0.06–0.12 sec.
|
|
43
|
+
- [ ] Skill never calls `summer_set_resource_property` on an inline sub_resource (e.g. for the flash StandardMaterial3D).
|
|
35
44
|
- [ ] Skill ends with `summer_save_scene` then `summer_get_script_errors`.
|
|
36
|
-
- [ ] Final hit flash duration is 0.06–0.12 sec (NOT 0.3 sec).
|
|
37
45
|
|
|
38
|
-
## Case 2: Failure Path —
|
|
46
|
+
## Case 2: Failure Path — missing audio buses
|
|
39
47
|
|
|
40
|
-
**Fixture:** `
|
|
48
|
+
**Fixture:** project's `default_bus_layout.tres` has only `Master`. No `Music`, `Ambient`, `SFX`.
|
|
41
49
|
|
|
42
|
-
**Input:** "Add
|
|
50
|
+
**Input:** "Add hit feedback. Audio ducking too."
|
|
43
51
|
|
|
44
52
|
**Expected behavior:**
|
|
45
53
|
|
|
46
|
-
- Skill calls `summer_get_scene_tree`
|
|
47
|
-
-
|
|
48
|
-
-
|
|
54
|
+
- Skill calls `summer_get_scene_tree` then inspects the bus layout (or asks the user to paste it).
|
|
55
|
+
- Sees only `Master` exists.
|
|
56
|
+
- Skill does NOT install AudioDucker silently — it explains: "Audio ducking only works if at least Music/Ambient/SFX buses exist. With one Master bus, ducking would also duck the impact SFX you're trying to highlight. I can either (a) install hit-flash + camera-shake now and defer ducking until you set up buses via the `audio-direction` skill, or (b) walk through bus setup first."
|
|
57
|
+
- Waits for user choice.
|
|
58
|
+
- Whichever path: hit-flash + camera-shake still install correctly.
|
|
49
59
|
|
|
50
60
|
**Assertions:**
|
|
51
61
|
|
|
52
|
-
- [ ] Skill detects
|
|
53
|
-
- [ ] Skill does NOT
|
|
54
|
-
- [ ] Skill names
|
|
62
|
+
- [ ] Skill detects single-bus layout and stops to ask.
|
|
63
|
+
- [ ] Skill does NOT register AudioDucker autoload silently.
|
|
64
|
+
- [ ] Skill names `audio-direction` skill as the prerequisite.
|
|
65
|
+
- [ ] Skill still ships the other two systems in this session if the user picks (a).
|
|
55
66
|
|
|
56
|
-
## Case 3: Edge Case —
|
|
67
|
+
## Case 3: Edge Case — pickup pulse without trauma
|
|
57
68
|
|
|
58
|
-
**Fixture:** Same as Case 1.
|
|
69
|
+
**Fixture:** Same as Case 1, but the user is polishing pickups (peaceful glow), not combat.
|
|
59
70
|
|
|
60
|
-
**Input:** "
|
|
71
|
+
**Input:** "Make my health pickups feel more rewarding when collected."
|
|
61
72
|
|
|
62
73
|
**Expected behavior:**
|
|
63
74
|
|
|
64
|
-
- Skill
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
75
|
+
- Skill asks the focused question; user says "pickups".
|
|
76
|
+
- Skill recognizes pickups should NOT trigger camera shake or aggressive audio ducking — those read as combat, not reward.
|
|
77
|
+
- Skill installs only the HitFlash module (renaming usage as a pickup pulse — same script, called on collection), and proposes a small upward duck (0.2 amount, 0.3 duration) so the pickup "ding" reads above ambient.
|
|
78
|
+
- Skill explicitly says: "Skipping camera shake here — shake reads as 'something hit me', wrong feeling for pickups. Same for heavy ducking. Want a tiny duck (0.2) to make the pickup chime pop?"
|
|
79
|
+
- Does NOT call `add_trauma` from the pickup handler.
|
|
68
80
|
|
|
69
81
|
**Assertions:**
|
|
70
82
|
|
|
71
|
-
- [ ] Skill
|
|
72
|
-
- [ ] Skill
|
|
73
|
-
- [ ]
|
|
83
|
+
- [ ] Skill recognizes pickups ≠ combat and adapts the trio.
|
|
84
|
+
- [ ] Skill explicitly skips camera-shake with reasoning, not silently.
|
|
85
|
+
- [ ] HitFlash script is reused (not a forked copy) — `flash()` called on the pickup mesh.
|
|
86
|
+
- [ ] Audio duck `amount` ≤ 0.3, duration ≤ 0.4 (gentle).
|
|
87
|
+
- [ ] Skill does NOT install the full combat trio when the answer was "pickups".
|
|
74
88
|
|
|
75
89
|
## Case 4: No Summer MCP — file-edit fallback
|
|
76
90
|
|
|
77
91
|
**Fixture:** Same as Case 1, but Summer MCP is unavailable (Cursor in a project where the engine is not connected).
|
|
78
92
|
|
|
79
|
-
**Input:** "Add
|
|
93
|
+
**Input:** "Add hit-flash + camera-shake + audio ducking to my combat."
|
|
80
94
|
|
|
81
95
|
**Expected behavior:**
|
|
82
96
|
|
|
83
97
|
- Skill detects MCP unavailable.
|
|
84
|
-
- Asks the user to paste the relevant `.tscn` snippet
|
|
85
|
-
- Writes `scripts/hit_flash.gd` directly via host file tools.
|
|
86
|
-
- Provides a `.tscn` patch
|
|
87
|
-
-
|
|
98
|
+
- Asks the user to paste (a) the relevant `.tscn` snippet showing Enemy + Player + Camera3D node paths, and (b) the bus list from `default_bus_layout.tres`.
|
|
99
|
+
- Writes `scripts/vfx/hit_flash.gd`, `scripts/vfx/camera_shake.gd`, `scripts/vfx/audio_ducker.gd` directly via host file tools.
|
|
100
|
+
- Provides a `.tscn` patch for adding the HitFlash node and a manual instruction for registering the AudioDucker autoload via `project.godot` or the editor.
|
|
101
|
+
- Provides a manual signal-connect instruction (or a `.tscn` connection block) instead of `summer_connect_signal`.
|
|
102
|
+
- Still asks "May I write `scripts/vfx/<file>.gd`?" before each file write.
|
|
103
|
+
- Does NOT blindly call `summer_*` tools.
|
|
88
104
|
|
|
89
105
|
**Assertions:**
|
|
90
106
|
|
|
91
|
-
- [ ] Skill identifies MCP
|
|
92
|
-
- [ ] Generated GDScript is syntactically valid Godot 4
|
|
93
|
-
- [ ] Skill asks the user to paste the
|
|
94
|
-
- [ ] Skill
|
|
107
|
+
- [ ] Skill identifies MCP unavailable and adapts.
|
|
108
|
+
- [ ] Generated GDScript is syntactically valid Godot 4.5 with type hints.
|
|
109
|
+
- [ ] Skill asks the user to paste the scene snippet AND the bus list.
|
|
110
|
+
- [ ] Skill provides explicit autoload-registration instructions (no `summer_*` autoload tool exists).
|
|
111
|
+
- [ ] Skill applies "May I write …" protocol on each of the 3 file writes.
|
|
112
|
+
- [ ] No `summer_*` tool calls appear in the transcript when MCP is flagged unavailable.
|
|
95
113
|
|
|
96
114
|
---
|
|
97
115
|
|