summer-engine 3.3.0 → 3.4.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.
Files changed (68) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +4 -2
  3. package/.codex-plugin/plugin.json +5 -3
  4. package/.cursor-plugin/plugin.json +4 -2
  5. package/.factory-plugin/plugin.json +4 -2
  6. package/CHANGELOG.md +36 -0
  7. package/README.md +2 -1
  8. package/assets/api-docs.json.gz +0 -0
  9. package/dist/cli/commands/install.d.ts +10 -0
  10. package/dist/cli/commands/install.js +55 -10
  11. package/dist/cli/commands/tool.d.ts +7 -0
  12. package/dist/cli/commands/tool.js +36 -1
  13. package/dist/core/capabilities/capture-gameplay.d.ts +69 -0
  14. package/dist/core/capabilities/capture-gameplay.js +283 -0
  15. package/dist/core/capabilities/creator.js +1 -1
  16. package/dist/core/capabilities/doctor.d.ts +9 -1
  17. package/dist/core/capabilities/doctor.js +47 -4
  18. package/dist/core/capabilities/engine-run.d.ts +4 -0
  19. package/dist/core/capabilities/engine-run.js +33 -5
  20. package/dist/core/capabilities/export-engine-version.d.ts +15 -0
  21. package/dist/core/capabilities/export-engine-version.js +24 -0
  22. package/dist/core/capabilities/export-game.d.ts +9 -1
  23. package/dist/core/capabilities/export-game.js +112 -36
  24. package/dist/core/capabilities/image-generation.d.ts +9 -0
  25. package/dist/core/capabilities/image-generation.js +19 -1
  26. package/dist/core/capabilities/publish-build.d.ts +14 -1
  27. package/dist/core/capabilities/publish-build.js +74 -22
  28. package/dist/core/capabilities/tool-dispatch.js +10 -0
  29. package/dist/core/config.js +2 -2
  30. package/dist/core/engine-install.d.ts +16 -0
  31. package/dist/core/engine-install.js +32 -2
  32. package/dist/core/hosted-mcp.d.ts +115 -0
  33. package/dist/core/hosted-mcp.js +164 -0
  34. package/dist/core/launch-posture.d.ts +3 -0
  35. package/dist/core/launch-posture.js +15 -2
  36. package/dist/mcp/hosted-mount.d.ts +13 -59
  37. package/dist/mcp/hosted-mount.js +24 -30
  38. package/dist/mcp/server.js +3 -2
  39. package/dist/mcp/tools/creator-tools.js +13 -3
  40. package/dist/mcp/tools/generate-tools.js +12 -2
  41. package/gemini-extension.json +3 -1
  42. package/library/references/mcp-tools-reference/mcp-tools-reference.md +3 -2
  43. package/library/skills/README.md +6 -4
  44. package/library/skills/bounded-engine-runs/SKILL.md +96 -0
  45. package/library/skills/bounded-engine-runs/resource.yaml +29 -0
  46. package/library/skills/compatibility-renderer-traps/SKILL.md +78 -0
  47. package/library/skills/compatibility-renderer-traps/resource.yaml +28 -0
  48. package/library/skills/concept-art/SKILL.md +2 -2
  49. package/library/skills/export-and-ship/SKILL.md +2 -0
  50. package/library/skills/headless-scripting/resource.yaml +1 -0
  51. package/library/skills/in-game-purchases/SKILL.md +123 -118
  52. package/library/skills/in-game-purchases/resource.yaml +2 -2
  53. package/library/skills/web-compatibility-look/resource.yaml +1 -0
  54. package/library/tools/capture-gameplay/resource.yaml +73 -0
  55. package/library/tools/generate-image/resource.yaml +15 -1
  56. package/library/tools/publish-build/resource.yaml +1 -1
  57. package/package.json +1 -1
  58. package/registry/generated/aliases.json +1 -0
  59. package/registry/generated/counts.json +3 -3
  60. package/registry/generated/gemini-extension.json +3 -1
  61. package/registry/generated/index.json +125 -12
  62. package/registry/generated/marketplace.claude.json +2 -2
  63. package/registry/generated/plugin.claude.json +4 -2
  64. package/registry/generated/plugin.codex.json +5 -3
  65. package/registry/generated/plugin.cursor.json +4 -2
  66. package/registry/generated/plugin.factory.json +4 -2
  67. package/registry/generated/skills-index.md +6 -4
  68. package/registry/generated/skills-registry.json +28 -1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: in-game-purchases
