summer-store is a skill from the Summer skill library: a guide your AI agent reads when the task matches, so it builds the good version on the first try. Sell Summer items for Sparks in a multiplayer game: shop, checkout, owned items, authority entitlement checks.
When your agent uses it
- Adding a shop, in-game purchases, cosmetics or skins sold for Sparks to a Summer game
- Checking on the authority that a player really owns an item before letting them use or equip it
- Checkout shows pending, manual_review, insufficient_funds or unavailable, or bought items don’t appear
Install
summer setup installs every skill for your agent. To add only this one:
claude-code for your agent, such as codex or cursor. See Skills for every option.
The skill
Overview
Players pay with Sparks, Summer’s currency. Your game never touches money, balances or approval:- The client lists your published offers with
Summer.client.store.list_items(). - The player presses Buy. The client asks for a checkout with offer ids and quantities, never prices.
- The Summer app shows its own purchase sheet. The player approves or declines there.
- Summer charges the Sparks and issues the item into the player’s inventory.
- The game sees the item in
Summer.client.items(client) andSummer.authority.items(authority).
The rule that makes it cheat-proof: the authority decides what a player may
use by reading that player’s inventory itself. A client saying “I bought it”
proves nothing.
What runs where:
Items and offers are created in your game’s Studio pages (
studio.open target
game). Code never creates items.
This skill extends the Courtyard game from multiplayer-project and
multiplayer-state.
Steps
1. The client shop
client/shop.gd, a child Shop of Main. In client/main.gd’s _ready(), call
$Shop.start() right after Summer.initialize succeeds:
checkouts_changed to the Buy buttons: disable them while busy is
true. Update rows by id; a status can be delivered again after a crash, so
rendering must be idempotent. Show each checkout state:
2. Unlock from the inventory only
Unlock cosmetics in menus withowns(definition_id), and refresh the menu on
refreshed, item_replaced and item_removed. Never unlock from a
purchased status: the receipt proves the debit, the inventory proves
ownership, including after a reinstall or on another device. An empty inventory
before is_projection_loaded() is “still loading”, not “owns nothing”.
3. The authority checks entitlement
Anything that changes the match because of an item goes through a Command, and the authority reads that Session’s inventory. Add"equip" to the match in
authority/main.gd’s _on_command:
await command({"c": "equip", "definition": definition_id})
and every client draws hats from match_doc.hats. Other players see the hat
only because the authority published it.
Summer.authority.items.inventory_changed(session, change) hints that a
player’s inventory changed during the match. To drop an item that was
refunded mid-match, re-read with inventory_for_session (or
refresh_inventory) on that hint.
4. Rules and why
- Offer ids and quantities only. Never send a price, an amount or a buyer. The platform prices everything, and a client-side price is what a cheater edits.
- One
request_checkoutis one purchase. Each call gets a new identity. Never call it again because a result is slow;is_outcome_unknown()means it may already be charged, and the status will still arrive. - Connect
checkout_status_changednormally, before reading statuses. A synchronous handler is how the SDK knows the result was delivered; a deferred-only handler leaves it undelivered. pendingandmanual_revieware not failures. Don’t show success or failure until a terminal state.- Carts: 1–16 different offers, at most 64 units, as one atomic purchase.
- Hide what’s unavailable.
check_browse_readiness()andcheck_checkout_readiness()return aSummerResult; show the shop only when they’reok.
5. Test
Local Play. The store and checkout reportunavailable, so the hidden-shop
path is what you test. Inventory and the authority check work from a fixture:
create .summer/test-users.json with item templates.
- Every persona owns one item per template.
- Ids are local stand-ins. Bob’s hat has owned item id
local-item:bob:0, definition idlocal-definition:courtyard:0, anditem_template_version_id"hat". - A check against your real catalog definition id is therefore
falsein Local Play. To exercise the authority path locally, equip with the local definition id the client reads fromitems.get_owned_items().
- decline the sheet;
- buy with too few Sparks;
- buy, then kill the game while the purchase is pending and relaunch: the result must arrive exactly once.
Checklist
- Local Play: the shop button is hidden, and there are no script errors.
- Local Play with the fixture:
items.refresh()succeeds, the owned item is listed,equipis accepted for it and refused withnot_ownedfor anything else. - No code path sends a price or grants an item from a checkout status.
- Buy is disabled while
has_pending_checkouts()is true. -
manual_reviewshows “confirming”, not success or failure. - Staging: a purchase shows up in the inventory, and the equipped item appears for every player.
Common mistakes
See also
multiplayer-state: Commands and the match document used for equipped itemsmultiplayer-testing: Local Play fixtures and personassummer-player-data: saving a player’s loadout between matchessummer-analytics: measuring the shop
From a shell
No Summer MCP tools in this session? Run each one as a command; whensummer --version is missing or below 4.0, use npx -y summer-engine@latest in place of summer. Fields are flags in kebab-case (--scene-path res://main.tscn) or one --input '<json>', and summer ops describe <op> prints the exact input. The summer-cli skill has the rest.
Related skills
multiplayer: Start here for Summer multiplayer: how authority, clients, Worlds and Sessions fit, and what comes next.multiplayer-state: Authority-owned state in Summer multiplayer: shared and private State groups, Commands, Events.multiplayer-testing: Test a Summer multiplayer game with Local Play: several players, bad network, bots, a passing run.summer-player-data: Save a player’s progress so it follows them to every match: authority load and commit, autosave, safe retries.summer-analytics: Send game events to Summer analytics from the authority or client: fire-and-forget, never blocking gameplay.
Source:
library/skills/summer-store/SKILL.md, rendered from summer-engine@4.3.0 on npm.
