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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "summer-engine",
3
- "version": "1.4.0",
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",
@@ -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 + states + telegraph + loot hook + "May I create?")
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 defines an `enum State` with at least 4 states (idle / alert / attack / dead).
40
- - [ ] Script has an attack `telegraph` parameter in 0.3–0.7 sec range.
41
- - [ ] Script emits a `died` signal.
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 FSM + LOS perception will eat your frame budget. For wave mobs at this scale, the right archetype is the wave-mob (2 states, no LOS, direct line to player). Want me to build that instead?"
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
- - Walks the four pillars but at boss scale: more tells per state, telegraph 0.5–0.7 sec, distinct mechanics per phase (cap at 4–6 total mechanics).
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 the relevant `.tscn` snippet) so node paths are right.
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 (or a connection block in the `.tscn`).
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 the same FSM structure (states / telegraph / died signal).
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. `summer_add_node(parent="./World/Player", type="Camera3D", name="Camera")`
20
- 5. `summer_set_prop("./World/Player/Collision", "shape", "CapsuleShape3D")` (creates standalone, NOT inline sub-resource)
21
- 6. `summer_set_resource_property("./World/Player/Collision", "shape.height", "1.8")` (only after the shape is standalone)
22
- 7. `summer_set_prop("./World/Player/Camera", "position", "Vector3(0, 1.6, 0)")`
23
- 8. `summer_input_map_bind(name="move_forward", events=[{type:"key", key:"W"}])`
24
- 9. `summer_input_map_bind(name="move_back", events=[{type:"key", key:"S"}])`
25
- 10. `summer_input_map_bind(name="move_left", events=[{type:"key", key:"A"}])`
26
- 11. `summer_input_map_bind(name="move_right", events=[{type:"key", key:"D"}])`
27
- 12. `summer_input_map_bind(name="jump", events=[{type:"key", key:"Space"}])`
28
- 13. (User-side write) `Write` script file `scripts/player_controller.gd` after the agent asks "May I attach the controller script to ./World/Player?"
29
- 14. `summer_save_scene`
30
- 15. `summer_get_script_errors`
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
- - [ ] Camera3D positioned at `y ≈ 1.6` (head height).
37
- - [ ] All 5 input actions bound: `move_forward`, `move_back`, `move_left`, `move_right`, `jump`.
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)" sections.
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 on a script).
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 / movement / pickups / damage / explosions / spell casts."
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`, then `summer_inspect_node "./World/Enemy"` to confirm a MeshInstance3D and a `damaged` signal exist.
18
- 4. Skill proposes hit flash + camera shake bundle: "I'm about to add HitFlash on `./World/Enemy`, attach `scripts/hit_flash.gd`, attach `scripts/camera_shake.gd` to `./World/Player/Camera3D`, and connect the `damaged` signal. OK?"
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 the scene tree and inspects the relevant node before adding nodes.
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 script uses Tween + `set_surface_override_material`, NOT a polled timer in `_process`.
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
- - [ ] Skill never calls `summer_set_resource_property` on an inline sub_resource (e.g. ParticleProcessMaterial).
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 — no Camera3D in scene
46
+ ## Case 2: Failure Path — missing audio buses
39
47
 
40
- **Fixture:** `World/Player` exists but has no Camera3D child. User asks for screen shake.
48
+ **Fixture:** project's `default_bus_layout.tres` has only `Master`. No `Music`, `Ambient`, `SFX`.
41
49
 
42
- **Input:** "Add screen shake when I take damage."
50
+ **Input:** "Add hit feedback. Audio ducking too."
43
51
 
44
52
  **Expected behavior:**
45
53
 
46
- - Skill calls `summer_get_scene_tree` and `summer_inspect_node "./World/Player"`, sees no Camera3D.
47
- - Skill does NOT silently spawn a Camera3D — it asks: "I don't see a Camera3D under `./World/Player`. Should I add one (and risk conflicting with an existing camera elsewhere), or do you have a camera node I should target?"
48
- - Waits for the answer.
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 the missing Camera3D and stops to ask.
53
- - [ ] Skill does NOT add a Camera3D unilaterally.
54
- - [ ] Skill names a concrete next-step path (point to existing camera OR add new one explicitly).
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 — user wants "all the juice"
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:** "Add all the VFX. Make it feel like Hades."
71
+ **Input:** "Make my health pickups feel more rewarding when collected."
61
72
 
62
73
  **Expected behavior:**
63
74
 
64
- - Skill resists the temptation to stack 5 recipes at once.
65
- - Anchors on combat hits first (per skill's own guidance: "loudest perceived improvement per minute spent").
66
- - Says: "I'd rather land hit-flash + screen-shake first so we can see them work, then stack pickups/explosions next. Sound good?"
67
- - Does NOT propose a single bundle of 5 recipes.
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 explicitly chooses ONE recipe to start with, not all.
72
- - [ ] Skill names "combat hits" as the anchor.
73
- - [ ] Skill defers other recipes to a follow-up pass.
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 a hit flash to my enemy."
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 (the Enemy node + its children) so the skill can write the right node-path references.
85
- - Writes `scripts/hit_flash.gd` directly via host file tools.
86
- - Provides a `.tscn` patch (or step-by-step manual editor instructions) for adding the HitFlash node and connecting the signal — does NOT blindly call `summer_*` tools.
87
- - Still asks "May I write `scripts/hit_flash.gd`?" before each file write.
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 is unavailable and adapts.
92
- - [ ] Generated GDScript is syntactically valid Godot 4 GDScript with type hints.
93
- - [ ] Skill asks the user to paste the `.tscn` snippet for accurate node paths.
94
- - [ ] Skill still applies the "May I write …" protocol on file writes.
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