3
- description: "Sell items for Sparks and let short players buy Sparks in game via the Summer purchase sheet: checkout, Sparks purchase, results, testing, store policy."
3
+ description: "Sell items for Sparks in a game shop via the Summer purchase sheet: checkout, results, not enough Sparks, inventory unlocks, authority grants, store policy."
4
4
  license: MIT
5
5
  compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
6
  category: gameplay
@@ -11,14 +11,19 @@ paths: ["**/*.gd"]
11
11
  # In-game purchases with Sparks
12
12
 
13
13
  Sparks are Summer's currency. A game never takes money, never holds a
14
- balance and never approves a payment. It asks Summer, Summer shows its own
15
- purchase sheet over the game, and the game reacts to the authoritative
16
- result. There are two calls and they follow the same shape:
14
+ balance and never approves a payment. It asks Summer for a checkout, Summer
15
+ shows its own purchase sheet over the game, and the game reacts to the
16
+ authoritative result.
17
17
 
18
18
  | The player wants to | Call | Validated by |
19
19
  | --- | --- | --- |
20
20
  | buy an item with Sparks they have | `Summer.client.store.request_checkout(lines)` | the Summer ledger (debit) |
21
- | buy Sparks because they are short | `Summer.client.store.request_sparks_purchase(minimum_sparks)` | the payment provider (Stripe or Apple), then the ledger credit |
21
+ | buy Sparks because they are short | no game call: the player gets Sparks in the Summer app | the Summer app |
22
+
23
+ The engine has no call that sells Sparks from inside the game. When a
24
+ checkout fails with `insufficient_funds`, tell the player they need more
25
+ Sparks and that they can get them in the Summer app, then let them press Buy
26
+ again.
22
27
 
23
28
  ## When to sell with Sparks
24
29
 
@@ -28,9 +33,9 @@ result. There are two calls and they follow the same shape:
28
33
  game.
29
34
  - Do not invent your own currency that is bought with money. *Why:* Summer
30
35
  can only refund, protect and pay you out for Sparks purchases.
31
- - Offer "Get Sparks" only when a purchase failed for lack of Sparks, or on an
32
- explicit shop button. *Why:* the Summer sheet interrupts play; surprise
33
- payment prompts read as pressure and get games reported.
36
+ - Open the shop only from an explicit shop or buy button. *Why:* the Summer
37
+ sheet interrupts play; surprise payment prompts read as pressure and get
38
+ games reported.
34
39
 
35
40
  ## Rules and the reason for each
36
41
 
@@ -38,66 +43,66 @@ result. There are two calls and they follow the same shape:
38
43
  `Summer.client.items` shows the player owns it. *Why:* the receipt proves
39
44
  the debit; the inventory projection is what the platform guarantees the
40
45
  player owns, including after a crash or on another device.
41
- 2. **Never send prices, amounts or providers.** The game passes an offer ID
42
- and a quantity, or a Sparks hint. *Why:* the platform prices everything; a
43
- client-supplied price is exactly what a cheater edits.
44
- 3. **One call is one purchase.** Every call creates a new identity. Never
45
- call again to "retry" an unknown outcome. *Why:* the first request may
46
- already be charged; the SDK and the Summer app recover it and deliver the
47
- result on the signal.
48
- 4. **Handle results only in the status signals, and deduplicate by
46
+ 2. **Never send prices or amounts.** The game passes an offer ID and a
47
+ quantity (`SummerCheckoutLine.create(offer_id, quantity)`). *Why:* the
48
+ platform prices everything; a client-supplied price is exactly what a
49
+ cheater edits.
50
+ 3. **One call is one purchase.** Every `request_checkout` creates a new
51
+ identity. Never call again to "retry" an unknown outcome
52
+ (`SummerCheckoutOperation.is_outcome_unknown()`). *Why:* the first request
53
+ may already be charged; the SDK recovers it and delivers the result on the
54
+ signal.
55
+ 4. **Handle results in `checkout_status_changed`, and deduplicate by
49
56
  `request_id` plus `version`.** Connect normally (not deferred) before
