@idosgames/mcp 0.1.45 → 0.1.46
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/cli.js +1597 -1542
- package/package.json +1 -1
- package/registry/host.json +3 -3
- package/registry/index.json +124 -77
- package/registry/modules/ai.json +2 -2
- package/registry/modules/board-game.json +3 -3
- package/registry/modules/boosts.json +2 -2
- package/registry/modules/character.json +2 -2
- package/registry/modules/chat.json +2 -2
- package/registry/modules/collections.json +2 -2
- package/registry/modules/coop-events.json +2 -2
- package/registry/modules/craft.json +2 -2
- package/registry/modules/deals.json +2 -2
- package/registry/modules/events.json +2 -2
- package/registry/modules/friends.json +2 -2
- package/registry/modules/hero-run.json +13 -232
- package/registry/modules/idle-rpg.json +3 -3
- package/registry/modules/inventory.json +2 -2
- package/registry/modules/invite.json +2 -2
- package/registry/modules/kit-game.json +171 -0
- package/registry/modules/leaderboard.json +2 -2
- package/registry/modules/lootboxes.json +2 -2
- package/registry/modules/mailbox.json +2 -2
- package/registry/modules/marketplace.json +2 -2
- package/registry/modules/mini-game.json +2 -2
- package/registry/modules/multiplayer.json +2 -2
- package/registry/modules/premium.json +2 -2
- package/registry/modules/profile.json +2 -2
- package/registry/modules/pvp.json +2 -2
- package/registry/modules/quests.json +2 -2
- package/registry/modules/rewards.json +2 -2
- package/registry/modules/season.json +2 -2
- package/registry/modules/store.json +2 -2
- package/registry/modules/tutorials.json +2 -2
- package/registry/modules/video-studio.json +3 -3
- package/registry/modules/voxelcraft.json +3 -3
- package/registry/modules/workshop.json +2 -2
- package/registry/skills/currency-system.json +1 -1
- package/registry/skills/idosgames-compose-modules.json +1 -1
- package/registry/skills/idosgames-getting-started.json +1 -1
- package/registry/video.json +1 -1
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"version": "0.1.4"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@idosgames/core": "0.26.
|
|
35
|
+
"@idosgames/core": "0.26.1",
|
|
36
36
|
"@idosgames/module-sdk": "0.13.3",
|
|
37
|
-
"@idosgames/react": "0.7.
|
|
37
|
+
"@idosgames/react": "0.7.1",
|
|
38
38
|
"react": "19.2.7"
|
|
39
39
|
},
|
|
40
40
|
"contentHash": "55544f5bae173482aaa22e92612cade99a356547ee80b68fc096dde8fb700b47",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"version": "0.1.3"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@idosgames/core": "0.26.
|
|
39
|
+
"@idosgames/core": "0.26.1",
|
|
40
40
|
"@idosgames/module-sdk": "0.13.3",
|
|
41
|
-
"@idosgames/react": "0.7.
|
|
41
|
+
"@idosgames/react": "0.7.1",
|
|
42
42
|
"react": "19.2.7"
|
|
43
43
|
},
|
|
44
44
|
"contentHash": "12cf31156c913e7275c83bbff9b555821f7eb8dff6241ceb569da20ed87de1eb",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"version": "0.1.3"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@idosgames/core": "0.26.
|
|
39
|
+
"@idosgames/core": "0.26.1",
|
|
40
40
|
"@idosgames/module-sdk": "0.13.3",
|
|
41
|
-
"@idosgames/react": "0.7.
|
|
41
|
+
"@idosgames/react": "0.7.1",
|
|
42
42
|
"react": "19.2.7"
|
|
43
43
|
},
|
|
44
44
|
"contentHash": "158824605f43164105e68883333c347fd5110a14b1ccb43c68036db930bbfe5f",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"version": "0.1.4"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@idosgames/core": "0.26.
|
|
33
|
+
"@idosgames/core": "0.26.1",
|
|
34
34
|
"@idosgames/module-sdk": "0.13.3",
|
|
35
|
-
"@idosgames/react": "0.7.
|
|
35
|
+
"@idosgames/react": "0.7.1",
|
|
36
36
|
"react": "19.2.7"
|
|
37
37
|
},
|
|
38
38
|
"contentHash": "4e855049c6c583010b8e9b39d552de30cd8544278690fe903fcc8d2a7e451382",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"version": "0.2.1"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@idosgames/core": "0.26.
|
|
42
|
+
"@idosgames/core": "0.26.1",
|
|
43
43
|
"@idosgames/module-sdk": "0.13.3",
|
|
44
|
-
"@idosgames/react": "0.7.
|
|
44
|
+
"@idosgames/react": "0.7.1",
|
|
45
45
|
"react": "19.2.7"
|
|
46
46
|
},
|
|
47
47
|
"contentHash": "03c011db581a59f3e98ea8696f113e02021db798e9a871be151d3e65726db2f8",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"version": "0.1.3"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@idosgames/core": "0.26.
|
|
35
|
+
"@idosgames/core": "0.26.1",
|
|
36
36
|
"@idosgames/module-sdk": "0.13.3",
|
|
37
|
-
"@idosgames/react": "0.7.
|
|
37
|
+
"@idosgames/react": "0.7.1",
|
|
38
38
|
"react": "19.2.7"
|
|
39
39
|
},
|
|
40
40
|
"contentHash": "d835f2923e281c625bd1888202c6e2df9005bd05ba54ed5793fbac2e03aab4dc",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"version": "0.1.5"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@idosgames/core": "0.26.
|
|
37
|
+
"@idosgames/core": "0.26.1",
|
|
38
38
|
"@idosgames/module-sdk": "0.13.3",
|
|
39
|
-
"@idosgames/react": "0.7.
|
|
39
|
+
"@idosgames/react": "0.7.1",
|
|
40
40
|
"react": "19.2.7"
|
|
41
41
|
},
|
|
42
42
|
"contentHash": "2448dc72d4408919336d2f4011a8dd8903a913811c8ef00db2b64c21b77f5b52",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"version": "0.1.2"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@idosgames/core": "0.26.
|
|
36
|
+
"@idosgames/core": "0.26.1",
|
|
37
37
|
"@idosgames/module-sdk": "0.13.3",
|
|
38
|
-
"@idosgames/react": "0.7.
|
|
38
|
+
"@idosgames/react": "0.7.1",
|
|
39
39
|
"react": "19.2.7"
|
|
40
40
|
},
|
|
41
41
|
"contentHash": "c374268f2fc7cfb1fb4a6073cceca7cb0666f85bc2f1c37bc4ba818233207b61",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"version": "0.1.3"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@idosgames/core": "0.26.
|
|
37
|
+
"@idosgames/core": "0.26.1",
|
|
38
38
|
"@idosgames/module-sdk": "0.13.3",
|
|
39
|
-
"@idosgames/react": "0.7.
|
|
39
|
+
"@idosgames/react": "0.7.1",
|
|
40
40
|
"react": "19.2.7"
|
|
41
41
|
},
|
|
42
42
|
"contentHash": "f30292cdadf0595a434aaae91598c279add865f03c5ae9767d5b4227e9d479ba",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"version": "0.1.3"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@idosgames/core": "0.26.
|
|
34
|
+
"@idosgames/core": "0.26.1",
|
|
35
35
|
"@idosgames/module-sdk": "0.13.3",
|
|
36
|
-
"@idosgames/react": "0.7.
|
|
36
|
+
"@idosgames/react": "0.7.1",
|
|
37
37
|
"react": "19.2.7"
|
|
38
38
|
},
|
|
39
39
|
"contentHash": "ab99622e4ba739d6e189e21c2e41c38125b2b2d140c2c63f670dd2205dca3efc",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"version": "0.1.3"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@idosgames/core": "0.26.
|
|
39
|
+
"@idosgames/core": "0.26.1",
|
|
40
40
|
"@idosgames/module-sdk": "0.13.3",
|
|
41
|
-
"@idosgames/react": "0.7.
|
|
41
|
+
"@idosgames/react": "0.7.1",
|
|
42
42
|
"react": "19.2.7"
|
|
43
43
|
},
|
|
44
44
|
"contentHash": "e7d48360d17873bd96f35c06a96a69320d2802f688a71e47392cc7dbf2c1d893",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"version": "0.1.4"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@idosgames/core": "0.26.
|
|
39
|
+
"@idosgames/core": "0.26.1",
|
|
40
40
|
"@idosgames/module-sdk": "0.13.3",
|
|
41
|
-
"@idosgames/react": "0.7.
|
|
41
|
+
"@idosgames/react": "0.7.1",
|
|
42
42
|
"react": "19.2.7"
|
|
43
43
|
},
|
|
44
44
|
"contentHash": "e702aa4d3c44024f0c964d4b3c791599a8724bece9020895df2de3286ad204ff",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"version": "0.1.4"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@idosgames/core": "0.26.
|
|
35
|
+
"@idosgames/core": "0.26.1",
|
|
36
36
|
"@idosgames/module-sdk": "0.13.3",
|
|
37
|
-
"@idosgames/react": "0.7.
|
|
37
|
+
"@idosgames/react": "0.7.1",
|
|
38
38
|
"react": "19.2.7"
|
|
39
39
|
},
|
|
40
40
|
"contentHash": "da4dbd714014f6a0f2a2d761ac737dc2e70f5c466ec05f9cc51194d3cd87f40f",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"version": "0.1.1"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@idosgames/characters": "0.1.
|
|
58
|
-
"@idosgames/core": "0.26.
|
|
57
|
+
"@idosgames/characters": "0.1.3",
|
|
58
|
+
"@idosgames/core": "0.26.1",
|
|
59
59
|
"@idosgames/module-sdk": "0.13.3",
|
|
60
|
-
"@idosgames/react": "0.7.
|
|
60
|
+
"@idosgames/react": "0.7.1",
|
|
61
61
|
"@idosgames/timeline": "0.1.0",
|
|
62
62
|
"react": "19.2.7",
|
|
63
63
|
"three": "0.185.1"
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
"version": "0.7.4"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@idosgames/characters": "0.1.
|
|
88
|
-
"@idosgames/core": "0.26.
|
|
87
|
+
"@idosgames/characters": "0.1.3",
|
|
88
|
+
"@idosgames/core": "0.26.1",
|
|
89
89
|
"@idosgames/module-sdk": "0.13.3",
|
|
90
|
-
"@idosgames/react": "0.7.
|
|
90
|
+
"@idosgames/react": "0.7.1",
|
|
91
91
|
"react": "19.2.7",
|
|
92
92
|
"three": "0.185.1"
|
|
93
93
|
},
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"version": "0.1.3"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@idosgames/core": "0.26.
|
|
42
|
+
"@idosgames/core": "0.26.1",
|
|
43
43
|
"@idosgames/module-sdk": "0.13.3",
|
|
44
|
-
"@idosgames/react": "0.7.
|
|
44
|
+
"@idosgames/react": "0.7.1",
|
|
45
45
|
"react": "19.2.7"
|
|
46
46
|
},
|
|
47
47
|
"contentHash": "c3a4b38be15260152a5244a06539639a1c7c08c207faf76d03baab43e639d148",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "currency-system",
|
|
3
3
|
"description": "Convert between currencies in a game on the iDosGames TypeScript SDK (@idosgames/core) via client.currency (CurrencyService): virtual-currency to virtual-currency (VC↔VC) conversion, and crypto-source conversion (crypto→VC or crypto→crypto). This is also the canonical home for the SDK-wide shared ResourceConsume/ResourceGrant/ResourceOperation/ResourceEntry cost-and-reward types used by every other module (Store, Character, Craft, Lootbox, Blockchain, …). Use this whenever the user is working in the iDosGames TS SDK or its game templates (board-game, idle-rpg) and wants a currency-exchange screen, gold-to-gems conversion, crypto conversion, or otherwise touches client.currency, CurrencyService, ConvertResponse, CryptoConvertResponse, ResourceConsume, ResourceGrant, ResourceOperation, or ResourceEntry — even if they don't name the module explicitly.",
|
|
4
|
-
"content": "---\nname: currency-system\ndescription: >-\n Convert between currencies in a game on the iDosGames TypeScript SDK\n (@idosgames/core) via client.currency (CurrencyService): virtual-currency to\n virtual-currency (VC↔VC) conversion, and crypto-source conversion\n (crypto→VC or crypto→crypto). This is also the canonical home for the\n SDK-wide shared ResourceConsume/ResourceGrant/ResourceOperation/ResourceEntry\n cost-and-reward types used by every other module (Store, Character, Craft,\n Lootbox, Blockchain, …). Use this whenever the user is working in the\n iDosGames TS SDK or its game templates (board-game, idle-rpg) and wants a\n currency-exchange screen, gold-to-gems conversion, crypto conversion, or\n otherwise touches client.currency, CurrencyService, ConvertResponse,\n CryptoConvertResponse, ResourceConsume, ResourceGrant, ResourceOperation, or\n ResourceEntry — even if they don't name the module explicitly.\n---\n\n# Currency system (iDosGames TS SDK)\n\nThe Currency module is small — two methods — but it's the module every other\nsystem rides on: it's the reference implementation for converting one balance\ninto another, and it's the canonical home for the **shared cost/reward\nprimitives** (`ResourceConsume`, `ResourceGrant`, `ResourceOperation`,\n`ResourceEntry`) that Store, Character, Craft, Lootbox, Blockchain, and others\nall use to describe \"this action costs X and grants Y.\" Read this skill once\nand the resource shapes in every other module's docs make sense by reference.\n\nEverything is **server-authoritative**: the client asks the backend to\nconvert, the backend validates status/rate/fee/limits and debits/credits, and\nthe SDK mirrors the confirmed result into a local cache your UI reads. You\nnever mutate balances yourself, and you never precompute the rate, fee, or\nrounding client-side — the backend owns all of it.\n\nThis skill is for **using** the production `CurrencyService`, not for porting\nor extending it. If a call is rejected, that's the backend enforcing a rule\n(disabled conversion, unlisted target, currency under maintenance, daily\nlimit, insufficient funds) — surface the error, don't try to reproduce the\ncheck client-side.\n\n## Two currency kinds\n\n- **Virtual currency (VC)** — title-defined, integer balances (gold, gems,\n energy). Config: `VirtualCurrencyDefinition`.\n- **Crypto currency** — on-chain-backed, decimal balances (ETH, USDT, …).\n Config: `CryptoCurrencyDefinition`. Crypto balances/deposits/withdrawals are\n otherwise the Blockchain module's territory — see the blockchain-system\n skill; Currency only covers converting a crypto balance you already hold.\n\nBoth currency kinds share a `CurrencyType` tag (`\"Virtual\"` | `\"Crypto\"`) used\nthroughout requests/responses to disambiguate `CurrencyID`s that could\notherwise collide.\n\n## Setup\n\n```ts\nimport { createIDosGamesClient } from \"@idosgames/core\";\n\nconst client = createIDosGamesClient({ titleID: \"your-title-id\" });\nawait client.auth.loginWithDeviceID(); // or any auth.* method\n\nconst currency = client.currency; // the CurrencyService\n```\n\nEvery currency method requires an authenticated session. Without one they\nreturn `{ ok: false, reason: \"unauthorized\" }` — they do not throw.\n\n## Methods\n\nAll methods return `Promise<OperationResult<T>>`: a discriminated union that\nis either `{ ok: true, data }` or `{ ok: false, reason, error }`. Always branch\non `result.ok` before touching `result.data`. `reason` is one of `\"client\"`\n(bad local args), `\"unauthorized\"`, `\"throttled\"` (fired the same endpoint\nagain inside the 600ms throttle window), `\"connection\"` (transient, offer\nRetry), `\"validation\"` (response/schema drift), or `\"server\"` (backend\nrejected it — `error` carries the human-readable reason, e.g. \"Conversion from\n'X' is disabled\", \"is under maintenance\", \"is deprecated and cannot receive\nnew credits\", \"Amount below pair minimum\", \"Per-pair daily limit exceeded\",\ninsufficient balance).\n\n| Method | Purpose | `data` on success |\n| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------- |\n| `convert(sourceType, sourceID, targetType, targetID, sourceAmount, transactionID?)` | VC↔VC conversion only. Integer amounts. | `ConvertResponse` |\n| `cryptoConvert(sourceType, sourceID, targetType, targetID, sourceAmount, transactionID?)` | Crypto-**source** conversion (crypto→VC or crypto→crypto). Decimal amounts. | `CryptoConvertResponse` |\n\n`sourceAmount` for `convert` is a `number` (truncated to an integer via\n`Math.trunc` before sending — VC balances are integer). For `cryptoConvert` it\nis `Decimal | string | number` (a `decimal.js` `Decimal`, a numeric string, or\na number) — pass a string or `Decimal` for anything beyond safe-integer/float\nprecision; the SDK depends on `decimal.js`. Each method validates locally\nbefore any network call and rejects mismatched pairs:\n\n- `convert` rejects if either side is `CurrencyType.Crypto` — \"Convert\n supports VC↔VC only. Use cryptoConvert for crypto.\"\n- `cryptoConvert` rejects if **both** sides are `CurrencyType.Virtual` —\n \"cryptoConvert requires at least one crypto side. Use convert for VC↔VC.\"\n- Both reject source === target, empty IDs, non-positive amounts.\n- `cryptoConvert` additionally rejects a non-integer amount when the source\n side is `Virtual` (VC amounts must be whole) — \"Virtual currency source\n amount must be integer.\"\n\nOne server-side constraint the SDK preflight does **not** catch: the backend's\n`CryptoConvert` endpoint requires the **source** to be `Crypto`, and\n`Virtual→Crypto` is not supported by any endpoint (`Convert` explicitly fails\nVirtual→Crypto with \"Virtual→Crypto conversion is not supported.\"). So the\nonly valid pairings are `convert` for VC→VC and `cryptoConvert` for crypto→VC\n/ crypto→crypto; a Virtual-source `cryptoConvert` passes the local check but\ncomes back `reason: \"server\"` (\"CryptoConvert requires source to be Crypto.\nUse Convert for VC↔VC.\").\n\n`transactionID` is optional; omit it and the SDK generates a unique one per\ncall (`convert_<sourceType>_<sourceID>_to_<targetType>_<targetID>_<uuid>` /\n`crypto_convert_...`). The backend folds `TransactionID` into its idempotency\nkey (`CurrencyConvert:<key>` / `CurrencyCryptoConvert:<key>`, where `<key>` is\n`TransactionID` verbatim when you supply one), stored per `(userID, reason)`\nfor 7 days. **Retrying with the same `transactionID` is safe** — the server\ndetects the replay and returns the stored result instead of charging again.\nTwo calls with different (e.g. auto-generated) IDs are two real conversions.\n\nOn success, both methods **mirror the confirmed debit/credit into the cache\nand emit an event** — you don't apply anything by hand. Read updated balances\nstraight from the cache.\n\n### Non-obvious server behavior worth knowing before you build UI\n\n- **Rate resolution**: `Automatic` mode divides the source's `ValueInUSD` by\n the target's `ValueInUSD` (`rate = src.ValueInUSD / tgt.ValueInUSD`);\n `Manual` mode uses the fixed `Rate` pinned on that specific\n `ConversionTarget`. Which mode applies is a property of the **source**\n currency (`Conversion.RateMode`), not the pair.\n- **Fee comes off first, then the rate, then rounding**: `fee = sourceAmount *\nFee`; `net = sourceAmount - fee`; `output = net * rate`. Order matters for\n previews.\n- **`Fee` is a SHARE, not a percent**: `0.05` means 5%. This is the unit used\n across the whole platform (`RateSpec`), and the value is passed straight into\n `RateSpec.Rate` with no rescaling — there is no division by 100 anywhere. The\n field is deliberately not called `FeePercent`; the `Percent` suffix is banned\n platform-wide because it was the source of 0..1 vs 0..100 confusion. Show\n percents in a UI by multiplying by 100. A `Fee` of `1.0` is a deliberate ban\n on converting that currency, and is not clamped away.\n- **Rounding is always truncation toward zero, never nearest/ceiling.** VC↔VC\n truncates the final `output` to a `long`. Crypto-source conversions keep\n full decimal precision throughout _except_ when the target is Virtual, where\n the decimal `output` is floored (`Math.Floor`) to a `long`. A conversion\n whose result rounds to 0 (dust) is rejected rather than silently granting\n nothing.\n- **Currency status gates asymmetrically**: `Maintenance` blocks the currency\n on either side; `Deprecated` blocks it only as a **target** (can't receive\n new credits) — a deprecated currency can still be spent down as a _source_.\n- **Two independent limit layers can reject the same call**: the per-pair\n `ConversionTarget.MinAmount` / `MaxAmount` / `DailyLimit` (scoped to this\n exact source→target pair), and the source currency's own\n `Economy.MinBalance` / `MaxBalance` / `DailyEarnLimit` / `DailySpendLimit`\n (scoped to the whole currency, across every way it can change). Either can\n fail independently — don't assume passing one means the other passed.\n\n## Reading state and reacting to changes\n\n```ts\n// Virtual currency balance (integer):\nclient.data.user.getVirtualCurrencyAmount(\"coins\"); // number\n\n// Crypto currency balance (decimal-as-string):\nclient.data.user.getCryptoCurrencyAmount(\"eth\"); // string, e.g. \"0.05\"\n\n// Currency catalog (config). Populated by client.title.getCurrencyDefinitions()\n// (emits \"title:currencyDefinitionsReceived\"), with a fallback to the `Currency`\n// section of the full title public configuration if that's been loaded:\nconst defs = client.data.config.currencyDefinitions; // CurrencyDefinitions | undefined\ndefs?.VirtualCurrencies?.[\"coins\"]?.Conversion?.Targets;\n```\n\nSubscribe with `client.on(...)`; each returns an unsubscribe fn:\n\n- `currency:converted` → `ConvertResponse`\n- `currency:cryptoConverted` → `CryptoConvertResponse`\n\nNeither `convert` nor `cryptoConvert` has its own coarse `user:currencyUpdated`\nevent — the balance change instead rides through the same resource pipeline\nevery other module uses. `convert` (VC↔VC) always applies through the integer\nresource pipeline, so it fires `user:virtualCurrencyUpdated` +\n`user:inventoryUpdated` (plus the umbrella `user:anyUpdated`) for both sides.\n`cryptoConvert` splits by side: a `Virtual` leg goes through the same\nresource pipeline (same events as above for that leg only); a `Crypto` leg\ngoes through a separate decimal patch that fires only\n`user:inventoryUpdated` + `user:anyUpdated` (no\n`user:virtualCurrencyUpdated`, since no VC balance changed). Listen at\nwhichever granularity suits your UI: the specific `currency:*` event for a\ntoast/confirmation, the coarse `user:*` event for a \"re-render balances\" hook.\n\n```ts\nconst off = client.on(\"currency:converted\", (r) => {\n console.log(\n `Spent ${r.SourceSpent} ${r.SourceID} -> got ${r.TargetCredited} ${r.TargetID}`,\n );\n});\n// later: off();\n```\n\n## Recipes\n\n### Convert gold to gems (VC↔VC)\n\n```ts\nimport { CurrencyType } from \"@idosgames/core\";\n\nconst res = await client.currency.convert(\n CurrencyType.Virtual,\n \"coins\",\n CurrencyType.Virtual,\n \"gems\",\n 100,\n);\nif (!res.ok) return showError(res.error);\n\nres.data.SourceSpent; // 100 (integer, includes the fee)\nres.data.FeeAmount; // e.g. 10 (integer, source-currency units — 10% fee here)\nres.data.TargetCredited; // e.g. 45 (integer: (100 - 10) * 0.5, truncated)\nres.data.RateApplied; // decimal-as-string, e.g. \"0.5\"\n// Balances are already updated in the cache:\nclient.data.user.getVirtualCurrencyAmount(\"coins\");\nclient.data.user.getVirtualCurrencyAmount(\"gems\");\n```\n\n### Convert a crypto balance to virtual currency\n\n```ts\nimport { CurrencyType } from \"@idosgames/core\";\nimport Decimal from \"decimal.js\";\n\nconst res = await client.currency.cryptoConvert(\n CurrencyType.Crypto,\n \"usdt\",\n CurrencyType.Virtual,\n \"gems\",\n new Decimal(\"2.50\"), // decimal source amount\n);\nif (!res.ok) return showError(res.error);\n\nres.data.SourceSpent; // \"2.50\" (decimal string)\nres.data.TargetCredited; // \"500\" (decimal string; VC side is still integer-valued and floored)\nclient.data.user.getCryptoCurrencyAmount(\"usdt\");\nclient.data.user.getVirtualCurrencyAmount(\"gems\");\n```\n\n### Read a cost/reward breakdown from another module's response\n\nCurrency doesn't return `ResourceOperation` itself (its responses are flat\n`ConvertResponse`/`CryptoConvertResponse`), but almost every other module's\nresponse embeds one under a `Resources` field — e.g. a Store purchase, a\nCharacter upgrade, a Blockchain deposit. Once you've read this skill you can\nread any of them the same way:\n\n```ts\nimport type { ResourceOperation } from \"@idosgames/core\";\n\nfunction summarize(op: ResourceOperation | null | undefined) {\n const spent = op?.Consume?.Standard?.Entries ?? [];\n const gained = op?.Grant?.Standard?.Entries ?? [];\n for (const e of spent)\n console.log(`-${e.Amount} ${e.CurrencyID ?? e.ItemID}`);\n for (const e of gained)\n console.log(`+${e.Amount} ${e.CurrencyID ?? e.ItemID}`);\n}\n```\n\n`Standard` is already the server-resolved final amount (premium\ndiscounts/bonuses folded in) — don't re-derive it from `PremiumDiscounts` /\n`PremiumTiers`. See [references/data-model.md](references/data-model.md) for\nthe full shape and every field.\n\n### Edge case: rejected pairing (client-side, no network call)\n\n```ts\nimport { CurrencyType } from \"@idosgames/core\";\n\nconst res = await client.currency.convert(\n CurrencyType.Crypto, // wrong method for a crypto side\n \"eth\",\n CurrencyType.Virtual,\n \"coins\",\n 1,\n);\n// res.ok === false, res.reason === \"client\" — rejected locally, no round-trip.\n// Use cryptoConvert instead.\n```\n\n### Edge case: not logged in\n\n```ts\nconst res = await client.currency.convert(\n CurrencyType.Virtual,\n \"coins\",\n CurrencyType.Virtual,\n \"gems\",\n 100,\n);\n// If called before auth.loginWithDeviceID() (or any auth.* login):\n// res.ok === false, res.reason === \"unauthorized\"\n```\n\n## Gotchas\n\n- **Guard against double-submit.** Each call mints a fresh idempotency key\n client-side (the auto-generated `TransactionID`), so two separate calls are\n two real operations — a double-clicked \"Convert\" can charge twice. Disable\n the control while a call is in flight. (Firing the same endpoint again\n within the throttle window, default 600 ms, is rejected with\n `reason: \"throttled\"` rather than duplicated, but don't rely on that for\n correctness.)\n- **`convert` and `cryptoConvert` are not interchangeable.** `convert` is\n VC↔VC only (integer amounts); `cryptoConvert` requires at least one crypto\n side (decimal amounts) and rejects a VC↔VC pair. Both reject mismatched\n calls with `reason: \"client\"` before any network round-trip.\n- **Amount precision matters.** VC amounts are always integers — `convert`\n truncates via `Math.trunc`. Crypto amounts are decimal; pass a `Decimal` or\n numeric string for `cryptoConvert` rather than a JS `number` once you're\n near float precision limits (the SDK's own crypto math uses `decimal.js`\n throughout).\n- **Rounding always favors the house, never the player.** Both the VC↔VC and\n the crypto→VC paths truncate/floor the credited amount down — there is no\n \"round to nearest.\" A tiny source amount can legitimately convert to 0\n target units, which the backend rejects outright rather than granting a\n free-rounding credit.\n- **`RateApplied`/`FeeAmount` are informational, not something to\n precompute.** The backend enforces the title's configured\n `CurrencyConversion` rules (`Enabled`, `RateMode`, `Fee`, whitelisted\n `Targets` with their own `MinAmount`/`MaxAmount`/`DailyLimit`) — read the\n actual applied numbers off the response rather than estimating client-side.\n- **A conversion can fail on the currency's global limits even if the pair\n looks fine.** `Economy.MinBalance`/`MaxBalance`/`DailyEarnLimit`/\n `DailySpendLimit` apply on top of (and independently of) the pair-specific\n `MinAmount`/`MaxAmount`/`DailyLimit` — show whichever `error` string comes\n back rather than trying to pre-validate both layers yourself.\n- **Render from the cache, handle the error from the result.** The happy path\n updates the cache + emits an event; the failure path gives you `reason` +\n `error`. Use `reason` to decide behavior (retry on `\"connection\"`, re-auth\n on `\"unauthorized\"`, toast the `error` on `\"server\"`).\n\n## Full reference\n\n[references/data-model.md](references/data-model.md) — the full\n`CurrencyDefinitions` config shape (virtual + crypto), the backend conversion\nformulas transcribed from source, and the complete, canonical documentation of\nthe shared `ResourceConsume` / `ResourceGrant` / `ResourceOperation` /\n`ResourceEntry` types used across the whole SDK. Read it before building\ncost/reward UI in any other module (Store offers, Character upgrades, Craft\nrecipes, Lootbox prices, Blockchain deposits/withdrawals all describe their\ncosts and payouts with these same shapes).\n",
|
|
4
|
+
"content": "---\nname: currency-system\ndescription: >-\n Convert between currencies in a game on the iDosGames TypeScript SDK\n (@idosgames/core) via client.currency (CurrencyService): virtual-currency to\n virtual-currency (VC↔VC) conversion, and crypto-source conversion\n (crypto→VC or crypto→crypto). This is also the canonical home for the\n SDK-wide shared ResourceConsume/ResourceGrant/ResourceOperation/ResourceEntry\n cost-and-reward types used by every other module (Store, Character, Craft,\n Lootbox, Blockchain, …). Use this whenever the user is working in the\n iDosGames TS SDK or its game templates (board-game, idle-rpg) and wants a\n currency-exchange screen, gold-to-gems conversion, crypto conversion, or\n otherwise touches client.currency, CurrencyService, ConvertResponse,\n CryptoConvertResponse, ResourceConsume, ResourceGrant, ResourceOperation, or\n ResourceEntry — even if they don't name the module explicitly.\n---\n\n# Currency system (iDosGames TS SDK)\n\nThe Currency module is small — two methods — but it's the module every other\nsystem rides on: it's the reference implementation for converting one balance\ninto another, and it's the canonical home for the **shared cost/reward\nprimitives** (`ResourceConsume`, `ResourceGrant`, `ResourceOperation`,\n`ResourceEntry`) that Store, Character, Craft, Lootbox, Blockchain, and others\nall use to describe \"this action costs X and grants Y.\" Read this skill once\nand the resource shapes in every other module's docs make sense by reference.\n\nEverything is **server-authoritative**: the client asks the backend to\nconvert, the backend validates status/rate/fee/limits and debits/credits, and\nthe SDK mirrors the confirmed result into a local cache your UI reads. You\nnever mutate balances yourself, and you never precompute the rate, fee, or\nrounding client-side — the backend owns all of it.\n\nThis skill is for **using** the production `CurrencyService`, not for porting\nor extending it. If a call is rejected, that's the backend enforcing a rule\n(disabled conversion, unlisted target, currency under maintenance, daily\nlimit, insufficient funds) — surface the error, don't try to reproduce the\ncheck client-side.\n\n## Two currency kinds\n\n- **Virtual currency (VC)** — title-defined, integer balances (gold, gems,\n energy). Config: `VirtualCurrencyDefinition`.\n- **Crypto currency** — on-chain-backed, decimal balances (ETH, USDT, …).\n Config: `CryptoCurrencyDefinition`. Crypto balances/deposits/withdrawals are\n otherwise the Blockchain module's territory — see the blockchain-system\n skill; Currency only covers converting a crypto balance you already hold.\n\nBoth currency kinds share a `CurrencyType` tag (`\"Virtual\"` | `\"Crypto\"`) used\nthroughout requests/responses to disambiguate `CurrencyID`s that could\notherwise collide.\n\n## Setup\n\n```ts\nimport { createIDosGamesClient } from \"@idosgames/core\";\n\nconst client = createIDosGamesClient({ titleID: \"your-title-id\" });\nawait client.auth.loginWithDeviceID(); // or any auth.* method\n\nconst currency = client.currency; // the CurrencyService\n```\n\nEvery currency method requires an authenticated session. Without one they\nreturn `{ ok: false, reason: \"unauthorized\" }` — they do not throw.\n\n## Methods\n\nAll methods return `Promise<OperationResult<T>>`: a discriminated union that\nis either `{ ok: true, data }` or `{ ok: false, reason, error }`. Always branch\non `result.ok` before touching `result.data`. `reason` is one of `\"client\"`\n(bad local args), `\"unauthorized\"`, `\"throttled\"` (fired the same endpoint\nagain inside the 600ms throttle window), `\"connection\"` (transient, offer\nRetry), `\"validation\"` (response/schema drift), or `\"server\"` (backend\nrejected it — `error` carries the human-readable reason, e.g. \"Conversion from\n'X' is disabled\", \"is under maintenance\", \"is deprecated and cannot receive\nnew credits\", \"Amount below pair minimum\", \"Per-pair daily limit exceeded\",\ninsufficient balance).\n\n| Method | Purpose | `data` on success |\n| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------- |\n| `convert(sourceType, sourceID, targetType, targetID, sourceAmount, transactionID?)` | VC↔VC conversion only. Integer amounts. | `ConvertResponse` |\n| `cryptoConvert(sourceType, sourceID, targetType, targetID, sourceAmount, transactionID?)` | Crypto-**source** conversion (crypto→VC or crypto→crypto). Decimal amounts. | `CryptoConvertResponse` |\n| `spend(spendID, { selectedOptionID?, relatedEntityID? }?)` | Pays for an action of the game (a revive, other upgrade cards) with the price of `Currency.Spends[spendID]`. Charges, grants nothing. | `SpendResponse` |\n\n`sourceAmount` for `convert` is a `number` (truncated to an integer via\n`Math.trunc` before sending — VC balances are integer). For `cryptoConvert` it\nis `Decimal | string | number` (a `decimal.js` `Decimal`, a numeric string, or\na number) — pass a string or `Decimal` for anything beyond safe-integer/float\nprecision; the SDK depends on `decimal.js`. Each method validates locally\nbefore any network call and rejects mismatched pairs:\n\n- `convert` rejects if either side is `CurrencyType.Crypto` — \"Convert\n supports VC↔VC only. Use cryptoConvert for crypto.\"\n- `cryptoConvert` rejects if **both** sides are `CurrencyType.Virtual` —\n \"cryptoConvert requires at least one crypto side. Use convert for VC↔VC.\"\n- Both reject source === target, empty IDs, non-positive amounts.\n- `cryptoConvert` additionally rejects a non-integer amount when the source\n side is `Virtual` (VC amounts must be whole) — \"Virtual currency source\n amount must be integer.\"\n\nOne server-side constraint the SDK preflight does **not** catch: the backend's\n`CryptoConvert` endpoint requires the **source** to be `Crypto`, and\n`Virtual→Crypto` is not supported by any endpoint (`Convert` explicitly fails\nVirtual→Crypto with \"Virtual→Crypto conversion is not supported.\"). So the\nonly valid pairings are `convert` for VC→VC and `cryptoConvert` for crypto→VC\n/ crypto→crypto; a Virtual-source `cryptoConvert` passes the local check but\ncomes back `reason: \"server\"` (\"CryptoConvert requires source to be Crypto.\nUse Convert for VC↔VC.\").\n\n`transactionID` is optional; omit it and the SDK generates a unique one per\ncall (`convert_<sourceType>_<sourceID>_to_<targetType>_<targetID>_<uuid>` /\n`crypto_convert_...`). The backend folds `TransactionID` into its idempotency\nkey (`CurrencyConvert:<key>` / `CurrencyCryptoConvert:<key>`, where `<key>` is\n`TransactionID` verbatim when you supply one), stored per `(userID, reason)`\nfor 7 days. **Retrying with the same `transactionID` is safe** — the server\ndetects the replay and returns the stored result instead of charging again.\nTwo calls with different (e.g. auto-generated) IDs are two real conversions.\n\nOn success, both methods **mirror the confirmed debit/credit into the cache\nand emit an event** — you don't apply anything by hand. Read updated balances\nstraight from the cache.\n\n### Non-obvious server behavior worth knowing before you build UI\n\n- **Rate resolution**: `Automatic` mode divides the source's `ValueInUSD` by\n the target's `ValueInUSD` (`rate = src.ValueInUSD / tgt.ValueInUSD`);\n `Manual` mode uses the fixed `Rate` pinned on that specific\n `ConversionTarget`. Which mode applies is a property of the **source**\n currency (`Conversion.RateMode`), not the pair.\n- **Fee comes off first, then the rate, then rounding**: `fee = sourceAmount *\nFee`; `net = sourceAmount - fee`; `output = net * rate`. Order matters for\n previews.\n- **`Fee` is a SHARE, not a percent**: `0.05` means 5%. This is the unit used\n across the whole platform (`RateSpec`), and the value is passed straight into\n `RateSpec.Rate` with no rescaling — there is no division by 100 anywhere. The\n field is deliberately not called `FeePercent`; the `Percent` suffix is banned\n platform-wide because it was the source of 0..1 vs 0..100 confusion. Show\n percents in a UI by multiplying by 100. A `Fee` of `1.0` is a deliberate ban\n on converting that currency, and is not clamped away.\n- **Rounding is always truncation toward zero, never nearest/ceiling.** VC↔VC\n truncates the final `output` to a `long`. Crypto-source conversions keep\n full decimal precision throughout _except_ when the target is Virtual, where\n the decimal `output` is floored (`Math.Floor`) to a `long`. A conversion\n whose result rounds to 0 (dust) is rejected rather than silently granting\n nothing.\n- **Currency status gates asymmetrically**: `Maintenance` blocks the currency\n on either side; `Deprecated` blocks it only as a **target** (can't receive\n new credits) — a deprecated currency can still be spent down as a _source_.\n- **Two independent limit layers can reject the same call**: the per-pair\n `ConversionTarget.MinAmount` / `MaxAmount` / `DailyLimit` (scoped to this\n exact source→target pair), and the source currency's own\n `Economy.MinBalance` / `MaxBalance` / `DailyEarnLimit` / `DailySpendLimit`\n (scoped to the whole currency, across every way it can change). Either can\n fail independently — don't assume passing one means the other passed.\n\n## Reading state and reacting to changes\n\n```ts\n// Virtual currency balance (integer):\nclient.data.user.getVirtualCurrencyAmount(\"coins\"); // number\n\n// Crypto currency balance (decimal-as-string):\nclient.data.user.getCryptoCurrencyAmount(\"eth\"); // string, e.g. \"0.05\"\n\n// Currency catalog (config). Populated by client.title.getCurrencyDefinitions()\n// (emits \"title:currencyDefinitionsReceived\"), with a fallback to the `Currency`\n// section of the full title public configuration if that's been loaded:\nconst defs = client.data.config.currencyDefinitions; // CurrencyDefinitions | undefined\ndefs?.VirtualCurrencies?.[\"coins\"]?.Conversion?.Targets;\n```\n\nSubscribe with `client.on(...)`; each returns an unsubscribe fn:\n\n- `currency:converted` → `ConvertResponse`\n- `currency:cryptoConverted` → `CryptoConvertResponse`\n- `currency:spent` → `SpendResponse`\n\nNeither `convert` nor `cryptoConvert` has its own coarse `user:currencyUpdated`\nevent — the balance change instead rides through the same resource pipeline\nevery other module uses. `convert` (VC↔VC) always applies through the integer\nresource pipeline, so it fires `user:virtualCurrencyUpdated` +\n`user:inventoryUpdated` (plus the umbrella `user:anyUpdated`) for both sides.\n`cryptoConvert` splits by side: a `Virtual` leg goes through the same\nresource pipeline (same events as above for that leg only); a `Crypto` leg\ngoes through a separate decimal patch that fires only\n`user:inventoryUpdated` + `user:anyUpdated` (no\n`user:virtualCurrencyUpdated`, since no VC balance changed). Listen at\nwhichever granularity suits your UI: the specific `currency:*` event for a\ntoast/confirmation, the coarse `user:*` event for a \"re-render balances\" hook.\n\n```ts\nconst off = client.on(\"currency:converted\", (r) => {\n console.log(\n `Spent ${r.SourceSpent} ${r.SourceID} -> got ${r.TargetCredited} ${r.TargetID}`,\n );\n});\n// later: off();\n```\n\n## Recipes\n\n### Pay for an action of the game (a revive, a reroll)\n\nThe title config lists the spends (`Currency.Spends`: id → price options). The client names the\nspend, never the amount, so a player cannot pay less or count a fake spend toward \"spend N\" quests.\nPrices are virtual currencies, items or rewarded-video credit; a store payment, Aspis (Credits) and\ncrypto are refused. Do the action only after `ok`. Show the button only when the spend exists, and\nread its price from the config rather than hard-coding it:\n\n```ts\nconst spends = client.data.config.getSection<CurrencyDefinitions>(\"Currency\")?.Spends;\nif (spends?.[\"revive\"]) {\n // the same key on a retry (a double tap, a timeout): the server answers paid without charging twice\n const res = await client.currency.spend(\"revive\", { relatedEntityID: `run-${runId}-revive` });\n if (res.ok) reviveHero();\n}\n```\n\nA store offer cannot do this: an offer must grant something.\n\n### Convert gold to gems (VC↔VC)\n\n```ts\nimport { CurrencyType } from \"@idosgames/core\";\n\nconst res = await client.currency.convert(\n CurrencyType.Virtual,\n \"coins\",\n CurrencyType.Virtual,\n \"gems\",\n 100,\n);\nif (!res.ok) return showError(res.error);\n\nres.data.SourceSpent; // 100 (integer, includes the fee)\nres.data.FeeAmount; // e.g. 10 (integer, source-currency units — 10% fee here)\nres.data.TargetCredited; // e.g. 45 (integer: (100 - 10) * 0.5, truncated)\nres.data.RateApplied; // decimal-as-string, e.g. \"0.5\"\n// Balances are already updated in the cache:\nclient.data.user.getVirtualCurrencyAmount(\"coins\");\nclient.data.user.getVirtualCurrencyAmount(\"gems\");\n```\n\n### Convert a crypto balance to virtual currency\n\n```ts\nimport { CurrencyType } from \"@idosgames/core\";\nimport Decimal from \"decimal.js\";\n\nconst res = await client.currency.cryptoConvert(\n CurrencyType.Crypto,\n \"usdt\",\n CurrencyType.Virtual,\n \"gems\",\n new Decimal(\"2.50\"), // decimal source amount\n);\nif (!res.ok) return showError(res.error);\n\nres.data.SourceSpent; // \"2.50\" (decimal string)\nres.data.TargetCredited; // \"500\" (decimal string; VC side is still integer-valued and floored)\nclient.data.user.getCryptoCurrencyAmount(\"usdt\");\nclient.data.user.getVirtualCurrencyAmount(\"gems\");\n```\n\n### Read a cost/reward breakdown from another module's response\n\nCurrency doesn't return `ResourceOperation` itself (its responses are flat\n`ConvertResponse`/`CryptoConvertResponse`), but almost every other module's\nresponse embeds one under a `Resources` field — e.g. a Store purchase, a\nCharacter upgrade, a Blockchain deposit. Once you've read this skill you can\nread any of them the same way:\n\n```ts\nimport type { ResourceOperation } from \"@idosgames/core\";\n\nfunction summarize(op: ResourceOperation | null | undefined) {\n const spent = op?.Consume?.Standard?.Entries ?? [];\n const gained = op?.Grant?.Standard?.Entries ?? [];\n for (const e of spent)\n console.log(`-${e.Amount} ${e.CurrencyID ?? e.ItemID}`);\n for (const e of gained)\n console.log(`+${e.Amount} ${e.CurrencyID ?? e.ItemID}`);\n}\n```\n\n`Standard` is already the server-resolved final amount (premium\ndiscounts/bonuses folded in) — don't re-derive it from `PremiumDiscounts` /\n`PremiumTiers`. See [references/data-model.md](references/data-model.md) for\nthe full shape and every field.\n\n### Edge case: rejected pairing (client-side, no network call)\n\n```ts\nimport { CurrencyType } from \"@idosgames/core\";\n\nconst res = await client.currency.convert(\n CurrencyType.Crypto, // wrong method for a crypto side\n \"eth\",\n CurrencyType.Virtual,\n \"coins\",\n 1,\n);\n// res.ok === false, res.reason === \"client\" — rejected locally, no round-trip.\n// Use cryptoConvert instead.\n```\n\n### Edge case: not logged in\n\n```ts\nconst res = await client.currency.convert(\n CurrencyType.Virtual,\n \"coins\",\n CurrencyType.Virtual,\n \"gems\",\n 100,\n);\n// If called before auth.loginWithDeviceID() (or any auth.* login):\n// res.ok === false, res.reason === \"unauthorized\"\n```\n\n## Gotchas\n\n- **Guard against double-submit.** Each call mints a fresh idempotency key\n client-side (the auto-generated `TransactionID`), so two separate calls are\n two real operations — a double-clicked \"Convert\" can charge twice. Disable\n the control while a call is in flight. (Firing the same endpoint again\n within the throttle window, default 600 ms, is rejected with\n `reason: \"throttled\"` rather than duplicated, but don't rely on that for\n correctness.)\n- **`convert` and `cryptoConvert` are not interchangeable.** `convert` is\n VC↔VC only (integer amounts); `cryptoConvert` requires at least one crypto\n side (decimal amounts) and rejects a VC↔VC pair. Both reject mismatched\n calls with `reason: \"client\"` before any network round-trip.\n- **Amount precision matters.** VC amounts are always integers — `convert`\n truncates via `Math.trunc`. Crypto amounts are decimal; pass a `Decimal` or\n numeric string for `cryptoConvert` rather than a JS `number` once you're\n near float precision limits (the SDK's own crypto math uses `decimal.js`\n throughout).\n- **Rounding always favors the house, never the player.** Both the VC↔VC and\n the crypto→VC paths truncate/floor the credited amount down — there is no\n \"round to nearest.\" A tiny source amount can legitimately convert to 0\n target units, which the backend rejects outright rather than granting a\n free-rounding credit.\n- **`RateApplied`/`FeeAmount` are informational, not something to\n precompute.** The backend enforces the title's configured\n `CurrencyConversion` rules (`Enabled`, `RateMode`, `Fee`, whitelisted\n `Targets` with their own `MinAmount`/`MaxAmount`/`DailyLimit`) — read the\n actual applied numbers off the response rather than estimating client-side.\n- **A conversion can fail on the currency's global limits even if the pair\n looks fine.** `Economy.MinBalance`/`MaxBalance`/`DailyEarnLimit`/\n `DailySpendLimit` apply on top of (and independently of) the pair-specific\n `MinAmount`/`MaxAmount`/`DailyLimit` — show whichever `error` string comes\n back rather than trying to pre-validate both layers yourself.\n- **Render from the cache, handle the error from the result.** The happy path\n updates the cache + emits an event; the failure path gives you `reason` +\n `error`. Use `reason` to decide behavior (retry on `\"connection\"`, re-auth\n on `\"unauthorized\"`, toast the `error` on `\"server\"`).\n\n## Full reference\n\n[references/data-model.md](references/data-model.md) — the full\n`CurrencyDefinitions` config shape (virtual + crypto), the backend conversion\nformulas transcribed from source, and the complete, canonical documentation of\nthe shared `ResourceConsume` / `ResourceGrant` / `ResourceOperation` /\n`ResourceEntry` types used across the whole SDK. Read it before building\ncost/reward UI in any other module (Store offers, Character upgrades, Craft\nrecipes, Lootbox prices, Blockchain deposits/withdrawals all describe their\ncosts and payouts with these same shapes).\n",
|
|
5
5
|
"references": [
|
|
6
6
|
{
|
|
7
7
|
"path": "data-model.md",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idosgames-compose-modules",
|
|
3
3
|
"description": "Put an iDosGames app together from the base and modules — the systems (shop, heroes, leaderboards, quests, chests, multiplayer…) as lobby tabs, one or several games behind \"Play\", progress shared across them. Use this whenever a developer wants to COMBINE or MERGE modules into a single title, add or remove a system, add a second game, switch between the lobby and game modes, share currency/inventory across modules, or asks how the lobby, \"Play\", ctx.modes, activeOnly panels, the shared-UI roles (sharedUi, shouldDraw, the \"modes\" role), typed cross-module events (defineTopic/shape) or host-level shared state work. Builds on idosgames-getting-started (scaffolding) and idosgames-module-contract (a single module).",
|
|
4
|
-
"content": "---\nname: idosgames-compose-modules\ndescription: >-\n Put an iDosGames app together from the base and modules — the systems (shop, heroes, leaderboards,\n quests, chests, multiplayer…) as lobby tabs, one or several games behind \"Play\", progress shared across them. Use this\n whenever a developer wants to COMBINE or MERGE modules into a single title, add or remove a system,\n add a second game, switch between the lobby and game modes, share currency/inventory across modules,\n or asks how the lobby, \"Play\", ctx.modes, activeOnly panels, the shared-UI roles (sharedUi,\n shouldDraw, the \"modes\" role), typed cross-module events (defineTopic/shape) or host-level shared\n state work. Builds on idosgames-getting-started (scaffolding) and idosgames-module-contract (a single\n module).\n---\n\n# Putting an app together: the base + modules\n\nEvery project is **the base + modules**. The base is sign-in and the lobby and comes from the SDK\n(`@idosgames/app-shell`: `createBaseModule`, `DefaultLoginScreen`, `DefaultPlayAccessScreen`); the project\nconfigures it in `src/app.config.ts` (where the player lands, the tab order, guest sign-in), `src/ui.config.ts`\n(the look) and `src/main.tsx`. Everything else is a module in `src/modules/<id>/`:\n\n- **systems** (`type: feature`) — one per player-facing module of the engine: store, premium (VIP),\n character, inventory, lootboxes, boosts, leaderboard, rewards (daily), quests, collections, craft,\n marketplace, deals, events, coop-events, season, friends, chat, invite, pvp, multiplayer, profile,\n tutorials, workshop, ai, mailbox. Each registers its screen as a **feature** → a tab of the lobby.\n The base installs only six of them into every new project (`inBase: true`: profile, inventory,\n invite, friends, quests, mailbox); every other system is installed only when the app needs it — a game\n brings the ones it lists in `uses`, and you install any other one as soon as the task needs what\n it `provides` (InstallModule / `get_module`) — the user does not have to name the module; reuse it\n instead of writing that feature again. Never install one the task does not need.\n- **games** (`type: template`) — board-game, idle-rpg, voxelcraft, mini-game, or the game's own. Each\n registers a **route** → a \"Play\" button in the lobby. A game that plugs into a system lists it in\n `uses` in its module.meta.json (`ctx.multiplayer` → `\"multiplayer\"`, `ctx.content` → `\"workshop\"`;\n `npm run check` enforces those two) — install those together with the game.\n\n## Register modules\n\n```ts\n// src/modules.ts — the platform regenerates it; by hand it is imports + the array\nexport const modules: Module[] = [\n storeModule, // systems: lobby tabs\n characterModule,\n leaderboardModule,\n boardGameModule, // games: \"Play\"\n idleRpgModule,\n];\n```\n\n`src/main.tsx` mounts `[createBaseModule(appConfig, …), ...modules]`. After sign-in the player lands in the game that\nlives inside the lobby, or in the lobby when the games take the whole screen (`src/app.config.ts`\n`start`: `\"game\"` — the default, `\"lobby\"`, or a game's mode id). \"Play\" switches the host to the\ngame's mode: only the active mode's scene is mounted and ticking; the rest are suspended (their RAF\nstops). This is why two different engines (Three + Phaser) live in one project — they never render\nat the same time. Two kinds of game (owner's decision 03.10.2026 — chosen per game, never for all):\n\n- `ctx.registerRoute({ …, inLobby: true })` — the game sits INSIDE the lobby (idle-rpg): the base\n keeps the balances on top and the tabs at the bottom (host panel slots `header`/`footer` — the\n game is laid out between them, it does nothing for it); \"Play\" is the game, any other tab opens\n that screen, the app starts in it. A game with its own bar to the systems hides it when\n `ctx.sharedUi.shouldDraw(\"menu\")` is false (idle-rpg does).\n- without it (the default) — the game takes the whole screen (voxelcraft, board-game, mini-game):\n \"Play\" in the lobby launches it, over it the base draws only a small \"To the lobby\" button.\n A game with its own way back (a pause menu with \"To the lobby\" → `ctx.lobby.open()`, and a pause\n button on touch screens) says `ownExit: true` in its route — then the base draws nothing over it\n (voxelcraft).\n\n- A system tab is hidden while the title has nothing for it (no store offers, no heroes…): the module\n registers with `available: false` and calls `ctx.features.setAvailable(id, ok, reason)`.\n- `ctx.modes.list()` / `current()` / `subscribe()` — every mode (id, label, icon, module, module type);\n the lobby builds \"Play\" from the `game` ones. Switch with `ctx.navigate(id)` (`\"lobby\"` is the base).\n- Open another system's screen by id: `ctx.features.open(\"store\")` — from a game too (the host shows\n it in a window while the lobby is off screen). Never import another module.\n\n## Share progress across modes (the merge)\n\nShared state lives in the ONE SDK client, not in any module. Every module reads/writes the same\n`client` (currency, inventory, characters), so progress carries across modes automatically:\n\n- Gold earned idle in the RPG mode is spendable in the Board mode and in the shop — same\n `client.currency`, same cache. No cross-module plumbing needed for durable state.\n- For live cross-module signals (not durable state), use `ctx.events` with a **typed topic** — see\n \"Signals between modules\" below.\n- Modules never import each other's files. The client and the event bus are the seams\n between them — which is what keeps a module reusable in another game. Code several of THIS game's\n modules need (shared types, the game's UI kit, helpers) goes to `src/shared/`, which never imports a\n module (see idosgames-project-structure).\n\n## Shared chrome: roles, and the base takes them\n\nBalances, the crypto wallet, the status line, \"Log out\" / player ID, the menu of features and the\nswitch between modes are **shared-UI roles** (`currency-bar | wallet | status | account | menu |\nmodes`). A module that draws one for EVERY mode declares it statically on the module object —\nthe base does:\n\n```ts\ndefineModule({\n id: \"base\",\n meta: { name: \"Base\", type: \"app\", engine: \"dom\" },\n sharedUi: {\n provides: [\"currency-bar\", \"wallet\", \"account\", \"menu\", \"modes\"],\n },\n setup(ctx) {\n /* the lobby: balances, wallet, profile, tabs, \"Play\" */\n },\n});\n```\n\nThe host resolves role owners from these declarations BEFORE any `setup()` — the first provider wins,\na conflict is warned — and every module asks `ctx.sharedUi.shouldDraw(role)`. A game draws its own\ncopy only while nobody took the role. So:\n\n- Next to the base a game hides its own balances and wallet by itself. Do NOT edit a catalog game to\n remove them: it does that itself, and an edit marks it customized (no more catalog updates).\n- `status` — the line of a game's own actions (\"Rolling…\") — stays with each game.\n- A game installed without a base stays complete — nobody owns a role, it draws everything, and the\n host draws its own nav of routes, ☰ and \"Log out\" (those are the host-drawn roles `modes`, `menu`,\n `account`).\n- A new module that needs, say, the balance bar but does not draw it declares\n `sharedUi: { requires: [\"currency-bar\"] }`; with no provider installed the host warns.\n\n**Layout is the host's job.** A game's overlay fills the screen under the base's small button; a\nscene's own DOM HUD can keep clear of host chrome with the CSS variables on the host root —\n`top: calc(var(--idos-safe-top, 0px) + 8px)`, `bottom: calc(var(--idos-safe-bottom, 0px) + 8px)`\n(voxelcraft's hotbar does this). Both are 0 when nothing is pinned. `hud`-slot panels are wrapped in\n`display: contents`, so a panel can stretch (`flex: 1`) and decide its own pointer-events: let clicks\nthrough to the game, take them only on controls.\n\n## Signals between modules (typed topics)\n\nA topic is a token passed by value: the name carries the major version, the payload is a flat JSON\nshape — one artifact that gives the TS type, the runtime check and the catalog entry.\n\n```ts\n// idle-rpg/events.ts — the EMITTER owns the topic\nimport { defineTopic, shape } from \"@idosgames/module-sdk\";\nexport const characterUpgraded = defineTopic(\n \"idle-rpg:character-upgraded@1\",\n shape({ characterId: \"string\", level: \"number\" }),\n);\n// setup(): hand the panel a callback that does\n// ctx.events.emit(characterUpgraded, { characterId, level });\n```\n\n```ts\n// board-game/events.ts — the LISTENER keeps its OWN copy (copied from the catalog), never an import.\n// Optional: idle-rpg may not be installed. Only the fields this module reads.\nexport const idleCharacterUpgraded = defineTopic(\n \"idle-rpg:character-upgraded@1\",\n shape({ level: \"number\" }),\n);\n// setup() — NOT a panel effect (activeOnly panels unmount with their mode and miss events):\nctx.events.on(idleCharacterUpgraded, ({ level }) =>\n news.push(`Idle RPG hero reached Lv ${level}`),\n);\n```\n\nDeclare both sides in `module.meta.json` — the catalog and the agent read this, not your code:\n\n```json\n\"events\": {\n \"emits\": [{ \"topic\": \"idle-rpg:character-upgraded@1\", \"when\": \"a hero levelled up\",\n \"payload\": { \"characterId\": \"string\", \"level\": \"number\" } }],\n \"listens\": [{ \"topic\": \"idle-rpg:character-upgraded@1\", \"why\": \"news chip in the board HUD\" }]\n}\n```\n\nRules:\n\n- Name `<your-module-id>:<event>@<major>`. Emit only into your own namespace — the host drops an\n emit into another module's namespace with `console.error`; `host:` is reserved. The only topics\n you import are `hostTopics` from `@idosgames/module-sdk` (`hostTopics.modeChanged` =\n `host:mode-changed@1 { from?: string; to: string }`, sent on every mode switch).\n- Shape leaves: `\"string\" | \"number\" | \"boolean\"`, with `\"[]\"` and/or a trailing `\"?\"`; nested\n objects allowed. Payloads are plain JSON — no functions, class instances or engine objects.\n- An incompatible change is a NEW topic `@2` (send both during the transition); within a major, only\n add fields. A listener whose fields are missing is skipped (one `console.warn`), never crashed.\n- An event is a signal, not state, and is never replayed — \"what exists now\" is SDK data or\n `ctx.sharedUi`. No request/response between modules: if you need an answer, it is data.\n- A throwing handler does not stop other listeners or reach the emitter; the host removes every\n subscription on logout, so re-login does not double handlers.\n- In the AI editor's preview, `gameState` returns the last 50 events, who listens to what, and hints\n like \"board-game listens to x@1 but only x@2 is emitted\".\n- A game's OWN modules may keep shared topic tokens in `src/shared/events/` and import them on both\n sides — still declare them in each `module.meta.json`.\n\n## Reference app\n\nThe base + the systems + `board-game` + `idle-rpg`:\n\n- sign-in → the lobby: balances, wallet and profile on top, the systems as tabs, \"Play\" with two games;\n- \"Play\" → one engine scene mounted at a time; the lobby's tabs stay around an `inLobby` game, a\n full-screen game gets the button back;\n- the games hide their own balances and wallet (the lobby has them); their status lines stay theirs;\n- currency changed in one mode is immediately visible in the others and in the lobby; levelling a hero\n in Idle RPG shows a news chip on the board (`idle-rpg:character-upgraded@1`).\n\nTo pull the modules, use `get_module {id}` for each (MCP) and register them as above. Adjust layouts\nper module (a full-bleed overlay UI vs a docked side panel) — see each module's RootPanel.\n",
|
|
4
|
+
"content": "---\nname: idosgames-compose-modules\ndescription: >-\n Put an iDosGames app together from the base and modules — the systems (shop, heroes, leaderboards,\n quests, chests, multiplayer…) as lobby tabs, one or several games behind \"Play\", progress shared across them. Use this\n whenever a developer wants to COMBINE or MERGE modules into a single title, add or remove a system,\n add a second game, switch between the lobby and game modes, share currency/inventory across modules,\n or asks how the lobby, \"Play\", ctx.modes, activeOnly panels, the shared-UI roles (sharedUi,\n shouldDraw, the \"modes\" role), typed cross-module events (defineTopic/shape) or host-level shared\n state work. Builds on idosgames-getting-started (scaffolding) and idosgames-module-contract (a single\n module).\n---\n\n# Putting an app together: the base + modules\n\nEvery project is **the base + modules**. The base is sign-in and the lobby and comes from the SDK\n(`@idosgames/app-shell`: `createBaseModule`, `DefaultLoginScreen`, `DefaultPlayAccessScreen`); the project\nconfigures it in `src/app.config.ts` (where the player lands, the tab order, guest sign-in), `src/ui.config.ts`\n(the look) and `src/main.tsx`. Everything else is a module in `src/modules/<id>/`:\n\n- **systems** (`type: feature`) — one per player-facing module of the engine: store, premium (VIP),\n character, inventory, lootboxes, boosts, leaderboard, rewards (daily), quests, collections, craft,\n marketplace, deals, events, coop-events, season, friends, chat, invite, pvp, multiplayer, profile,\n tutorials, workshop, ai, mailbox. Each registers its screen as a **feature** → a tab of the lobby.\n The base installs only six of them into every new project (`inBase: true`: profile, inventory,\n invite, friends, quests, mailbox); every other system is installed only when the app needs it — a game\n brings the ones it lists in `uses`, and you install any other one as soon as the task needs what\n it `provides` (InstallModule / `get_module`) — the user does not have to name the module; reuse it\n instead of writing that feature again. Never install one the task does not need.\n- **games** (`type: template`) — board-game, idle-rpg, voxelcraft, mini-game, or the game's own. Each\n registers a **route** → a \"Play\" button in the lobby. A game that plugs into a system lists it in\n `uses` in its module.meta.json (`ctx.multiplayer` → `\"multiplayer\"`, `ctx.content` → `\"workshop\"`;\n `npm run check` enforces those two) — install those together with the game.\n\n## Register modules\n\n```ts\n// src/modules.ts — the platform regenerates it; by hand it is imports + the array\nexport const modules: Module[] = [\n storeModule, // systems: lobby tabs\n characterModule,\n leaderboardModule,\n boardGameModule, // games: \"Play\"\n idleRpgModule,\n];\n```\n\n`src/main.tsx` mounts `[createBaseModule(appConfig, …), ...modules]`. After sign-in the player lands in the game that\nlives inside the lobby, or in the lobby when the games take the whole screen (`src/app.config.ts`\n`start`: `\"game\"` — the default, `\"lobby\"`, or a game's mode id). \"Play\" switches the host to the\ngame's mode: only the active mode's scene is mounted and ticking; the rest are suspended (their RAF\nstops). This is why two different engines (Three + Phaser) live in one project — they never render\nat the same time. Two kinds of game (owner's decision 03.10.2026 — chosen per game, never for all):\n\n- `ctx.registerRoute({ …, inLobby: true })` — the game sits INSIDE the lobby (idle-rpg): the base\n keeps the balances on top and the tabs at the bottom (host panel slots `header`/`footer` — the\n game is laid out between them, it does nothing for it); \"Play\" is the game, any other tab opens\n that screen, the app starts in it. A game with its own bar to the systems hides it when\n `ctx.sharedUi.shouldDraw(\"menu\")` is false (idle-rpg does).\n- without it (the default) — the game takes the whole screen (voxelcraft, board-game, mini-game):\n \"Play\" in the lobby launches it, over it the base draws only a small \"To the lobby\" button.\n A game with its own way back (a pause menu with \"To the lobby\" → `ctx.lobby.open()`, and a pause\n button on touch screens) says `ownExit: true` in its route — then the base draws nothing over it\n (voxelcraft).\n\n- A system tab is hidden while the title has nothing for it (no store offers, no heroes…): the module\n registers with `available: false` and calls `ctx.features.setAvailable(id, ok, reason)`.\n- `ctx.modes.list()` / `current()` / `subscribe()` — every mode (id, label, icon, module, module type);\n the lobby builds \"Play\" from the `game` ones. Switch with `ctx.navigate(id)` (`\"lobby\"` is the base).\n- Open another system's screen by id: `ctx.features.open(\"store\")` — from a game too (the host shows\n it in a window while the lobby is off screen). Never import another module.\n\n## Share progress across modes (the merge)\n\nShared state lives in the ONE SDK client, not in any module. Every module reads/writes the same\n`client` (currency, inventory, characters), so progress carries across modes automatically:\n\n- Gold earned idle in the RPG mode is spendable in the Board mode and in the shop — same\n `client.currency`, same cache. No cross-module plumbing needed for durable state.\n- For live cross-module signals (not durable state), use `ctx.events` with a **typed topic** — see\n \"Signals between modules\" below.\n- Modules never import each other's files. The client and the event bus are the seams\n between them — which is what keeps a module reusable in another game. Code several of THIS game's\n modules need (shared types, the game's UI kit, helpers) goes to `src/shared/`, which never imports a\n module (see idosgames-project-structure).\n\n## Shared chrome: roles, and the base takes them\n\nBalances, the crypto wallet, the status line, \"Log out\" / player ID, the menu of features and the\nswitch between modes are **shared-UI roles** (`currency-bar | wallet | status | account | menu |\nmodes`). A module that draws one for EVERY mode declares it statically on the module object —\nthe base does:\n\n```ts\ndefineModule({\n id: \"base\",\n meta: { name: \"Base\", type: \"app\", engine: \"dom\" },\n sharedUi: {\n provides: [\"currency-bar\", \"wallet\", \"account\", \"menu\", \"modes\"],\n },\n setup(ctx) {\n /* the lobby: balances, wallet, profile, tabs, \"Play\" */\n },\n});\n```\n\nThe host resolves role owners from these declarations BEFORE any `setup()` — the first provider wins,\na conflict is warned — and every module asks `ctx.sharedUi.shouldDraw(role)`. A game draws its own\ncopy only while nobody took the role. So:\n\n- Next to the base a game hides its own balances and wallet by itself. Do NOT edit a catalog game to\n remove them: it does that itself, and an edit marks it customized (no more catalog updates).\n- `status` — the line of a game's own actions (\"Rolling…\") — stays with each game.\n- A game installed without a base stays complete — nobody owns a role, it draws everything, and the\n host draws its own nav of routes, ☰ and \"Log out\" (those are the host-drawn roles `modes`, `menu`,\n `account`).\n- A new module that needs, say, the balance bar but does not draw it declares\n `sharedUi: { requires: [\"currency-bar\"] }`; with no provider installed the host warns.\n\n**Layout is the host's job.** A game's overlay fills the screen under the base's small button; a\nscene's own DOM HUD can keep clear of host chrome with the CSS variables on the host root —\n`top: calc(var(--idos-safe-top, 0px) + 8px)`, `bottom: calc(var(--idos-safe-bottom, 0px) + 8px)`\n(voxelcraft's hotbar does this). Both are 0 when nothing is pinned. `hud`-slot panels are wrapped in\n`display: contents`, so a panel can stretch (`flex: 1`) and decide its own pointer-events: let clicks\nthrough to the game, take them only on controls.\n\n## Signals between modules (typed topics)\n\nA topic is a token passed by value: the name carries the major version, the payload is a flat JSON\nshape — one artifact that gives the TS type, the runtime check and the catalog entry.\n\n```ts\n// idle-rpg/events.ts — the EMITTER owns the topic\nimport { defineTopic, shape } from \"@idosgames/module-sdk\";\nexport const characterUpgraded = defineTopic(\n \"idle-rpg:character-upgraded@1\",\n shape({ characterId: \"string\", level: \"number\" }),\n);\n// setup(): hand the panel a callback that does\n// ctx.events.emit(characterUpgraded, { characterId, level });\n```\n\n```ts\n// board-game/events.ts — the LISTENER keeps its OWN copy (copied from the catalog), never an import.\n// Optional: idle-rpg may not be installed. Only the fields this module reads.\nexport const idleCharacterUpgraded = defineTopic(\n \"idle-rpg:character-upgraded@1\",\n shape({ level: \"number\" }),\n);\n// setup() — NOT a panel effect (activeOnly panels unmount with their mode and miss events):\nctx.events.on(idleCharacterUpgraded, ({ level }) =>\n news.push(`Idle RPG hero reached Lv ${level}`),\n);\n```\n\nDeclare both sides in `module.meta.json` — the catalog and the agent read this, not your code:\n\n```json\n\"events\": {\n \"emits\": [{ \"topic\": \"idle-rpg:character-upgraded@1\", \"when\": \"a hero levelled up\",\n \"payload\": { \"characterId\": \"string\", \"level\": \"number\" } }],\n \"listens\": [{ \"topic\": \"idle-rpg:character-upgraded@1\", \"why\": \"news chip in the board HUD\" }]\n}\n```\n\nRules:\n\n- Name `<your-module-id>:<event>@<major>`. Emit only into your own namespace — the host drops an\n emit into another module's namespace with `console.error`; `host:` is reserved. The only topics\n you import are `hostTopics` from `@idosgames/module-sdk` (`hostTopics.modeChanged` =\n `host:mode-changed@1 { from?: string; to: string }`, sent on every mode switch).\n- Shape leaves: `\"string\" | \"number\" | \"boolean\"`, with `\"[]\"` and/or a trailing `\"?\"`; nested\n objects allowed. Payloads are plain JSON — no functions, class instances or engine objects.\n- An incompatible change is a NEW topic `@2` (send both during the transition); within a major, only\n add fields. A listener whose fields are missing is skipped (one `console.warn`), never crashed.\n- An event is a signal, not state, and is never replayed — \"what exists now\" is SDK data or\n `ctx.sharedUi`. No request/response between modules: if you need an answer, it is data.\n- A throwing handler does not stop other listeners or reach the emitter; the host removes every\n subscription on logout, so re-login does not double handlers.\n- In the AI editor's preview, `gameState` returns the last 50 events, who listens to what, and hints\n like \"board-game listens to x@1 but only x@2 is emitted\".\n- A game's OWN modules may keep shared topic tokens in `src/shared/events/` and import them on both\n sides — still declare them in each `module.meta.json`.\n\n## Reference app\n\nThe base + the systems + `board-game` + `idle-rpg`:\n\n- sign-in → straight into the `inLobby` game (else the first game): balances, wallet and profile on\n top, the systems as tabs; \"Play\" lists the two games;\n- \"Play\" → one engine scene mounted at a time; the lobby's tabs stay around an `inLobby` game, a\n full-screen game gets the button back;\n- the games hide their own balances and wallet (the lobby has them); their status lines stay theirs;\n- currency changed in one mode is immediately visible in the others and in the lobby; levelling a hero\n in Idle RPG shows a news chip on the board (`idle-rpg:character-upgraded@1`).\n\nTo pull the modules, use `get_module {id}` for each (MCP) and register them as above. Adjust layouts\nper module (a full-bleed overlay UI vs a docked side panel) — see each module's RootPanel.\n",
|
|
5
5
|
"references": []
|
|
6
6
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idosgames-getting-started",
|
|
3
3
|
"description": "Start a new game or app on the iDosGames composable-module architecture: scaffold the base (sign-in + the lobby on the host shell) and plug in modules — the systems (shop, heroes, leaderboards…) and the games (board-game, idle-rpg, voxelcraft, mini-game, …). Use this whenever a developer wants to CREATE an iDosGames project from scratch, add an iDosGames game module to a project, or asks how @idosgames/app-shell, @idosgames/module-sdk, the host shell, mountHost, or src/modules.ts fit together. Pairs with idosgames-module-contract (writing a module) and idosgames-compose-modules (merging several) and idosgames-project-structure (where code goes as the project grows). If pulling modules over MCP, use the @idosgames/mcp tools get_host_scaffold / get_module / get_manifest.",
|
|
4
|
-
"content": "---\nname: idosgames-getting-started\ndescription: >-\n Start a new game or app on the iDosGames composable-module architecture: scaffold the base (sign-in\n + the lobby on the host shell) and plug in modules — the systems (shop, heroes, leaderboards…) and\n the games (board-game, idle-rpg, voxelcraft, mini-game, …). Use this whenever\n a developer wants to CREATE an iDosGames project from scratch, add an iDosGames game module to a\n project, or asks how @idosgames/app-shell, @idosgames/module-sdk, the host shell, mountHost, or\n src/modules.ts fit together. Pairs with idosgames-module-contract (writing a module) and\n idosgames-compose-modules (merging several) and idosgames-project-structure (where code goes as\n the project grows). If pulling modules over MCP, use the @idosgames/mcp\n tools get_host_scaffold / get_module / get_manifest.\n---\n\n# Getting started (iDosGames composable modules)\n\nAn iDosGames project is **one host shell + N feature modules**. The host owns everything that exists\nonce — the SDK client, login, the React root, the screen, a module registry, and a Mode Router that\nswitches between modules. A module is a library (a game or app) that plugs into the host; it never\ncreates the client, logs in, or owns the root.\n\n## Runtime packages (npm)\n\n- `@idosgames/core` — the SDK client (`createIDosGamesClient`): auth, currency, store, characters,\n blockchain, ~28 services. See the per-service skills (currency-system, store-system, …). Its\n **central cache** `client.cache` decides every read: the login brings the title config (by version)\n and the player's state (by each container's `Rev`) in ONE call; afterwards read state through\n `client.cache.ensureState([...containers])`, definitions through `getDefinitions()` (served from the\n config cache), server views through their service (cached by policy) — never re-fetch on start-up.\n The server views the game reads ride on that same login call (`ClientState.Views`): the client\n option `loginViews` — `\"auto\"` (default, what the game read last time), `\"all\"`, `\"none\"` or a list.\n- `@idosgames/module-sdk` — the module contract types (`Module`, `ModuleContext`, `EngineScene`,\n `UiPanel`). See idosgames-module-contract.\n- `@idosgames/react` — shared React glue (`IDosGamesProvider`, `useIDosGamesClient`, `useUserState`,\n `StatusProvider`, `createControllerContext`).\n- `@idosgames/app-shell` — the host runtime (`mountHost`, the Mode Router, the module registry).\n- `@idosgames/wallet` — optional wallet bridge (EVM/Solana) for on-chain deposits/withdrawals.\n\nInstall the exact versions from `get_manifest` (MCP) or the registry `index.json` `runtimePackages`.\n\n## Project shape\n\n```\nIDOS.md # the project guide every agent reads first (short, evergreen)\nAGENTS.md, CLAUDE.md # pointers to IDOS.md for external tools\ndocs/feature-history/ # one file per game system + README.md index (the project's memory)\nindex.html # mounts #app\nsrc/main.tsx # creates ONE client and calls mountHost({container, client, modules})\nsrc/modules.ts # the registry: export const modules: Module[] = [ … ] — imports + array only\nsrc/modules/{id}/ # each module's source + its module.meta.json (from get_module)\n```\n\nPlatform-created projects also carry `idos.modules.lock.json` (which modules came from the catalog,\nwith file fingerprints) — it is platform-owned; don't edit it. Where new code goes as the game grows,\nand how the project documents itself, is **idosgames-project-structure**.\n\n`src/main.tsx` (host-owned) is the only place that creates the client — but it does **not** sign in.\n`mountHost` owns sign-in: it replays a previous session (`autoLogin`) and renders the login screen\nwhen there is nothing to replay. Calling a `login*` method here skips that screen for good, taking\n\"switch account\" and wallet sign-in with it:\n\n```ts\nconst client = createIDosGamesClient({ titleID, buildKey, throttleMs: 0 });\n// Do NOT log in here — the host does it.\nmountHost({\n container: app,\n client,\n modules, // from ./modules\n renderLogin, // optional: your own screen; omitted = a plain guest-only default\n});\n```\n\nWhether a returning player is signed back in silently is the login screen's business, not the\nhost's: it calls `client.auth.setRememberSession(remember)` before a `login*` method. See the\nauthentication skill.\n\n`src/modules.ts` registers what the project composes:\n\n```ts\nimport type { Module } from \"@idosgames/module-sdk\";\nimport { boardGameModule } from \"./modules/board-game\";\nexport const modules: Module[] = [boardGameModule];\n```\n\n## Steps to scaffold\n\n1. **Base** — write the host scaffold to the project root (`get_host_scaffold`, or copy\n `templates/base`): sign-in and the lobby come from `@idosgames/app-shell` (mounted in `src/main.tsx`), `src/app.config.ts`\n (start in the lobby or a game, guest sign-in), an empty `src/modules.ts` → an empty lobby.\n2. **Bind the Title** — open `src/idos.title.ts` and set `IDOS_TITLE_ID` to the game's canonical\n Title id (and `IDOS_BUILD_KEY` if the title enforces one). This file is the project's single\n centralized identity — it is the highest-priority source and the only channel a packaged mobile\n build, iframe embed, or shared link has. On platform-created projects the platform generates it;\n on a manually scaffolded project **you fill it yourself**. Do NOT bind the title via `.env.local`\n (`VITE_IDOS_TITLE_ID`) — that is a local-dev fallback for the raw template only, and it does not\n travel with the code.\n3. **Pick modules** — `list_modules` / `search`, then `get_module {id}` for each. Write its files\n into `src/modules/{id}/`. The base's systems are the modules with `inBase: true` (profile,\n inventory, invite, friends, quests, mailbox); add the game(s) with the modules each game `uses`, and any\n other system (shop, VIP, leaderboards…) only when the app needs it.\n4. **Register** — for each module add `import { {camelCase(id)}Module } from \"./modules/{id}\"` and\n push it into the `modules` array (e.g. `board-game` → `boardGameModule`). Then add the module to\n the \"Installed modules\" block of `IDOS.md` (one line: ``- `board-game` (0.1.0) — src/modules/board-game/``)\n — on platform projects the platform maintains that block itself.\n5. **Install deps** — the union of `runtimePackages` + each module's `dependencies`. Modules bring\n their own engine (three, phaser); the host brings react/react-dom/app-shell.\n6. **Run** — sign-in → the lobby: the systems' screens are its tabs (a tab hides itself while the\n title has nothing for it) and \"Play\" launches a game on the whole screen with a small button back\n — or, for a game whose route says `inLobby: true` (idle-rpg), straight into that game inside the\n lobby, the lobby's balances and tabs around it. The lobby draws the balances and the wallet; games hide their own copies by\n themselves (`sharedUi` roles).\n\nTo combine multiple genres into one game, see **idosgames-compose-modules**. To write or edit a\nmodule, see **idosgames-module-contract**.\n\n## Where state lives (decide this before writing the first save)\n\nThe project is client-side code in the player's browser. `localStorage`, module fields, and React\nstate are **not storage** — nothing there survives a device change, and nothing there is trusted.\n\n1. **A dedicated module owns it?** Use that module. Currencies, inventory, quests, characters,\n leaderboards, store purchases each have a service that enforces the rules server-side.\n2. **Otherwise, per-player data → `client.userCustomData`** — buckets `Private`/`Public` are\n client-writable (settings, cosmetics), `ReadOnly`/`Internal` are server-only. Anything a player\n could cheat by editing goes in the server-only buckets. See **user-custom-data**.\n3. **Shared by all players → `client.titleCustomData`** (event state, global counters, server\n thresholds, feature toggles). Read-only for clients. See **title-custom-data**.\n4. **Writing any of the server-only data, or any rule the player must not be able to fake** →\n a CloudCode handler, called with `client.cloudCode.execute(...)`. See **cloud-code**.\n\n## Two MCP surfaces: game CODE vs. a Title's live DATA\n\nThe platform exposes **two independent MCP servers** — don't confuse them:\n\n- **`@idosgames/mcp`** (this one) serves the **CODE registry**. Use it to WRITE a game's source:\n `get_host_scaffold`, `list_modules` / `search` / `get_module`, `get_manifest`, `list_skills` /\n `get_skill`. Transport: stdio (`npx -y @idosgames/mcp`). Its registry is bundled offline; to use the\n hosted copy set `IDOSGAMES_REGISTRY_URL=https://cloud.idosgames.com/drive/registry/latest` — a **base**\n the loader appends `/index.json`, `/modules/{id}.json`, etc. to (the base itself is not fetchable on\n R2; open `.../latest/index.json` to browse the catalog). Read-only, no auth. It never reads or changes\n a live Title's data.\n- **The Title-configuration MCP** (a separate backend server) owns a **live Title's DATA**. Use it to\n read/write the Title's `TitlePublicConfiguration` (`get_<field>` / `save_<field>`, or the whole\n model) and to generate assets (`generate_image` / `generate_audio` / `generate_text` /\n `generate_three_d` / `generate_video`). Transport: HTTP JSON-RPC at\n `POST https://site.idosgames.com/api/v2/mcp`; every tool call takes a `title_id` argument.\n Authorization is **OAuth 2.1** — there is no API key and nothing to paste. Connect it as a plain\n HTTP MCP server with **no headers**: your client gets a `401`, discovers the authorization\n server, registers itself, and opens a browser where the publisher picks which Titles and which\n permissions to grant. The token lives in your client's own credential store, so committed config\n holds only the URL:\n\n ```json\n {\n \"mcpServers\": {\n \"idosgames-title\": {\n \"type\": \"http\",\n \"url\": \"https://site.idosgames.com/api/v2/mcp\"\n }\n }\n }\n ```\n\n Permissions the publisher can grant: `config:read`, `config:write`, `cloudcode:write`,\n `ai:generate`, `kanban:read`, `kanban:write`, `build:write`. A grant is scoped to the Titles ticked on the consent screen, and the publisher\n can revoke it any time from **Connected apps** in the dashboard. If a call comes back\n `SCOPE_NOT_ALLOWED` or `TITLE_NOT_ALLOWED`, the token is fine — that permission or that Title\n simply was not granted; ask the publisher to re-authorize rather than retrying.\n\n **Shipping the game itself** (permission `build:write`) also goes through this server — the\n platform hosts it on the Title's own address (`{titleid}.idos.games`) and its idosgames.com page:\n\n 1. `begin_build_upload { title_id }` → `build_id`, a one-hour `upload_url`, `asset_base`.\n 2. Build the game as static files with that base: `vite build --base=<asset_base>` (a relative\n `./` base also works — Unity WebGL builds already are). Use the LIVE title id, never `-DEV`.\n 3. Zip the CONTENTS of the output folder (`index.html` at the zip root; a single `dist/` top folder\n is accepted) and upload it: `curl -T build.zip \"<upload_url>\"` — a plain PUT, no headers.\n 4. `finish_build_upload { title_id, build_id, deploy }` — unpacks it into a new version (it shows up\n in the dashboard's version history) and returns `TestUrl` (DEV address). `deploy: true` puts it\n live for players right away; without it, check `TestUrl` and call it again with `deploy: true`.\n Still unpacking when it returns → poll `get_build`. `list_builds` shows recent versions.\n\n A game that is live but whose Title is not published yet has no idosgames.com page — the\n response says so; the publisher publishes it from the dashboard.\n\n ⚠ **The build must be the game itself.** On the Title's idosgames.com page the game may embed\n (`<iframe>`) only platform hosts — never another website, with no setting to allow one. A\n \"wrapper\" build that signs in and then frames the publisher's own site shows **\"Content\n unavailable\"** there instead of the game: build and upload the real game. Network calls\n (`fetch`/WebSocket) from the embedded game reach platform hosts plus the Title's\n `Blockchain.IframeConnectHosts` (`save_blockchain`; wallet-connection hosts are always refused).\n\n Configuring a **fresh (empty) Title** so a game can actually run against it (currencies → game\n loop → bots) is its own checklist: see **idosgames-title-bootstrap**.\n\nRule of thumb: **game CODE → `@idosgames/mcp`; a Title's live config DATA and generated ASSETS → the\nbackend `v2/mcp` server.** Scaffolding a project and configuring/populating the Title it runs as are\ntwo different jobs on two different servers — connect the one that matches the task (or both).\n",
|
|
4
|
+
"content": "---\nname: idosgames-getting-started\ndescription: >-\n Start a new game or app on the iDosGames composable-module architecture: scaffold the base (sign-in\n + the lobby on the host shell) and plug in modules — the systems (shop, heroes, leaderboards…) and\n the games (board-game, idle-rpg, voxelcraft, mini-game, …). Use this whenever\n a developer wants to CREATE an iDosGames project from scratch, add an iDosGames game module to a\n project, or asks how @idosgames/app-shell, @idosgames/module-sdk, the host shell, mountHost, or\n src/modules.ts fit together. Pairs with idosgames-module-contract (writing a module) and\n idosgames-compose-modules (merging several) and idosgames-project-structure (where code goes as\n the project grows). If pulling modules over MCP, use the @idosgames/mcp\n tools get_host_scaffold / get_module / get_manifest.\n---\n\n# Getting started (iDosGames composable modules)\n\nAn iDosGames project is **one host shell + N feature modules**. The host owns everything that exists\nonce — the SDK client, login, the React root, the screen, a module registry, and a Mode Router that\nswitches between modules. A module is a library (a game or app) that plugs into the host; it never\ncreates the client, logs in, or owns the root.\n\n## Runtime packages (npm)\n\n- `@idosgames/core` — the SDK client (`createIDosGamesClient`): auth, currency, store, characters,\n blockchain, ~28 services. See the per-service skills (currency-system, store-system, …). Its\n **central cache** `client.cache` decides every read: the login brings the title config (by version)\n and the player's state (by each container's `Rev`) in ONE call; afterwards read state through\n `client.cache.ensureState([...containers])`, definitions through `getDefinitions()` (served from the\n config cache), server views through their service (cached by policy) — never re-fetch on start-up.\n The server views the game reads ride on that same login call (`ClientState.Views`): the client\n option `loginViews` — `\"auto\"` (default, what the game read last time), `\"all\"`, `\"none\"` or a list.\n- `@idosgames/module-sdk` — the module contract types (`Module`, `ModuleContext`, `EngineScene`,\n `UiPanel`). See idosgames-module-contract.\n- `@idosgames/react` — shared React glue (`IDosGamesProvider`, `useIDosGamesClient`, `useUserState`,\n `StatusProvider`, `createControllerContext`).\n- `@idosgames/app-shell` — the host runtime (`mountHost`, the Mode Router, the module registry).\n- `@idosgames/wallet` — optional wallet bridge (EVM/Solana) for on-chain deposits/withdrawals.\n\nInstall the exact versions from `get_manifest` (MCP) or the registry `index.json` `runtimePackages`.\n\n## Project shape\n\n```\nIDOS.md # the project guide every agent reads first (short, evergreen)\nAGENTS.md, CLAUDE.md # pointers to IDOS.md for external tools\ndocs/feature-history/ # one file per game system + README.md index (the project's memory)\nindex.html # mounts #app\nsrc/main.tsx # creates ONE client and calls mountHost({container, client, modules})\nsrc/modules.ts # the registry: export const modules: Module[] = [ … ] — imports + array only\nsrc/modules/{id}/ # each module's source + its module.meta.json (from get_module)\n```\n\nPlatform-created projects also carry `idos.modules.lock.json` (which modules came from the catalog,\nwith file fingerprints) — it is platform-owned; don't edit it. Where new code goes as the game grows,\nand how the project documents itself, is **idosgames-project-structure**.\n\n`src/main.tsx` (host-owned) is the only place that creates the client — but it does **not** sign in.\n`mountHost` owns sign-in: it replays a previous session (`autoLogin`) and renders the login screen\nwhen there is nothing to replay. Calling a `login*` method here skips that screen for good, taking\n\"switch account\" and wallet sign-in with it:\n\n```ts\nconst client = createIDosGamesClient({ titleID, buildKey, throttleMs: 0 });\n// Do NOT log in here — the host does it.\nmountHost({\n container: app,\n client,\n modules, // from ./modules\n renderLogin, // optional: your own screen; omitted = a plain guest-only default\n});\n```\n\nWhether a returning player is signed back in silently is the login screen's business, not the\nhost's: it calls `client.auth.setRememberSession(remember)` before a `login*` method. See the\nauthentication skill.\n\n`src/modules.ts` registers what the project composes:\n\n```ts\nimport type { Module } from \"@idosgames/module-sdk\";\nimport { boardGameModule } from \"./modules/board-game\";\nexport const modules: Module[] = [boardGameModule];\n```\n\n## Steps to scaffold\n\n1. **Base** — write the host scaffold to the project root (`get_host_scaffold`, or copy\n `templates/base`): sign-in and the lobby come from `@idosgames/app-shell` (mounted in `src/main.tsx`), `src/app.config.ts`\n (start in the lobby or a game, guest sign-in), an empty `src/modules.ts` → an empty lobby.\n2. **Bind the Title** — open `src/idos.title.ts` and set `IDOS_TITLE_ID` to the game's canonical\n Title id (and `IDOS_BUILD_KEY` if the title enforces one). This file is the project's single\n centralized identity — it is the highest-priority source and the only channel a packaged mobile\n build, iframe embed, or shared link has. On platform-created projects the platform generates it;\n on a manually scaffolded project **you fill it yourself**. Do NOT bind the title via `.env.local`\n (`VITE_IDOS_TITLE_ID`) — that is a local-dev fallback for the raw template only, and it does not\n travel with the code.\n3. **Pick modules** — `list_modules` / `search`, then `get_module {id}` for each. Write its files\n into `src/modules/{id}/`. The base's systems are the modules with `inBase: true` (profile,\n inventory, invite, friends, quests, mailbox); add the game(s) with the modules each game `uses`, and any\n other system (shop, VIP, leaderboards…) only when the app needs it.\n4. **Register** — for each module add `import { {camelCase(id)}Module } from \"./modules/{id}\"` and\n push it into the `modules` array (e.g. `board-game` → `boardGameModule`). Then add the module to\n the \"Installed modules\" block of `IDOS.md` (one line: ``- `board-game` (0.1.0) — src/modules/board-game/``)\n — on platform projects the platform maintains that block itself.\n5. **Install deps** — the union of `runtimePackages` + each module's `dependencies`. Modules bring\n their own engine (three, phaser); the host brings react/react-dom/app-shell.\n6. **Run** — sign-in → straight INTO the game, never a \"Play\" click (owner's rule 07.11.2026): a game\n whose route says `inLobby: true` (idle-rpg) sits inside the lobby, the lobby's balances and tabs\n (the systems' screens; a tab hides itself while the title has nothing for it) around it; else the\n first game on the whole screen with a small button back to the lobby. The lobby draws the balances and the wallet; games hide their own copies by\n themselves (`sharedUi` roles).\n\nTo combine multiple genres into one game, see **idosgames-compose-modules**. To write or edit a\nmodule, see **idosgames-module-contract**.\n\n## Where state lives (decide this before writing the first save)\n\nThe project is client-side code in the player's browser. `localStorage`, module fields, and React\nstate are **not storage** — nothing there survives a device change, and nothing there is trusted.\n\n1. **A dedicated module owns it?** Use that module. Currencies, inventory, quests, characters,\n leaderboards, store purchases each have a service that enforces the rules server-side.\n2. **Otherwise, per-player data → `client.userCustomData`** — buckets `Private`/`Public` are\n client-writable (settings, cosmetics), `ReadOnly`/`Internal` are server-only. Anything a player\n could cheat by editing goes in the server-only buckets. See **user-custom-data**.\n3. **Shared by all players → `client.titleCustomData`** (event state, global counters, server\n thresholds, feature toggles). Read-only for clients. See **title-custom-data**.\n4. **Writing any of the server-only data, or any rule the player must not be able to fake** →\n a CloudCode handler, called with `client.cloudCode.execute(...)`. See **cloud-code**.\n\n## Two MCP surfaces: game CODE vs. a Title's live DATA\n\nThe platform exposes **two independent MCP servers** — don't confuse them:\n\n- **`@idosgames/mcp`** (this one) serves the **CODE registry**. Use it to WRITE a game's source:\n `get_host_scaffold`, `list_modules` / `search` / `get_module`, `get_manifest`, `list_skills` /\n `get_skill`. Transport: stdio (`npx -y @idosgames/mcp`). Its registry is bundled offline; to use the\n hosted copy set `IDOSGAMES_REGISTRY_URL=https://cloud.idosgames.com/drive/registry/latest` — a **base**\n the loader appends `/index.json`, `/modules/{id}.json`, etc. to (the base itself is not fetchable on\n R2; open `.../latest/index.json` to browse the catalog). Read-only, no auth. It never reads or changes\n a live Title's data.\n- **The Title-configuration MCP** (a separate backend server) owns a **live Title's DATA**. Use it to\n read/write the Title's `TitlePublicConfiguration` (`get_<field>` / `save_<field>`, or the whole\n model) and to generate assets (`generate_image` / `generate_audio` / `generate_text` /\n `generate_three_d` / `generate_video`). Transport: HTTP JSON-RPC at\n `POST https://site.idosgames.com/api/v2/mcp`; every tool call takes a `title_id` argument.\n Authorization is **OAuth 2.1** — there is no API key and nothing to paste. Connect it as a plain\n HTTP MCP server with **no headers**: your client gets a `401`, discovers the authorization\n server, registers itself, and opens a browser where the publisher picks which Titles and which\n permissions to grant. The token lives in your client's own credential store, so committed config\n holds only the URL:\n\n ```json\n {\n \"mcpServers\": {\n \"idosgames-title\": {\n \"type\": \"http\",\n \"url\": \"https://site.idosgames.com/api/v2/mcp\"\n }\n }\n }\n ```\n\n Permissions the publisher can grant: `config:read`, `config:write`, `cloudcode:write`,\n `ai:generate`, `kanban:read`, `kanban:write`, `build:write`. A grant is scoped to the Titles ticked on the consent screen, and the publisher\n can revoke it any time from **Connected apps** in the dashboard. If a call comes back\n `SCOPE_NOT_ALLOWED` or `TITLE_NOT_ALLOWED`, the token is fine — that permission or that Title\n simply was not granted; ask the publisher to re-authorize rather than retrying.\n\n **Shipping the game itself** (permission `build:write`) also goes through this server — the\n platform hosts it on the Title's own address (`{titleid}.idos.games`) and its idosgames.com page:\n\n 1. `begin_build_upload { title_id }` → `build_id`, a one-hour `upload_url`, `asset_base`.\n 2. Build the game as static files with that base: `vite build --base=<asset_base>` (a relative\n `./` base also works — Unity WebGL builds already are). Use the LIVE title id, never `-DEV`.\n 3. Zip the CONTENTS of the output folder (`index.html` at the zip root; a single `dist/` top folder\n is accepted) and upload it: `curl -T build.zip \"<upload_url>\"` — a plain PUT, no headers.\n 4. `finish_build_upload { title_id, build_id, deploy }` — unpacks it into a new version (it shows up\n in the dashboard's version history) and returns `TestUrl` (DEV address). `deploy: true` puts it\n live for players right away; without it, check `TestUrl` and call it again with `deploy: true`.\n Still unpacking when it returns → poll `get_build`. `list_builds` shows recent versions.\n\n A game that is live but whose Title is not published yet has no idosgames.com page — the\n response says so; the publisher publishes it from the dashboard.\n\n ⚠ **The build must be the game itself.** On the Title's idosgames.com page the game may embed\n (`<iframe>`) only platform hosts — never another website, with no setting to allow one. A\n \"wrapper\" build that signs in and then frames the publisher's own site shows **\"Content\n unavailable\"** there instead of the game: build and upload the real game. Network calls\n (`fetch`/WebSocket) from the embedded game reach platform hosts plus the Title's\n `Blockchain.IframeConnectHosts` (`save_blockchain`; wallet-connection hosts are always refused).\n\n Configuring a **fresh (empty) Title** so a game can actually run against it (currencies → game\n loop → bots) is its own checklist: see **idosgames-title-bootstrap**.\n\nRule of thumb: **game CODE → `@idosgames/mcp`; a Title's live config DATA and generated ASSETS → the\nbackend `v2/mcp` server.** Scaffolding a project and configuring/populating the Title it runs as are\ntwo different jobs on two different servers — connect the one that matches the task (or both).\n",
|
|
5
5
|
"references": []
|
|
6
6
|
}
|
package/registry/video.json
CHANGED
|
@@ -9660,7 +9660,7 @@
|
|
|
9660
9660
|
"rig2d-character": {
|
|
9661
9661
|
"feature": "rig2d-character",
|
|
9662
9662
|
"modality": "text",
|
|
9663
|
-
"system": "You design a 2D cartoon hero for a short video: a layered chibi character on a skeleton, seen from the side. Reply with ONE JSON object — a hero recipe — and nothing else: no markdown, no comments.\n\n{ \"name\": string (≤ 40 chars, in the player's language), \"description\": string (≤ 300 chars),\n \"look\": { \"parts\": { \"<part type>\": \"<part id>\" | null, … }, \"colors\": { \"<colour target>\": \"#rrggbb\", … } },\n \"art\": { \"<picture path>\": { \"svg\": \"<svg markup without the svg wrapper>\" }, … } (optional) }\n\n- Pick parts ONLY from the lists below; null takes a part off where it is allowed. Choose colours that make the hero instantly readable.\n- \"art\" redraws a picture of a part you picked with your own SVG of exactly that canvas (width × height in px, y down): only <path>, <circle>, <ellipse>, <rect>, <line>, <polygon>, <polyline>, <g>, gradients and clipPath — no text, no images, no links, no scripts; ≤ 12000 characters each, ≤ 16 pictures. Draw in the same style: a heavy dark outline (#1f1a21, width 3–4), flat fills with one darker shade. Use it only when no part fits the idea (a chef's apron, a superhero suit).\n\nParts and pictures of the rig:\nBack (or null): back/1, back/2, back/3, back/4, back/5, back/6, back/7, back/8, back/9, back/10, back/11, back/12, back/13, back/14, back/15, back/16, back/17, back/18, back/19, back/20, back/21, back/22, back/23, back/24, back/25, back/26, back/27, back/28, back/29, back/30, back/knight_cape, back/mage_cape\nBeard (or null): beard/1, beard/2, beard/3, beard/4, beard/5, beard/6, beard/7, beard/8, beard/9, beard/10\nBoots (or null): boots/1, boots/2, boots/3, boots/4, boots/5, boots/6, boots/7, boots/8, boots/9, boots/10, boots/11, boots/12, boots/13, boots/14, boots/15, boots/16, boots/17, boots/18, boots/19, boots/20, boots/21, boots/22, boots/23, boots/24, boots/25, boots/26, boots/27, boots/28, boots/29, boots/30, boots/plate_boots, boots/leather_boots\nPants (or null): pants/1, pants/2, pants/3, pants/4, pants/5, pants/6, pants/7, pants/8, pants/9, pants/10, pants/11, pants/12, pants/13, pants/14, pants/15, pants/16, pants/17, pants/18, pants/19, pants/20, pants/21, pants/22, pants/23, pants/24, pants/25, pants/26, pants/27, pants/28, pants/29, pants/30, pants/31, pants/32, pants/33, pants/34, pants/35, pants/36, pants/37, pants/38, pants/39, pants/40, pants/41, pants/42, pants/43, pants/44, pants/45, pants/46, pants/47, pants/48, pants/49, pants/50\nBrows: brows/1, brows/2, brows/3, brows/4, brows/5, brows/6, brows/7, brows/8, brows/9, brows/10\nEyes: eyes/1, eyes/2, eyes/3, eyes/4, eyes/5, eyes/6, eyes/7, eyes/8, eyes/9, eyes/10, eyes/11, eyes/12, eyes/13, eyes/14, eyes/15, eyes/16, eyes/17, eyes/18, eyes/19, eyes/20\nGloves (or null): gloves/1, gloves/2, gloves/3, gloves/4, gloves/5, gloves/6, gloves/7, gloves/8, gloves/9, gloves/10, gloves/11, gloves/12, gloves/13, gloves/14, gloves/15, gloves/16, gloves/17, gloves/18, gloves/19, gloves/20, gloves/21, gloves/22, gloves/23, gloves/24, gloves/25, gloves/26, gloves/27, gloves/28, gloves/29, gloves/30, gloves/gauntlets, gloves/bracers\nHair (or null): hair/1, hair/2, hair/3, hair/4, hair/5, hair/6, hair/7, hair/8, hair/9, hair/10, hair/11, hair/12, hair/13, hair/14, hair/15, hair/16, hair/17, hair/18, hair/19, hair/20, hair/21, hair/22, hair/23, hair/24, hair/25, hair/26, hair/27, hair/28, hair/29, hair/30\nHat (or null): hat/1, hat/2, hat/3, hat/4, hat/5, hat/6, hat/7, hat/8, hat/9, hat/10, hat/11, hat/12, hat/13, hat/14, hat/15, hat/16, hat/17, hat/18, hat/19, hat/20, hat/21, hat/22, hat/23, hat/24, hat/25, hat/26, hat/27, hat/28, hat/29, hat/30, hat/31, hat/32, hat/33, hat/34, hat/35, hat/36, hat/37, hat/38, hat/39, hat/40, hat/41, hat/42, hat/43, hat/44, hat/45, hat/46, hat/47, hat/48, hat/49, hat/50, hat/51, hat/52, hat/53, hat/54, hat/55, hat/56, hat/57, hat/58, hat/59, hat/60, hat/great_helm, hat/knight_helm, hat/winged_helm, hat/wizard_hat, hat/ranger_hood, hat/iron_helm\nMouth: mouth/1, mouth/2, mouth/3, mouth/4, mouth/5, mouth/6, mouth/7, mouth/8, mouth/9, mouth/10\nGlasses (or null): glasses/1, glasses/2, glasses/3, glasses/4, glasses/5, glasses/6, glasses/7, glasses/8, glasses/9, glasses/10, glasses/11, glasses/12, glasses/13, glasses/14, glasses/15, glasses/16, glasses/17, glasses/18, glasses/19, glasses/20, glasses/21, glasses/22, glasses/23, glasses/24, glasses/25, glasses/26, glasses/27, glasses/28, glasses/29, glasses/30\nOffhand (or null): offhand/1, offhand/2, offhand/3, offhand/4, offhand/5, offhand/6, offhand/7, offhand/8, offhand/9, offhand/10, offhand/11, offhand/12, offhand/13, offhand/14, offhand/15, offhand/16, offhand/17, offhand/18, offhand/19, offhand/20, offhand/21, offhand/22, offhand/23, offhand/24, offhand/25, offhand/26, offhand/27, offhand/28, offhand/29, offhand/30, offhand/31, offhand/32, offhand/33, offhand/34, offhand/35, offhand/36, offhand/37, offhand/38, offhand/39, offhand/40, offhand/kite_shield, offhand/heater_shield, offhand/round_shield, offhand/tower_shield, offhand/buckler\nWeapon (or null): weapon/1, weapon/2, weapon/3, weapon/4, weapon/5, weapon/6, weapon/7, weapon/10, weapon/11, weapon/12, weapon/13, weapon/14, weapon/15, weapon/16, weapon/17, weapon/18, weapon/19, weapon/20, weapon/21, weapon/22, weapon/23, weapon/24, weapon/25, weapon/26, weapon/27, weapon/28, weapon/29, weapon/30, weapon/31, weapon/32, weapon/33, weapon/34, weapon/35, weapon/36, weapon/37, weapon/38, weapon/39, weapon/40, weapon/knight_sword, weapon/dark_blade, weapon/broadsword, weapon/flame_sword, weapon/dagger, weapon/battle_axe, weapon/double_axe, weapon/mace, weapon/warhammer, weapon/spear, weapon/wizard_staff, weapon/druid_staff, weapon/hunting_bow, weapon/elven_bow, weapon/pistol, weapon/crossbow, weapon/musket, weapon/rifle\nTop: top/1, top/2, top/3, top/4, top/5, top/6, top/7, top/8, top/9, top/10, top/11, top/12, top/13, top/14, top/15, top/16, top/17, top/18, top/19, top/20, top/21, top/22, top/23, top/24, top/25, top/26, top/27, top/28, top/29, top/30, top/31, top/32, top/33, top/34, top/35, top/36, top/37, top/38, top/39, top/40, top/41, top/42, top/43, top/44, top/45, top/46, top/47, top/48, top/49, top/50, top/51, top/52, top/53, top/54, top/55, top/56, top/57, top/58, top/59, top/60, top/plate_armor, top/chainmail, top/leather_armor, top/mage_robe\nSkin: skin/1\ncolors: Skin, Hair, Beard, Brows, Eyes (#rrggbb)\npictures (path WxH, y down): back/1 84x76; back/2 75x74; back/3 71x71; back/4 74x83; back/5 81x95; back/6 76x88; back/7 79x78; back/8 55x109; back/9 85x100; back/10 63x94; back/11 105x99; back/12 73x98; back/13 92x93; back/14 76x75; back/15 95x124; back/16 79x86; back/17 76x83; back/18 96x90; back/19 102x97; back/20 65x87; back/21 84x76; back/22 71x71; back/23 75x74; back/24 95x124; back/25 96x90; back/26 65x87; back/27 73x98; back/28 76x83; back/29 55x109; back/30 79x86; beard/1 43x27; beard/2 42x39; beard/3 89x30; beard/4 85x46; beard/5 73x44; beard/6 39x42; beard/7 54x20; beard/8 80x18; beard/9 29x25; beard/10 88x44; boots/1_near 31x20; boots/1_far 31x20; boots/2_near 30x23; boots/2_far 30x23; boots/3_near 32x23; boots/3_far 32x23; boots/4_near 32x22; boots/4_far 32x22; boots/5_near 26x30; boots/5_far 26x30; boots/6_near 30x18; boots/6_far 30x18; boots/7_near 33x25; boots/7_far 33x25; boots/8_near 32x20; boots/8_far 32x20; boots/9_near 32x20; boots/9_far 32x20; boots/10_near 35x19; boots/10_far 35x19; boots/11_near 32x26; boots/11_far 32x26; boots/12_near 30x22; boots/12_far 30x22; boots/13_near 28x23; boots/13_far 28x23; boots/14_near 31x17; boots/14_far 31x17; boots/15_near 28x28; boots/15_far 28x28; boots/16_near 30x25; boots/16_far 30x25; boots/17_near 30x24; boots/17_far 30x24; boots/18_near 31x18; boots/18_far 31x18; boots/19_near 28x22; boots/19_far 28x22; boots/20_near 35x24; boots/20_far 35x24; boots/21_near 31x20; boots/21_far 31x20; boots/22_near 28x22; boots/22_far 28x22; boots/23_near 28x22; boots/23_far 28x22; boots/24_near 28x28; boots/24_far 28x28; boots/25_near 28x28; boots/25_far 28x28; boots/26_near 30x24; boots/26_far 30x24; boots/27_near 26x30; boots/27_far 26x30; boots/28_near 26x30; boots/28_far 26x30; boots/29_near 30x23; boots/29_far 30x23; boots/30_near 32x20; boots/30_far 32x20; pants/1 79x26; pants/1_near 25x28; pants/1_far 25x28; pants/2 79x26; pants/2_near 25x28; pants/2_far 25x28; pants/3 79x26; pants/3_near 25x41; pants/3_far 25x41; pants/4 79x26; pants/4_near 31x28; pants/4_far 31x28; pants/5 79x26; pants/5_near 36x42; pants/5_far 38x43; pants/6 92x40; pants/7 92x40; pants/8 93x46; pants/9 92x41; pants/10 92x40; pants/11 88x40; pants/12 79x26; pants/13 79x26; pants/13_near 25x41; pants/13_far 25x41; pants/14 79x26; pants/14_near 36x42; pants/14_far 38x43; pants/15 79x26; pants/15_near 25x41; pants/15_far 25x41; pants/16 79x26; pants/16_near 25x28; pants/16_far 25x40; pants/17 92x39; pants/17_near 25x41; pants/17_far 25x41; pants/18 96x36; pants/19 79x26; pants/19_near 25x41; pants/19_far 25x41; pants/20 79x26; pants/20_near 25x28; pants/20_far 25x28; pants/21 79x26; pants/21_near 25x41; pants/21_far 25x41; pants/22 79x26; pants/22_near 25x41; pants/22_far 25x41; pants/23 79x26; pants/23_near 25x41; pants/23_far 25x41; pants/24 79x26; pants/24_near 25x41; pants/24_far 25x41; pants/25 79x26; pants/25_near 25x41; pants/25_far 25x41; pants/26 82x37; pants/27 79x26; pants/27_near 25x41; pants/27_far 25x41; pants/28 79x26; pants/28_near 25x28; pants/28_far 25x28; pants/29 79x26; pants/29_near 25x28; pants/29_far 25x40; pants/30 79x26; pants/30_near 25x41; pants/30_far 25x41; pants/31 79x26; pants/31_near 25x41; pants/31_far 25x41; pants/32 79x26; pants/32_near 31x28; pants/32_far 31x28; pants/33 79x26; pants/33_near 31x28; pants/33_far 31x28; pants/34 79x26; pants/34_near 31x28; pants/34_far 31x28; pants/35 79x26; pants/35_near 31x28; pants/35_far 31x28; pants/36 92x39; pants/37 79x26; pants/37_near 25x41; pants/37_far 25x41; pants/38 79x26; pants/38_near 25x28; pants/38_far 25x28; pants/39 79x26; pants/39_near 25x28; pants/39_far 25x28; pants/40 79x26; pants/40_near 25x41; pants/40_far 25x41; pants/41 79x26; pants/41_near 25x41; pants/41_far 25x41; pants/42 82x37; pants/43 82x37; pants/44 88x40; pants/45 79x26; pants/45_near 36x42; pants/45_far 38x43; pants/46 79x26; pants/46_near 36x42; pants/46_far 38x43; pants/47 96x36; pants/47_near 25x41; pants/47_far 25x41; pants/48 96x38; pants/49 79x26; pants/50 79x26; pants/50_near 31x28; pants/50_far 31x28; brows/1 51x11; brows/2 65x18; brows/3 61x16; brows/4 66x19; brows/5 72x23; brows/6 44x10; brows/7 62x18; brows/8 57x18; brows/9 53x13; brows/10 17x23; eyes/1 61x20; eyes/2 63x22; eyes/3 66x30; eyes/4 64x31; eyes/5 64x24; eyes/6 63x28; eyes/7 69x29; eyes/8 44x22; eyes/9 48x20; eyes/10 61x23; eyes/11 60x21; eyes/12 65x27; eyes/13 59x21; eyes/14 62x22; eyes/15 63x23; eyes/16 59x26; eyes/17 55x22; eyes/18 57x6; eyes/19 59x23; eyes/20 59x26; glasses/1 85x48; glasses/2 71x30; glasses/3 90x38; glasses/4 96x40; glasses/5 80x44; glasses/6 91x41; glasses/7 80x35; glasses/8 58x27; glasses/9 84x47; glasses/10 81x58; glasses/11 72x21; glasses/12 83x36; glasses/13 81x24; glasses/14 72x37; glasses/15 83x44; glasses/16 86x39; glasses/17 89x58; glasses/18 74x39; glasses/19 89x37; glasses/20 90x54; glasses/21 79x40; glasses/22 71x32; glasses/23 82x38; glasses/24 77x34; glasses/25 73x34; glasses/26 70x33; glasses/27 74x64; glasses/28 86x77; glasses/29 72x41; glasses/30 71x51; offhand/1 101x83; offhand/2 79x87; offhand/3 87x87; offhand/4 96x81; offhand/5 109x87; offhand/6 73x73; offhand/7 91x70; offhand/8 89x89; offhand/9 95x70; offhand/10 85x82; offhand/11_low 56x56; offhand/12_low 56x56; offhand/13_low 65x64; offhand/14_low 61x46; offhand/15_low 78x36; offhand/16_low 65x30; offhand/17_low 55x44; offhand/18_low 37x37; offhand/19_low 46x43; offhand/20_low 43x41; offhand/21_low 56x44; offhand/22_low 56x66; offhand/23_low 49x72; offhand/24_low 56x55; offhand/25_low 77x51; offhand/26_low 94x50; offhand/27_low 78x59; offhand/28_low 63x35; offhand/29_low 78x35; offhand/30_low 46x59; offhand/31_low 53x44; offhand/32_low 57x54; offhand/33_low 65x29; offhand/34_low 92x34; offhand/35_low 54x48; offhand/36_low 83x31; offhand/37_low 51x42; offhand/38_low 65x32; offhand/39_low 53x37; offhand/40_low 53x37; weapon/1 111x77; weapon/2 112x92; weapon/3 115x60; weapon/4 96x78; weapon/5 144x71; weapon/6 135x43; weapon/7 141x40; weapon/10 100x66; weapon/11 101x87; weapon/12 124x58; weapon/13 144x33; weapon/14 131x45; weapon/15 143x60; weapon/16 142x44; weapon/17 133x35; weapon/18 103x65; weapon/19 132x39; weapon/20 133x33; weapon/21 112x29; weapon/22 116x45; weapon/23 88x58; weapon/24 137x28; weapon/25 153x43; weapon/26 160x50; weapon/27 156x51; weapon/28 174x47; weapon/29 160x65; weapon/30 147x40; weapon/31 112x92; weapon/32 115x59; weapon/33 157x51; weapon/34 143x44; weapon/35 143x44; weapon/36 175x47; weapon/37 124x58; weapon/38 135x43; weapon/39 143x60; weapon/40 87x58; gloves/1_near 28x25; gloves/1_far 27x25; gloves/2_near 34x35; gloves/2_far 32x35; gloves/3_near 28x25; gloves/3_far 27x25; gloves/4_near 31x34; gloves/4_far 32x33; gloves/5_near 36x36; gloves/5_far 35x36; gloves/6_near 31x32; gloves/6_far 32x33; gloves/7_near 35x34; gloves/7_far 34x34; gloves/8_near 30x14; gloves/8_far 28x14; gloves/9_near 38x18; gloves/9_far 38x18; gloves/10_near 34x35; gloves/10_far 32x35; gloves/11_near 30x23; gloves/11_far 28x23; gloves/12_near 33x35; gloves/12_far 33x36; gloves/13_near 36x36; gloves/13_far 32x37; gloves/14_near 32x27; gloves/14_far 29x24; gloves/15_near 36x40; gloves/15_far 35x40; gloves/16_near 31x36; gloves/16_far 30x37; gloves/17_near 31x36; gloves/17_far 30x37; gloves/18_near 32x31; gloves/18_far 30x30; gloves/19_near 32x34; gloves/19_far 29x34; gloves/20_near 34x38; gloves/20_far 31x38; gloves/21_near 28x25; gloves/21_far 27x25; gloves/22_near 28x25; gloves/22_far 27x25; gloves/23_near 36x40; gloves/23_far 35x40; gloves/24_near 36x40; gloves/24_far 35x40; gloves/25_near 36x36; gloves/25_far 32x37; gloves/26_near 30x14; gloves/26_far 28x14; gloves/27_near 38x18; gloves/27_far 38x18; gloves/28_near 38x18; gloves/28_far 38x18; gloves/29_near 31x34; gloves/29_far 32x33; gloves/30_near 32x27; gloves/30_far 29x24; hair_under/1 87x54; hair_under/2 111x60; hair_under/3 97x54; hair_under/4 103x55; hair_under/5 101x64; hair_under/6 96x54; hair_under/7 92x52; hair_under/8 123x75; hair_under/9 105x78; hair_under/10 130x72; hair_under/11 97x87; hair_under/12 126x64; hair_under/13 104x40; hair_under/14 96x55; hair_under/15 92x49; hair_under/16 95x55; hair_under/17 102x57; hair_under/18 118x69; hair_under/19 118x88; hair_under/20 96x52; hair_under/21 101x60; hair_under/22 90x53; hair_under/23 94x53; hair_under/24 122x66; hair_under/25 118x67; hair_under/26 98x55; hair_under/27 103x52; hair_under/28 122x79; hair_under/29 99x90; hair_under/30 116x96; hair/1 87x54; hair/2 113x61; hair/3 98x65; hair/4 119x94; hair/5 108x75; hair/6 115x70; hair/7 97x67; hair/8 147x120; hair/9 105x113; hair/10 136x84; hair/11 133x113; hair/12 134x74; hair/13 105x40; hair/14 114x80; hair/15 96x57; hair/16 97x86; hair/17 105x63; hair/18 128x83; hair/19 128x107; hair/20 97x59; hair/20_back 67x91; hair/21 101x65; hair/21_back 96x105; hair/22 114x71; hair/23 124x84; hair/24 127x74; hair/25 135x102; hair/26 103x68; hair/27 103x53; hair/28 125x86; hair/28_back 97x102; hair/29 99x111; hair/29_back 79x112; hair/30 119x114; hat/1 120x110; hat/1_back 95x59; hat/2 154x75; hat/3_low 126x82; hat/4 122x78; hat/5 118x120; hat/6 130x82; hat/7 121x105; hat/7_back 33x53; hat/8_low 136x75; hat/9_low 117x93; hat/10_low 140x94; hat/11_low 114x115; hat/12_low 145x69; hat/13_low 117x131; hat/14_low 140x120; hat/15 145x102; hat/16 129x97; hat/16_back 36x56; hat/17_low 126x84; hat/18 142x93; hat/19_low 122x106; hat/20_low 106x71; hat/20_back 68x44; hat/21_low 117x75; hat/22_low 123x94; hat/22_back 37x46; hat/23 137x110; hat/23_back 134x64; hat/24 133x81; hat/25 126x100; hat/25_back 43x60; hat/26 108x70; hat/26_back 46x52; hat/27_low 133x83; hat/28 126x107; hat/28_back 112x56; hat/29_low 124x117; hat/30_low 117x78; hat/31_low 136x75; hat/32 120x110; hat/32_back 95x59; hat/33_low 145x69; hat/34_low 106x71; hat/34_back 68x44; hat/35_low 106x71; hat/35_back 68x44; hat/36 154x75; hat/37 133x81; hat/38 133x81; hat/39 118x120; hat/40_low 117x75; hat/41_low 117x75; hat/42_low 117x93; hat/43_low 123x94; hat/43_back 37x46; hat/44_low 133x79; hat/45_low 114x115; hat/46_low 114x115; hat/47_low 140x94; hat/48 122x78; hat/49_low 107x70; hat/49_back 46x52; hat/50_low 124x117; hat/51_low 117x131; hat/52 126x107; hat/52_back 112x56; hat/53_low 140x120; hat/54_low 140x120; hat/55 137x110; hat/55_back 134x64; hat/56 137x110; hat/56_back 134x64; hat/57 130x82; hat/58 129x97; hat/58_back 36x56; hat/59 121x105; hat/59_back 33x53; hat/60 145x102; mouth/1 15x7; mouth/2 27x19; mouth/3 23x12; mouth/4 24x11; mouth/5 33x16; mouth/6 20x18; mouth/7 20x19; mouth/8 23x13; mouth/9 24x19; mouth/10 23x15; skin/1_arm_near 23x72; skin/1_arm_far 29x71; skin/1_torso 77x50; skin/1_head 103x90; skin/1_leg 27x55; top/1 87x71; top/2 91x88; top/2_sleeve_near 37x34; top/2_sleeve_far 34x35; top/3 90x88; top/3_sleeve_near 31x64; top/3_sleeve_far 32x63; top/4 87x88; top/4_sleeve_near 37x34; top/4_sleeve_far 34x35; top/5 87x81; top/5_sleeve_near 37x34; top/5_sleeve_far 34x35; top/6 88x82; top/6_sleeve_near 29x37; top/6_sleeve_far 29x38; top/7 87x83; top/7_sleeve_near 29x37; top/7_sleeve_far 29x38; top/8 87x80; top/9 87x81; top/9_sleeve_near 28x58; top/9_cap_near 37x34; top/9_sleeve_far 34x59; top/9_cap_far 34x36; top/10 56x59; top/10_coat 88x106; top/10_sleeve_near 28x58; top/10_sleeve_far 32x59; top/11 87x72; top/11_sleeve_near 37x34; top/11_sleeve_far 34x35; top/12 86x81; top/12_sleeve_near 28x58; top/12_sleeve_far 32x59; top/13 88x82; top/13_sleeve_near 28x58; top/13_sleeve_far 32x59; top/14 87x81; top/15 90x88; top/15_sleeve_near 31x64; top/15_sleeve_far 32x63; top/16 86x84; top/16_sleeve_near 31x59; top/16_sleeve_far 34x59; top/17 87x83; top/17_sleeve_near 34x37; top/17_sleeve_far 33x36; top/18 89x74; top/18_sleeve_near 37x34; top/18_sleeve_far 34x35; top/19 89x92; top/19_sleeve_near 37x34; top/19_sleeve_far 34x35; top/20 87x71; top/20_sleeve_near 28x58; top/20_sleeve_far 32x59; top/21 87x71; top/21_sleeve_near 28x58; top/21_sleeve_far 32x59; top/22 87x81; top/22_sleeve_near 28x58; top/22_sleeve_far 32x59; top/23 87x81; top/23_sleeve_near 28x58; top/23_sleeve_far 32x59; top/24 87x81; top/24_sleeve_near 28x58; top/24_sleeve_far 32x59; top/25 88x72; top/25_sleeve_near 28x58; top/25_sleeve_far 32x59; top/26 92x93; top/27 87x82; top/27_sleeve_near 28x58; top/27_sleeve_far 32x59; top/28 86x74; top/29 63x65; top/29_coat 88x106; top/29_sleeve_near 28x58; top/29_sleeve_far 32x59; top/30 87x80; top/30_sleeve_near 28x58; top/30_sleeve_far 32x59; top/31 90x88; top/31_sleeve_near 31x64; top/31_sleeve_far 32x63; top/32 88x82; top/32_sleeve_near 28x58; top/32_sleeve_far 32x59; top/33 87x81; top/33_sleeve_near 37x34; top/33_sleeve_far 34x35; top/34 87x81; top/34_sleeve_near 37x34; top/34_sleeve_far 34x35; top/35 87x81; top/36 87x71; top/36_sleeve_near 28x58; top/36_sleeve_far 32x59; top/37 87x81; top/37_sleeve_near 29x37; top/37_sleeve_far 29x38; top/38 87x82; top/38_sleeve_near 28x58; top/38_sleeve_far 32x59; top/39 87x83; top/39_sleeve_near 34x37; top/39_sleeve_far 33x36; top/40 90x88; top/40_sleeve_near 31x64; top/40_sleeve_far 32x63; top/41 91x87; top/41_sleeve_near 31x64; top/41_sleeve_far 32x63; top/42 87x81; top/42_sleeve_near 28x58; top/42_sleeve_far 32x59; top/43 87x79; top/43_sleeve_near 29x37; top/43_sleeve_far 29x38; top/44 87x72; top/44_sleeve_near 31x64; top/44_sleeve_far 32x63; top/45 87x88; top/45_sleeve_near 28x58; top/45_sleeve_far 32x59; top/46 87x72; top/46_sleeve_near 28x58; top/46_cap_near 37x34; top/46_sleeve_far 32x59; top/46_cap_far 34x35; top/47 88x82; top/47_sleeve_near 29x37; top/47_sleeve_far 29x38; top/48 87x71; top/48_sleeve_near 28x58; top/48_sleeve_far 32x59; top/49 86x81; top/49_sleeve_near 28x58; top/49_sleeve_far 32x59; top/50 89x74; top/50_sleeve_near 37x34; top/50_sleeve_far 34x35; top/51 87x72; top/51_sleeve_near 31x64; top/51_sleeve_far 32x63; top/52 87x72; top/52_sleeve_near 29x37; top/52_sleeve_far 29x38; top/53 87x72; top/53_sleeve_near 29x37; top/53_sleeve_far 29x38; top/54 89x92; top/54_sleeve_near 37x34; top/54_sleeve_far 34x35; top/55 87x81; top/56 86x84; top/56_sleeve_near 31x59; top/56_sleeve_far 34x59; top/57 87x80; top/57_sleeve_near 28x58; top/57_sleeve_far 32x59; top/58 92x93; top/59 90x88; top/59_sleeve_near 31x64; top/59_sleeve_far 32x63; top/60 87x80; weapon/knight_sword 156x51; weapon/dark_blade 160x50; weapon/broadsword 160x65; weapon/flame_sword 174x47; weapon/dagger 112x29; weapon/battle_axe 115x60; weapon/double_axe 112x92; weapon/mace 144x71; weapon/warhammer 103x65; weapon/spear 147x40; weapon/wizard_staff 135x43; weapon/druid_staff 144x33; offhand/kite_shield 85x82; offhand/heater_shield 96x81; offhand/round_shield 89x89; offhand/tower_shield 109x87; offhand/buckler 73x73; hat/great_helm 118x120; hat/knight_helm_low 114x115; hat/winged_helm_low 133x83; hat/wizard_hat 154x75; hat/ranger_hood_low 114x115; hat/iron_helm 130x82; top/plate_armor 87x88; top/plate_armor_sleeve_near 37x34; top/plate_armor_sleeve_far 34x35; top/chainmail 91x88; top/chainmail_sleeve_near 37x34; top/chainmail_sleeve_far 34x35; top/leather_armor 89x92; top/leather_armor_sleeve_near 37x34; top/leather_armor_sleeve_far 34x35; top/mage_robe 89x74; top/mage_robe_sleeve_near 37x34; top/mage_robe_sleeve_far 34x35; gloves/gauntlets_near 34x35; gloves/gauntlets_far 32x35; gloves/bracers_near 36x40; gloves/bracers_far 35x40; boots/plate_boots_near 30x23; boots/plate_boots_far 30x23; boots/leather_boots_near 26x30; boots/leather_boots_far 26x30; back/knight_cape 95x124; back/mage_cape 95x124; weapon/hunting_bow 70x112; weapon/hunting_bow_drawn 70x112; weapon/hunting_bow_shot 70x112; weapon/elven_bow 72x128; weapon/elven_bow_drawn 72x128; weapon/elven_bow_shot 72x128; weapon/pistol 96x40; weapon/pistol_shot 96x40; weapon/crossbow 104x56; weapon/crossbow_shot 104x56; weapon/musket 180x40; weapon/musket_shot 180x40; weapon/rifle 160x52; weapon/rifle_shot 160x52",
|
|
9663
|
+
"system": "You design a 2D cartoon hero for a short video: a layered chibi character on a skeleton, seen from the side. Reply with ONE JSON object — a hero recipe — and nothing else: no markdown, no comments.\n\n{ \"name\": string (≤ 40 chars, in the player's language), \"description\": string (≤ 300 chars),\n \"look\": { \"parts\": { \"<part type>\": \"<part id>\" | null, … }, \"colors\": { \"<colour target>\": \"#rrggbb\", … } },\n \"art\": { \"<picture path>\": { \"svg\": \"<svg markup without the svg wrapper>\" }, … } (optional) }\n\n- Pick parts ONLY from the lists below; null takes a part off where it is allowed. Choose colours that make the hero instantly readable.\n- \"art\" redraws a picture of a part you picked with your own SVG of exactly that canvas (width × height in px, y down): only <path>, <circle>, <ellipse>, <rect>, <line>, <polygon>, <polyline>, <g>, gradients and clipPath — no text, no images, no links, no scripts; ≤ 12000 characters each, ≤ 16 pictures. Draw in the same style: a heavy dark outline (#1f1a21, width 3–4), flat fills with one darker shade. Use it only when no part fits the idea (a chef's apron, a superhero suit).\n\nParts and pictures of the rig:\nBack (or null): back/1, back/2, back/3, back/4, back/5, back/6, back/7, back/8, back/9, back/10, back/11, back/12, back/13, back/14, back/15, back/16, back/17, back/18, back/19, back/20, back/21, back/22, back/23, back/24, back/25, back/26, back/27, back/28, back/29, back/30, back/knight_cape, back/mage_cape\nBeard (or null): beard/1, beard/2, beard/3, beard/4, beard/5, beard/6, beard/7, beard/8, beard/9, beard/10\nBoots (or null): boots/1, boots/2, boots/3, boots/4, boots/5, boots/6, boots/7, boots/8, boots/9, boots/10, boots/11, boots/12, boots/13, boots/14, boots/15, boots/16, boots/17, boots/18, boots/19, boots/20, boots/21, boots/22, boots/23, boots/24, boots/25, boots/26, boots/27, boots/28, boots/29, boots/30, boots/plate_boots, boots/leather_boots\nPants (or null): pants/1, pants/2, pants/3, pants/4, pants/5, pants/6, pants/7, pants/8, pants/9, pants/10, pants/11, pants/12, pants/13, pants/14, pants/15, pants/16, pants/17, pants/18, pants/19, pants/20, pants/21, pants/22, pants/23, pants/24, pants/25, pants/26, pants/27, pants/28, pants/29, pants/30, pants/31, pants/32, pants/33, pants/34, pants/35, pants/36, pants/37, pants/38, pants/39, pants/40, pants/41, pants/42, pants/43, pants/44, pants/45, pants/46, pants/47, pants/48, pants/49, pants/50\nBrows: brows/1, brows/2, brows/3, brows/4, brows/5, brows/6, brows/7, brows/8, brows/9, brows/10\nEyes: eyes/1, eyes/2, eyes/3, eyes/4, eyes/5, eyes/6, eyes/7, eyes/8, eyes/9, eyes/10, eyes/11, eyes/12, eyes/13, eyes/14, eyes/15, eyes/16, eyes/17, eyes/18, eyes/19, eyes/20\nGloves (or null): gloves/1, gloves/2, gloves/3, gloves/4, gloves/5, gloves/6, gloves/7, gloves/8, gloves/9, gloves/10, gloves/11, gloves/12, gloves/13, gloves/14, gloves/15, gloves/16, gloves/17, gloves/18, gloves/19, gloves/20, gloves/21, gloves/22, gloves/23, gloves/24, gloves/25, gloves/26, gloves/27, gloves/28, gloves/29, gloves/30, gloves/gauntlets, gloves/bracers\nHair (or null): hair/1, hair/2, hair/3, hair/4, hair/5, hair/6, hair/7, hair/8, hair/9, hair/10, hair/11, hair/12, hair/13, hair/14, hair/15, hair/16, hair/17, hair/18, hair/19, hair/20, hair/21, hair/22, hair/23, hair/24, hair/25, hair/26, hair/27, hair/28, hair/29, hair/30\nHat (or null): hat/1, hat/2, hat/3, hat/4, hat/5, hat/6, hat/7, hat/8, hat/9, hat/10, hat/11, hat/12, hat/13, hat/14, hat/15, hat/16, hat/17, hat/18, hat/19, hat/20, hat/21, hat/22, hat/23, hat/24, hat/25, hat/26, hat/27, hat/28, hat/29, hat/30, hat/31, hat/32, hat/33, hat/34, hat/35, hat/36, hat/37, hat/38, hat/39, hat/40, hat/41, hat/42, hat/43, hat/44, hat/45, hat/46, hat/47, hat/48, hat/49, hat/50, hat/51, hat/52, hat/53, hat/54, hat/55, hat/56, hat/57, hat/58, hat/59, hat/60, hat/great_helm, hat/knight_helm, hat/winged_helm, hat/wizard_hat, hat/ranger_hood, hat/iron_helm\nMouth: mouth/1, mouth/2, mouth/3, mouth/4, mouth/5, mouth/6, mouth/7, mouth/8, mouth/9, mouth/10\nGlasses (or null): glasses/1, glasses/2, glasses/3, glasses/4, glasses/5, glasses/6, glasses/7, glasses/8, glasses/9, glasses/10, glasses/11, glasses/12, glasses/13, glasses/14, glasses/15, glasses/16, glasses/17, glasses/18, glasses/19, glasses/20, glasses/21, glasses/22, glasses/23, glasses/24, glasses/25, glasses/26, glasses/27, glasses/28, glasses/29, glasses/30\nOffhand (or null): offhand/1, offhand/2, offhand/3, offhand/4, offhand/5, offhand/6, offhand/7, offhand/8, offhand/9, offhand/10, offhand/11, offhand/12, offhand/13, offhand/14, offhand/15, offhand/16, offhand/17, offhand/18, offhand/19, offhand/20, offhand/21, offhand/22, offhand/23, offhand/24, offhand/25, offhand/26, offhand/27, offhand/28, offhand/29, offhand/30, offhand/31, offhand/32, offhand/33, offhand/34, offhand/35, offhand/36, offhand/37, offhand/38, offhand/39, offhand/40, offhand/kite_shield, offhand/heater_shield, offhand/round_shield, offhand/tower_shield, offhand/buckler\nWeapon (or null): weapon/1, weapon/2, weapon/3, weapon/4, weapon/5, weapon/6, weapon/7, weapon/10, weapon/11, weapon/12, weapon/13, weapon/14, weapon/15, weapon/16, weapon/17, weapon/18, weapon/19, weapon/20, weapon/21, weapon/22, weapon/23, weapon/24, weapon/25, weapon/26, weapon/27, weapon/28, weapon/29, weapon/30, weapon/31, weapon/32, weapon/33, weapon/34, weapon/35, weapon/36, weapon/37, weapon/38, weapon/39, weapon/40, weapon/knight_sword, weapon/dark_blade, weapon/broadsword, weapon/flame_sword, weapon/dagger, weapon/battle_axe, weapon/double_axe, weapon/mace, weapon/warhammer, weapon/spear, weapon/wizard_staff, weapon/druid_staff, weapon/hunting_bow, weapon/elven_bow, weapon/pistol, weapon/crossbow, weapon/musket, weapon/rifle\nTop: top/1, top/2, top/3, top/4, top/5, top/6, top/7, top/8, top/9, top/10, top/11, top/12, top/13, top/14, top/15, top/16, top/17, top/18, top/19, top/20, top/21, top/22, top/23, top/24, top/25, top/26, top/27, top/28, top/29, top/30, top/31, top/32, top/33, top/34, top/35, top/36, top/37, top/38, top/39, top/40, top/41, top/42, top/43, top/44, top/45, top/46, top/47, top/48, top/49, top/50, top/51, top/52, top/53, top/54, top/55, top/56, top/57, top/58, top/59, top/60, top/plate_armor, top/chainmail, top/leather_armor, top/mage_robe\nSkin: skin/1\ncolors: Skin, Hair, Beard, Brows, Eyes (#rrggbb)\npictures (path WxH, y down): back/1 84x76; back/2 75x74; back/3 71x71; back/4 74x83; back/5 81x95; back/6 76x88; back/7 79x78; back/8 55x109; back/9 85x100; back/10 63x94; back/11 105x99; back/12 73x98; back/13 92x93; back/14 76x75; back/15 95x124; back/16 79x86; back/17 76x83; back/18 96x90; back/19 102x97; back/20 65x87; back/21 84x76; back/22 71x71; back/23 75x74; back/24 95x124; back/25 96x90; back/26 65x87; back/27 73x98; back/28 76x83; back/29 55x109; back/30 79x86; beard/1 43x27; beard/2 42x39; beard/3 89x30; beard/4 85x46; beard/5 73x44; beard/6 39x42; beard/7 54x20; beard/8 80x18; beard/9 29x25; beard/10 88x44; boots/1_near 31x20; boots/1_far 31x20; boots/2_near 30x23; boots/2_far 30x23; boots/3_near 32x23; boots/3_far 32x23; boots/4_near 32x22; boots/4_far 32x22; boots/5_near 26x30; boots/5_far 26x30; boots/6_near 30x18; boots/6_far 30x18; boots/7_near 33x25; boots/7_far 33x25; boots/8_near 32x20; boots/8_far 32x20; boots/9_near 32x20; boots/9_far 32x20; boots/10_near 35x19; boots/10_far 35x19; boots/11_near 32x26; boots/11_far 32x26; boots/12_near 30x22; boots/12_far 30x22; boots/13_near 28x23; boots/13_far 28x23; boots/14_near 31x17; boots/14_far 31x17; boots/15_near 28x28; boots/15_far 28x28; boots/16_near 30x25; boots/16_far 30x25; boots/17_near 30x24; boots/17_far 30x24; boots/18_near 31x18; boots/18_far 31x18; boots/19_near 28x22; boots/19_far 28x22; boots/20_near 35x24; boots/20_far 35x24; boots/21_near 31x20; boots/21_far 31x20; boots/22_near 28x22; boots/22_far 28x22; boots/23_near 28x22; boots/23_far 28x22; boots/24_near 28x28; boots/24_far 28x28; boots/25_near 28x28; boots/25_far 28x28; boots/26_near 30x24; boots/26_far 30x24; boots/27_near 26x30; boots/27_far 26x30; boots/28_near 26x30; boots/28_far 26x30; boots/29_near 30x23; boots/29_far 30x23; boots/30_near 32x20; boots/30_far 32x20; pants/1 79x26; pants/1_near 25x28; pants/1_far 25x28; pants/2 79x26; pants/2_near 25x28; pants/2_far 25x28; pants/3 79x26; pants/3_near 25x41; pants/3_far 25x41; pants/4 79x26; pants/4_near 31x28; pants/4_far 31x28; pants/5 79x26; pants/5_near 36x42; pants/5_far 38x43; pants/6 92x40; pants/7 92x40; pants/8 93x46; pants/9 92x41; pants/10 92x40; pants/11 88x40; pants/12 79x26; pants/13 79x26; pants/13_near 25x41; pants/13_far 25x41; pants/14 79x26; pants/14_near 36x42; pants/14_far 38x43; pants/15 79x26; pants/15_near 25x41; pants/15_far 25x41; pants/16 79x26; pants/16_near 25x28; pants/16_far 25x40; pants/17 92x39; pants/17_near 25x41; pants/17_far 25x41; pants/18 96x36; pants/19 79x26; pants/19_near 25x41; pants/19_far 25x41; pants/20 79x26; pants/20_near 25x28; pants/20_far 25x28; pants/21 79x26; pants/21_near 25x41; pants/21_far 25x41; pants/22 79x26; pants/22_near 25x41; pants/22_far 25x41; pants/23 79x26; pants/23_near 25x41; pants/23_far 25x41; pants/24 79x26; pants/24_near 25x41; pants/24_far 25x41; pants/25 79x26; pants/25_near 25x41; pants/25_far 25x41; pants/26 82x37; pants/27 79x26; pants/27_near 25x41; pants/27_far 25x41; pants/28 79x26; pants/28_near 25x28; pants/28_far 25x28; pants/29 79x26; pants/29_near 25x28; pants/29_far 25x40; pants/30 79x26; pants/30_near 25x41; pants/30_far 25x41; pants/31 79x26; pants/31_near 25x41; pants/31_far 25x41; pants/32 79x26; pants/32_near 31x28; pants/32_far 31x28; pants/33 79x26; pants/33_near 31x28; pants/33_far 31x28; pants/34 79x26; pants/34_near 31x28; pants/34_far 31x28; pants/35 79x26; pants/35_near 31x28; pants/35_far 31x28; pants/36 92x39; pants/37 79x26; pants/37_near 25x41; pants/37_far 25x41; pants/38 79x26; pants/38_near 25x28; pants/38_far 25x28; pants/39 79x26; pants/39_near 25x28; pants/39_far 25x28; pants/40 79x26; pants/40_near 25x41; pants/40_far 25x41; pants/41 79x26; pants/41_near 25x41; pants/41_far 25x41; pants/42 82x37; pants/43 82x37; pants/44 88x40; pants/45 79x26; pants/45_near 36x42; pants/45_far 38x43; pants/46 79x26; pants/46_near 36x42; pants/46_far 38x43; pants/47 96x36; pants/47_near 25x41; pants/47_far 25x41; pants/48 96x38; pants/49 79x26; pants/50 79x26; pants/50_near 31x28; pants/50_far 31x28; brows/1 51x11; brows/2 65x18; brows/3 61x16; brows/4 66x19; brows/5 72x23; brows/6 44x10; brows/7 62x18; brows/8 57x18; brows/9 53x13; brows/10 17x23; eyes/1 61x20; eyes/2 63x22; eyes/3 66x30; eyes/4 64x31; eyes/5 64x24; eyes/6 63x28; eyes/7 69x29; eyes/8 44x22; eyes/9 48x20; eyes/10 61x23; eyes/11 60x21; eyes/12 65x27; eyes/13 59x21; eyes/14 62x22; eyes/15 63x23; eyes/16 59x26; eyes/17 55x22; eyes/18 57x6; eyes/19 59x23; eyes/20 59x26; glasses/1 85x48; glasses/2 71x30; glasses/3 90x38; glasses/4 96x40; glasses/5 80x44; glasses/6 91x41; glasses/7 80x35; glasses/8 58x27; glasses/9 84x47; glasses/10 81x58; glasses/11 72x21; glasses/12 83x36; glasses/13 81x24; glasses/14 72x37; glasses/15 83x44; glasses/16 86x39; glasses/17 89x58; glasses/18 74x39; glasses/19 89x37; glasses/20 90x54; glasses/21 79x40; glasses/22 71x32; glasses/23 82x38; glasses/24 77x34; glasses/25 73x34; glasses/26 70x33; glasses/27 74x64; glasses/28 86x77; glasses/29 72x41; glasses/30 71x51; offhand/1 101x83; offhand/2 79x87; offhand/3 87x87; offhand/4 96x81; offhand/5 109x87; offhand/6 73x73; offhand/7 91x70; offhand/8 89x89; offhand/9 95x70; offhand/10 85x82; offhand/11_low 56x56; offhand/12_low 56x56; offhand/13_low 65x64; offhand/14_low 61x46; offhand/15_low 78x36; offhand/16_low 65x30; offhand/17_low 55x44; offhand/18_low 37x37; offhand/19_low 46x43; offhand/20_low 43x41; offhand/21_low 56x44; offhand/22_low 56x66; offhand/23_low 49x72; offhand/24_low 56x55; offhand/25_low 77x51; offhand/26_low 94x50; offhand/27_low 78x59; offhand/28_low 63x35; offhand/29_low 78x35; offhand/30_low 46x59; offhand/31_low 53x44; offhand/32_low 57x54; offhand/33_low 65x29; offhand/34_low 92x34; offhand/35_low 54x48; offhand/36_low 83x31; offhand/37_low 51x42; offhand/38_low 65x32; offhand/39_low 53x37; offhand/40_low 53x37; weapon/1 111x77; weapon/2 112x92; weapon/3 115x60; weapon/4 96x78; weapon/5 144x71; weapon/6 135x43; weapon/7 141x40; weapon/10 100x66; weapon/11 101x87; weapon/12 124x58; weapon/13 144x33; weapon/14 131x45; weapon/15 143x60; weapon/16 142x44; weapon/17 133x35; weapon/18 103x65; weapon/19 132x39; weapon/20 133x33; weapon/21 112x29; weapon/22 116x45; weapon/23 88x58; weapon/24 137x28; weapon/25 153x43; weapon/26 160x50; weapon/27 156x51; weapon/28 174x47; weapon/29 160x65; weapon/30 147x40; weapon/31 112x92; weapon/32 115x59; weapon/33 157x51; weapon/34 143x44; weapon/35 143x44; weapon/36 175x47; weapon/37 124x58; weapon/38 135x43; weapon/39 143x60; weapon/40 87x58; gloves/1_near 28x25; gloves/1_far 27x25; gloves/2_near 34x35; gloves/2_far 32x35; gloves/3_near 28x25; gloves/3_far 27x25; gloves/4_near 31x34; gloves/4_far 32x33; gloves/5_near 36x36; gloves/5_far 35x36; gloves/6_near 31x32; gloves/6_far 32x33; gloves/7_near 35x34; gloves/7_far 34x34; gloves/8_near 30x14; gloves/8_far 28x14; gloves/9_near 38x18; gloves/9_far 38x18; gloves/10_near 34x35; gloves/10_far 32x35; gloves/11_near 30x23; gloves/11_far 28x23; gloves/12_near 33x35; gloves/12_far 33x36; gloves/13_near 36x36; gloves/13_far 32x37; gloves/14_near 32x27; gloves/14_far 29x24; gloves/15_near 36x40; gloves/15_far 35x40; gloves/16_near 31x36; gloves/16_far 30x37; gloves/17_near 31x36; gloves/17_far 30x37; gloves/18_near 32x31; gloves/18_far 30x30; gloves/19_near 32x34; gloves/19_far 29x34; gloves/20_near 34x38; gloves/20_far 31x38; gloves/21_near 28x25; gloves/21_far 27x25; gloves/22_near 28x25; gloves/22_far 27x25; gloves/23_near 36x40; gloves/23_far 35x40; gloves/24_near 36x40; gloves/24_far 35x40; gloves/25_near 36x36; gloves/25_far 32x37; gloves/26_near 30x14; gloves/26_far 28x14; gloves/27_near 38x18; gloves/27_far 38x18; gloves/28_near 38x18; gloves/28_far 38x18; gloves/29_near 31x34; gloves/29_far 32x33; gloves/30_near 32x27; gloves/30_far 29x24; hair_under/1 87x54; hair_under/2 111x60; hair_under/3 97x54; hair_under/4 103x55; hair_under/5 101x64; hair_under/6 96x54; hair_under/7 92x52; hair_under/8 123x75; hair_under/9 105x78; hair_under/10 130x72; hair_under/11 97x87; hair_under/12 126x64; hair_under/13 104x40; hair_under/14 96x55; hair_under/15 92x49; hair_under/16 95x55; hair_under/17 102x57; hair_under/18 118x69; hair_under/19 118x88; hair_under/20 96x52; hair_under/21 101x60; hair_under/22 90x53; hair_under/23 94x53; hair_under/24 122x66; hair_under/25 118x67; hair_under/26 98x55; hair_under/27 103x52; hair_under/28 122x79; hair_under/29 99x90; hair_under/30 116x96; hair/1 87x54; hair/2 113x61; hair/3 98x65; hair/4 119x94; hair/5 108x75; hair/6 115x70; hair/7 97x67; hair/8 147x120; hair/9 105x113; hair/10 136x84; hair/11 133x113; hair/12 134x74; hair/13 105x40; hair/14 114x80; hair/15 96x57; hair/16 97x86; hair/17 105x63; hair/18 128x83; hair/19 128x107; hair/20 97x59; hair/20_back 67x91; hair/21 101x65; hair/21_back 96x105; hair/22 114x71; hair/23 124x84; hair/24 127x74; hair/25 135x102; hair/26 103x68; hair/27 103x53; hair/28 125x86; hair/28_back 97x102; hair/29 99x111; hair/29_back 79x112; hair/30 119x114; hat/1 120x110; hat/1_back 95x59; hat/2 154x75; hat/3_low 126x82; hat/4 122x78; hat/5 118x120; hat/6 130x82; hat/7 121x105; hat/7_back 33x53; hat/8_low 136x75; hat/9_low 117x93; hat/10_low 140x94; hat/11_low 114x115; hat/12_low 145x69; hat/13_low 117x131; hat/14_low 140x120; hat/15 145x102; hat/16 129x97; hat/16_back 36x56; hat/17_low 126x84; hat/18 142x93; hat/19_low 122x106; hat/20_low 106x71; hat/20_back 68x44; hat/21_low 117x75; hat/22_low 123x94; hat/22_back 37x46; hat/23 137x110; hat/23_back 134x64; hat/24 133x81; hat/25 126x100; hat/25_back 43x60; hat/26 108x70; hat/26_back 46x52; hat/27_low 133x83; hat/28 126x107; hat/28_back 112x56; hat/29_low 124x117; hat/30_low 117x78; hat/31_low 136x75; hat/32 120x110; hat/32_back 95x59; hat/33_low 145x69; hat/34_low 106x71; hat/34_back 68x44; hat/35_low 106x71; hat/35_back 68x44; hat/36 154x75; hat/37 133x81; hat/38 133x81; hat/39 118x120; hat/40_low 117x75; hat/41_low 117x75; hat/42_low 117x93; hat/43_low 123x94; hat/43_back 37x46; hat/44_low 133x79; hat/45_low 114x115; hat/46_low 114x115; hat/47_low 140x94; hat/48 122x78; hat/49_low 107x70; hat/49_back 46x52; hat/50_low 124x117; hat/51_low 117x131; hat/52 126x107; hat/52_back 112x56; hat/53_low 140x120; hat/54_low 140x120; hat/55 137x110; hat/55_back 134x64; hat/56 137x110; hat/56_back 134x64; hat/57 130x82; hat/58 129x97; hat/58_back 36x56; hat/59 121x105; hat/59_back 33x53; hat/60 145x102; mouth/1 15x7; mouth/2 27x19; mouth/3 23x12; mouth/4 24x11; mouth/5 33x16; mouth/6 20x18; mouth/7 20x19; mouth/8 23x13; mouth/9 24x19; mouth/10 23x15; skin/1_arm_near 23x72; skin/1_arm_far 29x71; skin/1_torso 77x50; skin/1_head 103x90; skin/1_leg 27x55; race_skin/elf_head 103x90; race_skin/dwarf_head 103x90; race_skin/orc_head 103x90; race_skin/undead_head 103x90; ears/elf 103x90; ears/orc 103x90; tusks/1 28x18; tusks/2 28x18; top/1 87x71; top/2 91x88; top/2_sleeve_near 37x34; top/2_sleeve_far 34x35; top/3 90x88; top/3_sleeve_near 31x64; top/3_sleeve_far 32x63; top/4 87x88; top/4_sleeve_near 37x34; top/4_sleeve_far 34x35; top/5 87x81; top/5_sleeve_near 37x34; top/5_sleeve_far 34x35; top/6 88x82; top/6_sleeve_near 29x37; top/6_sleeve_far 29x38; top/7 87x83; top/7_sleeve_near 29x37; top/7_sleeve_far 29x38; top/8 87x80; top/9 87x81; top/9_sleeve_near 28x58; top/9_cap_near 37x34; top/9_sleeve_far 34x59; top/9_cap_far 34x36; top/10 56x59; top/10_coat 88x106; top/10_sleeve_near 28x58; top/10_sleeve_far 32x59; top/11 87x72; top/11_sleeve_near 37x34; top/11_sleeve_far 34x35; top/12 86x81; top/12_sleeve_near 28x58; top/12_sleeve_far 32x59; top/13 88x82; top/13_sleeve_near 28x58; top/13_sleeve_far 32x59; top/14 87x81; top/15 90x88; top/15_sleeve_near 31x64; top/15_sleeve_far 32x63; top/16 86x84; top/16_sleeve_near 31x59; top/16_sleeve_far 34x59; top/17 87x83; top/17_sleeve_near 34x37; top/17_sleeve_far 33x36; top/18 89x74; top/18_sleeve_near 37x34; top/18_sleeve_far 34x35; top/19 89x92; top/19_sleeve_near 37x34; top/19_sleeve_far 34x35; top/20 87x71; top/20_sleeve_near 28x58; top/20_sleeve_far 32x59; top/21 87x71; top/21_sleeve_near 28x58; top/21_sleeve_far 32x59; top/22 87x81; top/22_sleeve_near 28x58; top/22_sleeve_far 32x59; top/23 87x81; top/23_sleeve_near 28x58; top/23_sleeve_far 32x59; top/24 87x81; top/24_sleeve_near 28x58; top/24_sleeve_far 32x59; top/25 88x72; top/25_sleeve_near 28x58; top/25_sleeve_far 32x59; top/26 92x93; top/27 87x82; top/27_sleeve_near 28x58; top/27_sleeve_far 32x59; top/28 86x74; top/29 63x65; top/29_coat 88x106; top/29_sleeve_near 28x58; top/29_sleeve_far 32x59; top/30 87x80; top/30_sleeve_near 28x58; top/30_sleeve_far 32x59; top/31 90x88; top/31_sleeve_near 31x64; top/31_sleeve_far 32x63; top/32 88x82; top/32_sleeve_near 28x58; top/32_sleeve_far 32x59; top/33 87x81; top/33_sleeve_near 37x34; top/33_sleeve_far 34x35; top/34 87x81; top/34_sleeve_near 37x34; top/34_sleeve_far 34x35; top/35 87x81; top/36 87x71; top/36_sleeve_near 28x58; top/36_sleeve_far 32x59; top/37 87x81; top/37_sleeve_near 29x37; top/37_sleeve_far 29x38; top/38 87x82; top/38_sleeve_near 28x58; top/38_sleeve_far 32x59; top/39 87x83; top/39_sleeve_near 34x37; top/39_sleeve_far 33x36; top/40 90x88; top/40_sleeve_near 31x64; top/40_sleeve_far 32x63; top/41 91x87; top/41_sleeve_near 31x64; top/41_sleeve_far 32x63; top/42 87x81; top/42_sleeve_near 28x58; top/42_sleeve_far 32x59; top/43 87x79; top/43_sleeve_near 29x37; top/43_sleeve_far 29x38; top/44 87x72; top/44_sleeve_near 31x64; top/44_sleeve_far 32x63; top/45 87x88; top/45_sleeve_near 28x58; top/45_sleeve_far 32x59; top/46 87x72; top/46_sleeve_near 28x58; top/46_cap_near 37x34; top/46_sleeve_far 32x59; top/46_cap_far 34x35; top/47 88x82; top/47_sleeve_near 29x37; top/47_sleeve_far 29x38; top/48 87x71; top/48_sleeve_near 28x58; top/48_sleeve_far 32x59; top/49 86x81; top/49_sleeve_near 28x58; top/49_sleeve_far 32x59; top/50 89x74; top/50_sleeve_near 37x34; top/50_sleeve_far 34x35; top/51 87x72; top/51_sleeve_near 31x64; top/51_sleeve_far 32x63; top/52 87x72; top/52_sleeve_near 29x37; top/52_sleeve_far 29x38; top/53 87x72; top/53_sleeve_near 29x37; top/53_sleeve_far 29x38; top/54 89x92; top/54_sleeve_near 37x34; top/54_sleeve_far 34x35; top/55 87x81; top/56 86x84; top/56_sleeve_near 31x59; top/56_sleeve_far 34x59; top/57 87x80; top/57_sleeve_near 28x58; top/57_sleeve_far 32x59; top/58 92x93; top/59 90x88; top/59_sleeve_near 31x64; top/59_sleeve_far 32x63; top/60 87x80; weapon/knight_sword 156x51; weapon/dark_blade 160x50; weapon/broadsword 160x65; weapon/flame_sword 174x47; weapon/dagger 112x29; weapon/battle_axe 115x60; weapon/double_axe 112x92; weapon/mace 144x71; weapon/warhammer 103x65; weapon/spear 147x40; weapon/wizard_staff 135x43; weapon/druid_staff 144x33; offhand/kite_shield 85x82; offhand/heater_shield 96x81; offhand/round_shield 89x89; offhand/tower_shield 109x87; offhand/buckler 73x73; hat/great_helm 118x120; hat/knight_helm_low 114x115; hat/winged_helm_low 133x83; hat/wizard_hat 154x75; hat/ranger_hood_low 114x115; hat/iron_helm 130x82; top/plate_armor 87x88; top/plate_armor_sleeve_near 37x34; top/plate_armor_sleeve_far 34x35; top/chainmail 91x88; top/chainmail_sleeve_near 37x34; top/chainmail_sleeve_far 34x35; top/leather_armor 89x92; top/leather_armor_sleeve_near 37x34; top/leather_armor_sleeve_far 34x35; top/mage_robe 89x74; top/mage_robe_sleeve_near 37x34; top/mage_robe_sleeve_far 34x35; gloves/gauntlets_near 34x35; gloves/gauntlets_far 32x35; gloves/bracers_near 36x40; gloves/bracers_far 35x40; boots/plate_boots_near 30x23; boots/plate_boots_far 30x23; boots/leather_boots_near 26x30; boots/leather_boots_far 26x30; back/knight_cape 95x124; back/mage_cape 95x124; weapon/hunting_bow 70x112; weapon/hunting_bow_drawn 70x112; weapon/hunting_bow_shot 70x112; weapon/elven_bow 72x128; weapon/elven_bow_drawn 72x128; weapon/elven_bow_shot 72x128; weapon/pistol 96x40; weapon/pistol_shot 96x40; weapon/crossbow 104x56; weapon/crossbow_shot 104x56; weapon/musket 180x40; weapon/musket_shot 180x40; weapon/rifle 160x52; weapon/rifle_shot 160x52",
|
|
9664
9664
|
"style": "chibi-2d",
|
|
9665
9665
|
"asset": {
|
|
9666
9666
|
"kind": "actor",
|