50
- requesting. *Why:* results can arrive after a restart or twice after a
51
- crash; a synchronous handler is how the SDK knows the result was delivered.
52
- 5. **Treat `manual_review` and pending as not done.** *Why:* the platform
57
+ requesting. To draw a checkout panel, read `get_checkout_statuses()` and
58
+ `has_pending_checkouts()` instead of keeping your own list. *Why:* results
59
+ can arrive after a restart or twice after a crash; a synchronous handler is
60
+ how the SDK knows the result was delivered.
61
+ 5. **Treat `manual_review` and `pending` as not done.** *Why:* the platform
53
62
  has not decided yet; showing success or failure would be a guess.
54
63
  6. **Check readiness and hide what is unavailable.**
55
- `check_checkout_readiness()` and `check_sparks_purchase_readiness()` return
56
- a `SummerResult`. *Why:* editor play, guest sessions and older Summer apps
57
- do not offer purchases; a dead button is worse than no button.
64
+ `check_checkout_readiness()` (buying) and `check_browse_readiness()`
65
+ (listing with `list_items()`) return a `SummerResult`. *Why:* editor play,
66
+ guest sessions and older Summer apps do not offer purchases; a dead button
67
+ is worse than no button.
58
68
 
59
69
  ## Result states
60
70
 
61
- Both signals deliver a status with `state`:
71
+ `checkout_status_changed(status: SummerCheckoutStatus)` delivers a status
72
+ whose `state` is one of:
62
73
 
63
74
  | State | Meaning | What the game does |
64
75
  | --- | --- | --- |
65
76
  | `awaiting_approval` | the Summer sheet is open | nothing; optionally dim the shop |
66
- | `pending` | payment or debit in progress | show "processing" |
67
- | `purchased` | done; Sparks debited (checkout) or credited (Sparks) | checkout: refresh items and unlock from inventory. Sparks: say "Sparks added", let the player buy again |
77
+ | `pending` | the debit is in progress | show "processing" |
78
+ | `purchased` | done; Sparks debited, `status.receipt` is set | refresh items and unlock from inventory |
68
79
  | `declined` | the player closed the sheet | return to the shop quietly |
69
- | `expired` | the sheet or payment timed out | same as declined |
70
- | `rejected` | refused; read `error_code` | checkout with `insufficient_funds`: offer "Get Sparks". Sparks: `provider_rejected`, say the payment did not go through |
80
+ | `expired` | the sheet timed out | same as declined |
81
+ | `rejected` | refused; read `error_code` | `insufficient_funds`: "Not enough Sparks. Get Sparks in the Summer app." Other codes: "The purchase was not completed." |
71
82
  | `manual_review` | platform is checking it | "We are confirming this purchase" |
72
- | `refunded` | Sparks only: a credited purchase was refunded or charged back (`full_refund`, `dispute_lost`) | nothing to undo; Summer already took the Sparks back. Refresh any balance display |
73
83
 
74
- A Sparks request can also fail to start with `purchase_limit` (the player
75
- reached the daily in-game Sparks limit) or `age_required` / `adult_required`
76
- (only adults with a saved age can buy Sparks). Show a short message and no
77
- retry button. *Why:* both limits are platform policy; retrying changes nothing.
84
+ `status.is_terminal()` tells you whether the state can still change. Unknown
85
+ future `error_code` values never mean success.
78
86
 
79
- The operation returned by each call also completes (`completed` signal) on
80
- `purchased` (ok) or a terminal failure with codes such as `checkout_declined`
81
- or `sparks_purchase_declined`. The status signal is the one to build UI on.
87
+ The operation returned by `request_checkout` also completes: ok on
88
+ `purchased`, a failure otherwise. The status signal is the one to build UI on.
82
89
 
83
90
  ## Minimal complete example
84
91
 
85
92
  ```gdscript
86
93
  extends Node
87
- ## A one-item shop. Buys `offer_id`; if the player is short, lets them buy
88
- ## Sparks through the Summer sheet, then they press Buy again.
94
+ ## A one-item shop. Buys `offer_id` and unlocks from inventory.
89
95
 
90
96
  @export var offer_id := "offer_sword"
91
97
  @export var definition_id := "item_sword"
92
- @export var price_sparks := 250 # display only; the platform charges its own price
93
98
 
94
99
  signal message(text: String)
95
100
  var _seen := {}
96
101
 
97
102
  func _ready() -> void:
98
- var store := Summer.client.store
99
- store.checkout_status_changed.connect(_on_checkout)
100
- store.sparks_purchase_status_changed.connect(_on_sparks)
103
+ if Summer.client == null:
104
+ return # not running as a player's game
105
+ Summer.client.store.checkout_status_changed.connect(_on_checkout)
101
106
  Summer.client.items.refreshed.connect(_sync_owned)
102
107
  _sync_owned()
103
108
 
@@ -108,13 +113,7 @@ func buy() -> void:
108
113
  var lines: Array[SummerCheckoutLine] = [SummerCheckoutLine.create(offer_id, 1)]
109
114
  Summer.client.store.request_checkout(lines) # result arrives in _on_checkout
110
115
 
111
- func get_sparks() -> void:
112
- if not Summer.client.store.check_sparks_purchase_readiness().ok:
113
- message.emit("Get Sparks from the Summer app.")
114
- return
115
- Summer.client.store.request_sparks_purchase(price_sparks) # hint only
116
-
117
- func _first_time(status) -> bool:
116
+ func _first_time(status: SummerCheckoutStatus) -> bool:
118
117
  var key := "%s:%d" % [status.request_id, status.version]
119
118
  if _seen.has(key):
120
119
  return false
@@ -129,7 +128,7 @@ func _on_checkout(status: SummerCheckoutStatus) -> void:
129
128
  Summer.client.items.refresh() # ownership comes from inventory
130
129
  &"rejected":
131
130
  if status.error_code == &"insufficient_funds":
132
- message.emit("Not enough Sparks.") # show a Get Sparks button -> get_sparks()
131
+ message.emit("Not enough Sparks. Get Sparks in the Summer app, then buy again.")
133
132
  else:
134
133
  message.emit("The purchase was not completed.")
135
134
  &"manual_review":
@@ -137,84 +136,91 @@ func _on_checkout(status: SummerCheckoutStatus) -> void:
137
136
  &"declined", &"expired":
138
137
  pass
139
138
 
140
- func _on_sparks(status: SummerSparksPurchaseStatus) -> void:
141
- if not _first_time(status):
142
- return
143
- match status.state:
144
- &"purchased":
145
- message.emit("%d Sparks added." % status.receipt.sparks_credited)
146
- &"rejected":
147
- message.emit("The payment did not go through.")
148
- &"manual_review":
149
- message.emit("We are confirming your payment.")
150
-
151
139
  func _sync_owned() -> void:
152
140
  if Summer.client.items.has_owned_definition(definition_id):
153
141
  message.emit("Sword unlocked.") # the only place the item is granted
154
142
  ```
155
143
 
156
- After Sparks are added the player presses Buy again. That is a new checkout
157
- and Summer shows it again. *Why:* the player confirms each spend separately.
158
-
159
144
  ## Multiplayer: granting an effect on a dedicated authority
160
145
 
161
- Buying Sparks only adds Sparks to the wallet. Anything the Sparks buy
162
- ("Grow now", "Summon rain") is a separate checkout, and on a server the
163
- authority must never grant it because a client says it paid. *Why:* a client
164
- message is the one thing a cheater controls.
165
-
166
- Model the effect as a consumable catalog item with instance ownership (each
167
- purchase is a separate item, not a stack). *Why:* a stackable (quantity) item
168
- shares one owned item ID across purchases, so the ID could not tell two
169
- purchases apart. The client checks out; the receipt's `owned_item_ids` are item instances in
170
- the player's Summer inventory. The client sends the authority only that owned
171
- item ID. The authority consumes it with
172
- `Summer.authority.items.consume(session, owned_item_id, idempotency_key)` and
173
- applies the effect only after a successful receipt. *Why:* the platform ledger
174
- consumes an instance item once, so the same item cannot be redeemed twice, in
175
- this world or any other.
146
+ Anything the Sparks buy ("Grow now", "Summon rain") is a checkout for an
147
+ item, and on a server the authority must never grant it because a client says
148
+ it paid. *Why:* a client message is the one thing a cheater controls.
149
+
150
+ Model the effect as a catalog item with instance ownership (each purchase is a
151
+ separate owned item, not a stack). *Why:* a stackable item shares one owned
152
+ item ID across purchases, so the ID could not tell two purchases apart.
153
+
154
+ The client checks out; the receipt's `items[i].owned_item_ids` are the owned
155
+ items. The client sends the authority only an owned item ID. The authority:
156
+
157
+ 1. reads that player's inventory with
158
+ `Summer.authority.items.inventory_for_session(session)` and checks the
159
+ item is there, owned, and of the right definition;
160
+ 2. checks its own record of redeemed item IDs, kept in the player's secret
161
+ player data (`Summer.authority.player_data`), so the item grants once per
162
+ player in every world;
163
+ 3. writes the new record, and applies the effect only after that write
164
+ succeeds.
176
165
 
177
166
  ```gdscript
178
- # On the dedicated authority. `redeemed` is saved with the world.
167
+ # On the dedicated authority.
168
+ const EFFECT_DEFINITION := "item_grow_now"
169
+
179
170
  func redeem(session: SummerSession, owned_item_id: String) -> void:
171
+ var inv := Summer.authority.items.inventory_for_session(session)
172
+ var read: SummerResult = await inv.get_result_or_completed_signal()
173
+ if not read.ok:
174
+ return # unavailable: the client may ask again later
175
+ var item: SummerInventoryItem = null
176
+ for candidate in inv.items:
177
+ if candidate.get_owned_item_id() == owned_item_id:
178
+ item = candidate
179
+ if item == null or not item.is_owned() or item.get_definition_id() != EFFECT_DEFINITION:
180
+ return # not owned by this player: never grant
181
+
182
+ var load_op := Summer.authority.player_data.load(session)
183
+ var loaded: SummerResult = await load_op.get_result_or_completed_signal()
184
+ if not loaded.ok:
185
+ return
186
+ var data: Dictionary = load_op.secret_record.data
187
+ var redeemed: Dictionary = data.get("redeemed", {})
180
188
  if redeemed.has(owned_item_id):
181
189
  return # a repeated message grants once
182
- var key := "redeem_" + owned_item_id.sha256_text().substr(0, 40)
183
- var op := Summer.authority.items.consume(session, owned_item_id, key)
184
- var result: SummerResult = await op.get_result_or_completed_signal()
185
- if not result.ok:
186
- # item_not_found: not owned or already used, never grant.
187
- # unavailable: retry later with the same key.
188
- return
189
- redeemed[owned_item_id] = op.command_id # record, apply, then save the world
190
+ redeemed[owned_item_id] = true
191
+ data["redeemed"] = redeemed
192
+ var save_id := "redeem_" + owned_item_id.sha256_text().substr(0, 40)
193
+ var commit := Summer.authority.player_data.commit_secret(session, save_id, data)
194
+ var saved: SummerResult = await commit.get_result_or_completed_signal()
195
+ if not saved.ok:
196
+ return # retryable: commit again with the same save_id
190
197
  apply_grow_now(session)
191
198
  ```
192
199
 
193
- - Derive the key from the owned item ID. *Why:* the same key returns the same
194
- receipt (`op.replayed` is true), so a retry after a timeout never consumes
195
- twice, and a different item with that key fails with `idempotency_conflict`.
196
- - Apply the effect when the receipt arrives even if `op.replayed` is true, as
197
- long as `redeemed` does not have the item. *Why:* a replay means the item was
198
- consumed earlier but your world may have crashed before saving the effect.
199
- - Save the world right after applying. *Why:* the receipt replay is tied to
200
- the authority process; after a restart as a different process the same key
201
- is a new consumption and the already-consumed item returns `item_not_found`.
200
+ - The save ID comes from the owned item ID. *Why:* resending the same save ID
201
+ with the same content returns the earlier commit (`commit.replayed`), so a
202
+ retry after a timeout records the redemption once.
203
+ - Apply the effect when the commit succeeds even if `commit.replayed` is true
204
+ and the effect is not in your world yet. *Why:* a replay means the
205
+ redemption was recorded earlier, but your world may have crashed before it
206
+ applied the effect.
207
+ - The engine has no call that uses up an owned item. The item stays in the
208
+ player's inventory after it is redeemed; the redeemed list is what stops a
209
+ second grant. Show redeemed items as used in your UI.
202
210
 
203
211
  ## Idempotency
204
212
 
205
- The SDK generates the request ID and replays it across transport failures and
206
- app restarts; the platform credits a Sparks purchase once per provider
207
- transaction and debits a checkout once per request. Your only job is the
208
- `request_id:version` dedupe above, so a replayed signal does not show a
209
- message or play an effect twice.
213
+ The SDK generates the checkout request ID and replays it across transport
214
+ failures and app restarts; the platform debits a checkout once per request.
215
+ Your only job is the `request_id:version` dedupe above, so a replayed signal
216
+ does not show a message or play an effect twice.
210
217
 
211
218
  ## Per-surface behaviour
212
219
 
213
- - **Summer Games app on iOS:** a native Summer sheet over the game. Sparks are
214
- bought with Apple In-App Purchase only.
215
- - **Summer desktop app:** an overlay sheet; Sparks are paid with Stripe in the
216
- browser, and the result returns to the game on the signal.
217
- - **Web:** the same sheet in the page, paid with Stripe.
220
+ - **Summer Games app on iOS:** a native Summer sheet over the game.
221
+ - **Summer desktop app:** an overlay sheet; the result returns to the game on
222
+ the signal.
223
+ - **Web:** the same sheet in the page.
218
224
  - **Editor play and local runs:** readiness is `unavailable`. Build the
219
225
  unavailable path first.
220
226
 
@@ -222,31 +228,30 @@ The game code is identical on every surface.
222
228
 
223
229
  ## Testing
224
230
 
225
- - Test against Summer staging, which runs payments in sandbox mode: Stripe
226
- sandbox cards on web and desktop, an Apple Sandbox tester account on iOS test
227
- builds. *Why:* no real money moves and results are identical to production.
231
+ - Test against Summer staging, which runs in sandbox mode. *Why:* no real
232
+ money moves and results are identical to production.
228
233
  - Walk every state: decline the sheet, let it expire, buy with too few
229
- Sparks, buy Sparks, then buy the item. Kill the game while a payment is
230
- pending and relaunch; the result must still arrive once.
234
+ Sparks, get Sparks in the Summer app, then buy the item. Kill the game while
235
+ a checkout is pending and relaunch; the result must still arrive once.
231
236
  - Verify the unlock comes from `items.refreshed`, not from the checkout
232
237
  signal.
233
238
 
234
239
  ## Store policy notes
235
240
 
236
241
  - Apple requires In-App Purchase for digital goods and currency sold inside
237
- iOS apps. Summer's iOS sheet handles this; never add your own payment links,
238
- web prices or "cheaper on the web" text inside the game. *Why:* it can get
239
- the Summer app removed from the App Store for every game.
242
+ iOS apps. Summer's iOS sheet and app handle this; never add your own payment
243
+ links, web prices or "cheaper on the web" text inside the game. *Why:* it
244
+ can get the Summer app removed from the App Store for every game.
240
245
  - Randomized paid rewards must disclose their odds before purchase (Apple and
241
246
  most regions require it).
242
247
  - Say what a purchase gives in plain words next to the price.
243
248
 
244
249
  ## Availability
245
250
 
246
- `request_checkout` ships today. `request_sparks_purchase` needs a Summer
247
- Engine and Summer app release that advertise `player.store.sparks_purchase@1`;
248
- until then `check_sparks_purchase_readiness()` reports `unavailable` and the
249
- example above falls back to "Get Sparks from the Summer app".
251
+ `request_checkout`, `list_items`, `Summer.client.items`,
252
+ `Summer.authority.items.inventory_for_session` and `Summer.authority.player_data`
253
+ ship in the engine today. Buying Sparks inside the game does not: players get
254
+ Sparks in the Summer app.
250
255
 
251
256
  ## See also
252
257
 
@@ -1,10 +1,10 @@
1
1
  id: skill/in-game-purchases
2
2
  kind: skill
3
3
  version: 1.0.0
4
- summary: "Sell items for Sparks and let short players buy Sparks in game via the Summer purchase sheet: checkout, Sparks purchase, results, testing, store policy."
4
+ summary: "Sell items for Sparks in a game shop via the Summer purchase sheet: checkout, results, not enough Sparks, inventory unlocks, authority grants, store policy."
5
5
  use_when:
6
6
  - "adding a shop, buy button or paid item to a Summer game and wiring request_checkout"
7
- - "a checkout was rejected with insufficient_funds and the player should be able to buy Sparks without leaving the game"
7
+ - "the player does not have enough Sparks (checkout rejected with insufficient_funds) and the game should tell them how to buy more Sparks"
8
8
  - "deciding whether an item should be sold for Sparks, or handling pending, declined, expired, rejected or manual_review purchase results"
9
9
  do_not_use_when:
10
10
  - "designing multiplayer state ownership unrelated to purchases (use multiplayer-state)"
@@ -24,6 +24,7 @@ related:
24
24
  - skill/art-direction
25
25
  - skill/tune-performance
26
26
  - skill/ui-basics
27
+ - skill/compatibility-renderer-traps
27
28
  source: official
28
29
  license: MIT
29
30
  status: preview
@@ -0,0 +1,73 @@
1
+ id: tool/capture-gameplay
2
+ kind: tool
3
+ version: 1.0.0
4
+ summary: Capture real gameplay frames (PNG, HUD included) in Summer Engine's offscreen verify instance, without a running editor, e.g. store screenshots.
5
+ use_when:
6
+ - '"take gameplay screenshots for my store page"'
7
+ - '"I need screenshots but the editor is not open"'
8
+ - '"capture a 1920x1080 frame of my game"'
9
+ - collecting real gameplay screenshots for summer_store_set_art desktop.screenshots or mobile.screenshots
10
+ do_not_use_when:
11
+ - the editor is open and the game is running — tool/screenshot with target game shows the live game
12
+ - key art, covers or icons — those are generated art, not gameplay frames
13
+ - checking an interaction with inputs and assertions — RunVerification through tool/batch
14
+ facets:
15
+ lifecycle:
16
+ - launch
17
+ - build
18
+ domains:
19
+ - creator
20
+ - headless
21
+ compatibility:
22
+ engine: ">=4.6"
23
+ source: official
24
+ license: MIT
25
+ status: preview
26
+ aliases:
27
+ - summer_capture_gameplay
28
+ implementation:
29
+ module: src/core/capabilities/capture-gameplay.ts
30
+ export: captureGameplay
31
+ surfaces:
32
+ mcp:
33
+ tool_name: summer_capture_gameplay
34
+ remote: true
35
+ input_schema:
36
+ type: object
37
+ properties:
38
+ project:
39
+ type: string
40
+ description: Project folder with project.godot. Defaults to the MCP's bound project, then the working directory.
41
+ scene:
42
+ type: string
43
+ description: Scene to start (res://... or uid://...). Default is the project's main scene, often a title menu; pass the gameplay scene for gameplay frames.
44
+ resolution:
45
+ type: string
46
+ description: Window size WIDTHxHEIGHT (default 1920x1080; 1080x1920 for portrait).
47
+ frames:
48
+ type: integer
49
+ minimum: 1
50
+ maximum: 10
51
+ description: Frames to save (default 1).
52
+ waitSeconds:
53
+ type: number
54
+ minimum: 0
55
+ maximum: 120
56
+ description: Seconds the game runs before the first frame (default 3).
57
+ intervalSeconds:
58
+ type: number
59
+ minimum: 0.1
60
+ maximum: 60
61
+ description: Seconds between frames (default 1).
62
+ out:
63
+ type: string
64
+ description: Output folder. Defaults to <project>/.summer/captures/<time>/ (ignored by git).
65
+ additionalProperties: false
66
+ authority:
67
+ filesystem: true
68
+ editor_mutation: false
69
+ network: false
70
+ credentials: false
71
+ publish: false
72
+ evidence_checks:
73
+ - none
@@ -45,10 +45,24 @@ input_schema:
45
45
  removeBackground:
46
46
  type: boolean
47
47
  description: Remove the background server-side and return an alpha PNG for sprites, icons, or isolated objects.
48
+ aspectRatio:
49
+ type: string
50
+ enum: ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "5:4", "4:5", "21:9"]
51
+ description: Aspect ratio of the output (default 1:1). The result reports the real width and height.
52
+ width:
53
+ type: integer
54
+ minimum: 64
55
+ maximum: 4096
56
+ description: Smallest output width in pixels; pass with height. 1920 with height 1080 for store key art, 1080 with 1920 for a tall cover.
57
+ height:
58
+ type: integer
59
+ minimum: 64
60
+ maximum: 4096
61
+ description: Smallest output height in pixels; pass with width.
48
62
  options:
49
63
  type: object
50
64
  additionalProperties: {}
51
- description: Provider-specific params (guidance_scale, seed, image_size, negative_prompt, etc.)
65
+ description: Extra model params (seed, negative_prompt, ...). The result lists any it did not use in ignoredOptions.
52
66
  required:
53
67
  - prompt
54
68
  additionalProperties: false
@@ -37,7 +37,7 @@ input_schema:
37
37
  properties:
38
38
  gameId:
39
39
  type: string
40
- description: The store game id (Studio store page URL). Omit to get the list of your games.
40
+ description: The store game id (from summer_store_list_games, or the Studio store page URL). Without it the call fails with game_required and lists your store games, or says why the store did not list them.
41
41
  file:
42
42
  type: string
43
43
  description: Exported .zip. Defaults to the last summer_export_game result.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "summer-engine",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Local Summer CLI and MCP server for Summer Engine. Install and run the engine, connect Claude Code, Cursor, Codex, Gemini, and other agents, and build real games with bundled skills, hooks, and plugins.",
5
5
  "keywords": [
6
6
  "summer-engine",
@@ -103,6 +103,7 @@
103
103
  "summer_attach_to_surface": "tool/attach-to-surface",
104
104
  "summer_batch": "tool/batch",
105
105
  "summer_camera_bookmark": "tool/camera-bookmark",
106
+ "summer_capture_gameplay": "tool/capture-gameplay",
106
107
  "summer_check_job": "tool/check-job",
107
108
  "summer_clear_console": "tool/clear-console",
108
109
  "summer_connect_ports": "tool/connect-ports",
@@ -4,9 +4,9 @@
4
4
  "collection": 0,
5
5
  "example": 0,
6
6
  "reference": 9,
7
- "skill": 116,
7
+ "skill": 118,
8
8
  "template": 19,
9
- "tool": 104
9
+ "tool": 105
10
10
  },
11
- "total": 248
11
+ "total": 251
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_generated": "GENERATED from integrations/gemini — do not edit; npm run generate:registry",
3
3
  "name": "summer",
4
- "version": "3.3.0",
4
+ "version": "3.4.0",
5
5
  "description": "Agent tooling for Summer Engine: MCP bridge, context primer, and game-dev skills. Use with the npm CLI for skill files on disk.",
6
6
  "contextFileName": "GEMINI.md",
7
7
  "mcpServers": {
@@ -35,6 +35,7 @@
35
35
  "./library/skills/asset-strategy/",
36
36
  "./library/skills/audio-direction/",
37
37
  "./library/skills/auto-fire-targeting/",
38
+ "./library/skills/bounded-engine-runs/",
38
39
  "./library/skills/brainstorm-game/",
39
40
  "./library/skills/brainstorming/",
40
41
  "./library/skills/browse-templates/",
@@ -46,6 +47,7 @@
46
47
  "./library/skills/character-portrait/",
47
48
  "./library/skills/cinematic-cutscene/",
48
49
  "./library/skills/combat-basics/",
50
+ "./library/skills/compatibility-renderer-traps/",
49
51
  "./library/skills/concept-art/",
50
52
  "./library/skills/create-asset-sheet/",
51
53
  "./library/skills/debug/",