{"_id":"@alife-sdk/economy","_rev":"5-63c01181a72f62b77f8a1e775cb2e0be","name":"@alife-sdk/economy","dist-tags":{"latest":"0.4.1"},"versions":{"0.1.0":{"name":"@alife-sdk/economy","version":"0.1.0","keywords":["alife","game-ai","economy","trade","inventory","quest"],"author":{"name":"Eugene Rusakov"},"license":"MIT","_id":"@alife-sdk/economy@0.1.0","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"dist":{"shasum":"4adc914d9a14d72568a6364524e8730cbb159d0c","tarball":"https://registry.npmjs.org/@alife-sdk/economy/-/economy-0.1.0.tgz","fileCount":119,"integrity":"sha512-hp4s4SjyIJ4eKA2+I+glOQYWZUKYQOKAp0kFXTSNOs02BKnOXuMZUGxBV7Fwr+FfaMfgATABYu7LsgtHUHGrtw==","signatures":[{"sig":"MEUCIQDygNC9UBev3DHBDxLA7puz+QxZYMbN7t7cBfBU9WvJXwIgGGnTAZwBu+cmSOR/Gsz3xiflNRv84x3l3/zpLCvqeu8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":286168},"main":"dist/index.js","type":"module","_from":"file:alife-sdk-economy-0.1.0.tgz","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./quest":{"types":"./dist/quest/index.d.ts","import":"./dist/quest/index.js"},"./trade":{"types":"./dist/trade/index.d.ts","import":"./dist/trade/index.js"},"./types":{"types":"./dist/types/index.d.ts","import":"./dist/types/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"},"./inventory":{"types":"./dist/inventory/index.d.ts","import":"./dist/inventory/index.js"}},"scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"_resolved":"/private/var/folders/jv/cpf6x8mj1g11mppj8rpdhlmr0000gn/T/2e3bbe7ddc2ba09758a2a7c81033edb0/alife-sdk-economy-0.1.0.tgz","_integrity":"sha512-hp4s4SjyIJ4eKA2+I+glOQYWZUKYQOKAp0kFXTSNOs02BKnOXuMZUGxBV7Fwr+FfaMfgATABYu7LsgtHUHGrtw==","repository":{"url":"git+https://github.com/eurusik/alife-sdk.git","type":"git","directory":"packages/alife-economy"},"_npmVersion":"10.8.1","description":"Trade, inventory, and quest systems for A-Life SDK","directories":{},"sideEffects":false,"_nodeVersion":"20.16.0","dependencies":{"@alife-sdk/core":"0.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/economy_0.1.0_1772739947650_0.6301854993432678","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@alife-sdk/economy","version":"0.1.1","keywords":["alife","game-ai","economy","trade","inventory","quest"],"author":{"name":"Eugene Rusakov"},"license":"MIT","_id":"@alife-sdk/economy@0.1.1","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"dist":{"shasum":"ac1d301396adda32f0b31f7ab383a8ff44216a97","tarball":"https://registry.npmjs.org/@alife-sdk/economy/-/economy-0.1.1.tgz","fileCount":83,"integrity":"sha512-eDAbGHDmDtsZdt3VhJLQGx7sGAJFP8TAObIegLFqhO2ddj9v2bZtI06IfhAfrHs1eL3XbzZ6CEM/MaSPei+W5w==","signatures":[{"sig":"MEQCIFK1tsPLsVAt9EhMw2z/Xjz2tDR9raPX1Ojjtl+pfUFMAiAuHNReehhyeJhcbtvswjwwN6DK+ByInPtlgEVYs3qFqA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":172352},"main":"dist/index.js","type":"module","_from":"file:alife-sdk-economy-0.1.1.tgz","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./quest":{"types":"./dist/quest/index.d.ts","import":"./dist/quest/index.js"},"./trade":{"types":"./dist/trade/index.d.ts","import":"./dist/trade/index.js"},"./types":{"types":"./dist/types/index.d.ts","import":"./dist/types/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"},"./inventory":{"types":"./dist/inventory/index.d.ts","import":"./dist/inventory/index.js"}},"scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"_resolved":"/tmp/9a681a1b8249ae3d04a10155b2ad0d34/alife-sdk-economy-0.1.1.tgz","_integrity":"sha512-eDAbGHDmDtsZdt3VhJLQGx7sGAJFP8TAObIegLFqhO2ddj9v2bZtI06IfhAfrHs1eL3XbzZ6CEM/MaSPei+W5w==","repository":{"url":"git+https://github.com/eurusik/alife-sdk.git","type":"git","directory":"packages/alife-economy"},"_npmVersion":"10.8.2","description":"Trade, inventory, and quest systems for A-Life SDK","directories":{},"sideEffects":false,"_nodeVersion":"20.20.0","dependencies":{"@alife-sdk/core":"0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/economy_0.1.1_1772742735470_0.27962206164766057","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@alife-sdk/economy","version":"0.3.0","keywords":["alife","game-ai","economy","trade","inventory","quest"],"author":{"name":"Eugene Rusakov"},"license":"MIT","_id":"@alife-sdk/economy@0.3.0","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"dist":{"shasum":"46b8580f8e9b46a7150ac40aad6a1ac24440c618","tarball":"https://registry.npmjs.org/@alife-sdk/economy/-/economy-0.3.0.tgz","fileCount":83,"integrity":"sha512-6tSC85i8lFQMVuWC8WWn6mhGZcjMk3uKw9ixY1DWZPkEgyBRkZ2SisnbLr0r55/OSejQPGrSaIsebNi8cmlRJw==","signatures":[{"sig":"MEUCIAnEDEy1pUhaGU8pAY1jfFL7wbxSuIbnS7s7iwdC2VFnAiEA17vlfdI9CaqqhPn8f+j641QCaQxOL7V9h9FHk2JLe2M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":172352},"main":"dist/index.js","type":"module","_from":"file:alife-sdk-economy-0.3.0.tgz","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./quest":{"types":"./dist/quest/index.d.ts","import":"./dist/quest/index.js"},"./trade":{"types":"./dist/trade/index.d.ts","import":"./dist/trade/index.js"},"./types":{"types":"./dist/types/index.d.ts","import":"./dist/types/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"},"./inventory":{"types":"./dist/inventory/index.d.ts","import":"./dist/inventory/index.js"}},"scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"_resolved":"/tmp/748e849ff419f0783fb26420691f4b9e/alife-sdk-economy-0.3.0.tgz","_integrity":"sha512-6tSC85i8lFQMVuWC8WWn6mhGZcjMk3uKw9ixY1DWZPkEgyBRkZ2SisnbLr0r55/OSejQPGrSaIsebNi8cmlRJw==","repository":{"url":"git+https://github.com/eurusik/alife-sdk.git","type":"git","directory":"packages/alife-economy"},"_npmVersion":"10.8.2","description":"Trade, inventory, and quest systems for A-Life SDK","directories":{},"sideEffects":false,"_nodeVersion":"20.20.0","dependencies":{"@alife-sdk/core":"0.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/economy_0.3.0_1772789682540_0.8907182073301947","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@alife-sdk/economy","version":"0.4.0","keywords":["alife","game-ai","economy","trade","inventory","quest"],"author":{"name":"Eugene Rusakov"},"license":"MIT","_id":"@alife-sdk/economy@0.4.0","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"dist":{"shasum":"f190ce8a278da3edbf7a6d7866d4cc5dd8a3faa8","tarball":"https://registry.npmjs.org/@alife-sdk/economy/-/economy-0.4.0.tgz","fileCount":83,"integrity":"sha512-7G8pBaJarg+nZYyNVYpoA4OaZm2qRSWqrrihj1Uz2PuGjDiKOae6gbNZQ2Uck4DFLlgAXY/+9gheEkNJcuZR7w==","signatures":[{"sig":"MEUCIQDVX0X2P5PThi4Ww62qlulhkdBVk/i2JNxiy/5Z1pkKRwIgJBN6WPoA+kF/02wfWzhCBzQWoC4umMoZ1YI5yK+H7mg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":172352},"main":"dist/index.js","type":"module","_from":"file:alife-sdk-economy-0.4.0.tgz","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./quest":{"types":"./dist/quest/index.d.ts","import":"./dist/quest/index.js"},"./trade":{"types":"./dist/trade/index.d.ts","import":"./dist/trade/index.js"},"./types":{"types":"./dist/types/index.d.ts","import":"./dist/types/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"},"./inventory":{"types":"./dist/inventory/index.d.ts","import":"./dist/inventory/index.js"}},"scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"_resolved":"/tmp/710c2fd24cd6d5c1d4b90642a823de50/alife-sdk-economy-0.4.0.tgz","_integrity":"sha512-7G8pBaJarg+nZYyNVYpoA4OaZm2qRSWqrrihj1Uz2PuGjDiKOae6gbNZQ2Uck4DFLlgAXY/+9gheEkNJcuZR7w==","repository":{"url":"git+https://github.com/eurusik/alife-sdk.git","type":"git","directory":"packages/alife-economy"},"_npmVersion":"10.8.2","description":"Trade, inventory, and quest systems for A-Life SDK","directories":{},"sideEffects":false,"_nodeVersion":"20.20.0","dependencies":{"@alife-sdk/core":"0.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/economy_0.4.0_1772823546449_0.5458933279494922","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@alife-sdk/economy","version":"0.4.1","description":"Trade, inventory, and quest systems for A-Life SDK","type":"module","sideEffects":false,"main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./types":{"types":"./dist/types/index.d.ts","import":"./dist/types/index.js"},"./inventory":{"types":"./dist/inventory/index.d.ts","import":"./dist/inventory/index.js"},"./trade":{"types":"./dist/trade/index.d.ts","import":"./dist/trade/index.js"},"./quest":{"types":"./dist/quest/index.d.ts","import":"./dist/quest/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"}},"keywords":["alife","game-ai","economy","trade","inventory","quest"],"license":"MIT","author":{"name":"Eugene Rusakov"},"repository":{"type":"git","url":"git+https://github.com/eurusik/alife-sdk.git","directory":"packages/alife-economy"},"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"engines":{"node":">=20.0.0"},"publishConfig":{"access":"public"},"dependencies":{"@alife-sdk/core":"0.4.1"},"devDependencies":{"typescript":"^5.9.3","vitest":"^4.0.18"},"scripts":{"build":"tsc","dev":"tsc --watch","test":"vitest run","test:watch":"vitest"},"_id":"@alife-sdk/economy@0.4.1","_integrity":"sha512-FBo9Oskah/4/9WqQ8MHX+6ESRtP6P8hzjiUChpsDIcSy0kmZEB/RkRYgn4/H04OvPohe3ojmmIRoyQbyacRPhA==","_resolved":"/tmp/37d0432ac9d59a5a429b6dfa8ff6cf39/alife-sdk-economy-0.4.1.tgz","_from":"file:alife-sdk-economy-0.4.1.tgz","_nodeVersion":"20.20.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-FBo9Oskah/4/9WqQ8MHX+6ESRtP6P8hzjiUChpsDIcSy0kmZEB/RkRYgn4/H04OvPohe3ojmmIRoyQbyacRPhA==","shasum":"4f92fe826e94d6be304785e118b81998d0427600","tarball":"https://registry.npmjs.org/@alife-sdk/economy/-/economy-0.4.1.tgz","fileCount":83,"unpackedSize":172951,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEYQWvTJER3orM5pehQnYlKfllX06PZOD18f3z/maOEkAiB5BVOjvm42HMeqFg8VaXaycbC3lz7iMITDfSv3Hl8sHw=="}]},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"directories":{},"maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/economy_0.4.1_1773837144674_0.020922816531284694"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-05T19:45:47.549Z","modified":"2026-03-18T12:32:24.950Z","0.1.0":"2026-03-05T19:45:47.819Z","0.1.1":"2026-03-05T20:32:15.640Z","0.3.0":"2026-03-06T09:34:42.683Z","0.4.0":"2026-03-06T18:59:06.607Z","0.4.1":"2026-03-18T12:32:24.818Z"},"bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"author":{"name":"Eugene Rusakov"},"license":"MIT","homepage":"https://github.com/eurusik/alife-sdk#readme","keywords":["alife","game-ai","economy","trade","inventory","quest"],"repository":{"type":"git","url":"git+https://github.com/eurusik/alife-sdk.git","directory":"packages/alife-economy"},"description":"Trade, inventory, and quest systems for A-Life SDK","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"readme":"# @alife-sdk/economy\n\nTrade, inventory, and quest systems for A-Life SDK.\n\nEngine-agnostic. Depends on [`@alife-sdk/core`](../alife-core/README.md).\n\n```\nnpm install @alife-sdk/economy\n```\n\n---\n\n## What this package does\n\n`@alife-sdk/economy` provides the full player-facing economy layer:\n\n- **Inventory** — slot-based item container with stack limits and overflow handling\n- **Trade** — player ↔ NPC buy/sell, item gifting, pricing with faction discounts\n- **Offline NPC-NPC trade** — background trader-to-trader exchanges via co-location\n- **Quests** — lifecycle FSM with objective tracking, prerequisites, typed events, and terrain effects\n\nThe package is engine-agnostic — it never touches the renderer, UI, or event bus.\nAll integration goes through optional **ports** — typed adapters that are optional registration keys\ndecoupling the economy plugin from your game's terrain, simulation, and item systems.\nRegister them via `kernel.portRegistry.provide()` before `kernel.init()`.\n\n---\n\n## Quick start\n\n> `EconomyPlugin` works standalone or inside `ALifeKernel`.\n> The example below uses the kernel. For standalone usage skip `kernel.use()` and call methods directly.\n\n```ts\nimport { ALifeKernel }              from '@alife-sdk/core';\nimport { SeededRandom }             from '@alife-sdk/core/ports';\nimport { EconomyPlugin }            from '@alife-sdk/economy/plugin';\nimport { TradeResult }              from '@alife-sdk/economy/trade';\n\n// 1. Create and install the plugin\nconst random = new SeededRandom(42);\nconst econ   = new EconomyPlugin(random);\nkernel.use(econ);\nawait kernel.init();\n\n// 2. Stock a trader\necon.traders.register('sid', 'loner', 5000);\necon.traders.addStock('sid', 'medkit',   10);\necon.traders.addStock('sid', 'ammo_9mm', 200);\n\n// 3. Register quests\necon.quests.registerQuest({\n  id: 'q_clear_village',\n  name: 'Clear the Village',\n  objectives: [\n    { id: 'obj_kill', type: 'kill', target: 'bandit', description: 'Kill 5 bandits', count: 5, current: 0, completed: false },\n  ],\n});\n\n// 4. Subscribe to quest events\necon.quests.on('quest:completed', ({ questId }) => {\n  econ.playerInventory.add('reward_artifact', 1);\n});\n\n// 5. Player buys a medkit from the trader\nconst outcome = executeBuy({\n  playerInventory: econ.playerInventory,\n  playerMoney:     2000,\n  traders:         econ.traders,\n  traderId:        'sid',\n  itemId:          'medkit',\n  basePrice:       500,\n  factionRelation: 40,\n  config:          econ.config.trade,\n});\n// outcome.receipt.result === TradeResult.SUCCESS\n\n// 6. Quest progress — call per kill event\necon.quests.startQuest('q_clear_village');\necon.quests.updateObjectiveProgress('q_clear_village', 'obj_kill'); // +1 per kill\n\n// 7. Save / load\nconst save = econ.serialize();\necon.restore(save);\n```\n\n---\n\n## Sub-path imports\n\nEach module has its own import path for optimal tree-shaking:\n\n| Import path | What's inside | Module docs |\n|-------------|--------------|-------------|\n| `@alife-sdk/economy` | Full re-export of all sub-modules | [src/](src/) |\n| `@alife-sdk/economy/plugin` | `EconomyPlugin` — kernel plugin entry point | [plugin/](src/plugin/README.md) |\n| `@alife-sdk/economy/inventory` | `Inventory` — slot-based item container | [inventory/](src/inventory/README.md) |\n| `@alife-sdk/economy/trade` | `TraderInventory`, `executeBuy`, `executeSell`, `executeGift`, `OfflineTradeScheduler` | [trade/](src/trade/README.md) |\n| `@alife-sdk/economy/quest` | `QuestEngine`, `QuestStatus`, `QuestEventMap` | [quest/](src/quest/README.md) |\n| `@alife-sdk/economy/ports` | `EconomyPorts` — optional integration tokens | [ports/](src/ports/README.md) |\n| `@alife-sdk/economy/types` | All interfaces and config types | [types/](src/types/README.md) |\n\n---\n\n## Architecture\n\n```\n                ┌────────────────────────────────────────┐\n                │              ALifeKernel               │\n                │   (from @alife-sdk/core)               │\n                └──────────────┬─────────────────────────┘\n                               │ kernel.use(econ)\n                ┌──────────────▼─────────────────────────┐\n                │            EconomyPlugin               │\n                │                                        │\n                │  playerInventory  ── Inventory         │\n                │  traders  ────────── TraderInventory   │\n                │  quests   ────────── QuestEngine       │\n                │  config   ────────── IEconomyConfig    │\n                └──┬─────────────────────────────────────┘\n                   │ optional ports (register before init)\n       ┌───────────┼──────────────────┐\n       │           │                  │\n  TerrainLock  CoLocation        ItemCatalogue\n  (quest lock)  (NPC-NPC trade)  (offline prices)\n\nTrade functions (stateless, use anywhere):\n  executeBuy / executeSell     player ↔ trader\n  executeGift                  any inventory ↔ any inventory (no money)\n  OfflineTradeScheduler        background NPC-NPC trading\n```\n\n---\n\n## Key concepts\n\n### Inventory — slot-based container\n\n`Inventory` stores items as `(itemId → quantity)` slots with configurable\ncapacity and per-item stack limits. `add()` returns overflow — items that\ndidn't fit. `remove()` is all-or-nothing.\n\n```ts\nconst inv = new Inventory({ maxSlots: 30, defaultMaxStack: 99 });\nconst overflow = inv.add('medkit', 5, 10); // → 0 (all fit, maxStack 10)\ninv.remove('medkit', 2);                   // → true\ninv.has('medkit', 3);                      // → true\n```\n\nSee [`inventory/README.md`](src/inventory/README.md).\n\n### Trade — buy, sell, gift\n\nThree pure functions handle all item movement:\n\n- **`executeBuy`** — player buys 1 item from a trader; validates relation,\n  stock, money, inventory space; returns `ITradeOutcome` with a result code\n- **`executeSell`** — player sells 1 item to a trader; validates relation,\n  player stock, trader funds\n- **`executeGift`** — moves items between any two `Inventory` instances with\n  no money involved; supports partial overflow; use for quest rewards,\n  NPC handoffs, loot containers\n\nAll three are **stateless** — no state is mutated on failure.\n\nSee [`trade/README.md`](src/trade/README.md).\n\n### QuestEngine — lifecycle FSM with events\n\n`QuestEngine` tracks quest state through `AVAILABLE → ACTIVE → COMPLETED / FAILED`.\nKey features:\n\n- **Prerequisites** — `requires: ['q_prev']` gates `startQuest()` until prior quests complete\n- **Typed events** — subscribe with `engine.on('quest:completed', cb)` instead of polling\n- **Terrain effects** — declarative `lock/unlock` actions on `on_start`, `on_complete`, `on_fail`\n- **Open objective types** — any string valid; engine drives all via `completeObjective()` / `updateObjectiveProgress()`\n\n`startQuest()` returns `false` if prerequisites are unmet or the quest is already active (not in `AVAILABLE` status).\n\n```ts\nengine.on('quest:completed', ({ questId }) => giveReward(questId));\nengine.on('objective:progress', ({ current, total }) => ui.setProgress(current / total));\n```\n\nSee [`quest/README.md`](src/quest/README.md).\n\n### Optional ports\n\nRegister adapters before `kernel.init()` to unlock optional features:\n\n| Port | Activates |\n|------|-----------|\n| `EconomyPorts.TerrainLock` | Quest-driven terrain lock/unlock |\n| `EconomyPorts.CoLocationSource` | Offline NPC-NPC trading |\n| `EconomyPorts.ItemCatalogue` | Offline trade price lookup |\n\nAll ports are optional — `EconomyPlugin` works without them.\nSee [`ports/README.md`](src/ports/README.md).\n\n---\n\n## Lifecycle\n\n```\nkernel.use(econ)       ← install plugin\n  ↓\nkernel.init()          ← validate config, wire optional ports\n  ↓\nboot time:\n  traders.register()   ← register NPC traders\n  traders.addStock()   ← set initial stock (also sets restock baseline)\n  quests.registerQuest() ← define all quests\n  ↓\ngame loop:\n  traders.restock(now) ← call periodically (every in-game hour or timer)\n  quests events fire automatically on state transitions\n  ↓\nsave / load:\n  econ.serialize()     ← inventory + traders + quests\n  econ.restore(state)  ← re-register quests first, then restore\n  ↓\nkernel.destroy()       ← clears inventory + traders, releases kernel ref\n```\n\n> **Important**: Call `quests.registerQuest()` for ALL quest definitions BEFORE calling `econ.restore()`.\n> The plugin merges saved progress into registered definitions — quests not registered before restore are silently skipped.\n\n---\n\n## Testing\n\nThe package has **228 tests** (vitest). Run them:\n\n```\npnpm test --filter @alife-sdk/economy\n```\n\nAll subsystems are pure — no kernel needed for unit tests:\n\n```ts\nimport { Inventory }                from '@alife-sdk/economy/inventory';\nimport { executeBuy, TradeResult }  from '@alife-sdk/economy/trade';\nimport { QuestEngine }              from '@alife-sdk/economy/quest';\nimport { createDefaultEconomyConfig } from '@alife-sdk/economy/types';\nimport { SeededRandom }             from '@alife-sdk/core/ports';\n\nconst config  = createDefaultEconomyConfig();\nconst traders = new TraderInventory(config.trade, new SeededRandom(0));\ntraders.register('t1', 'loner', 9999);\ntraders.addStock('t1', 'medkit', 5);\n\nconst outcome = executeBuy({\n  playerInventory: new Inventory(config.inventory),\n  playerMoney: 0,\n  traders, traderId: 't1', itemId: 'medkit',\n  basePrice: 100, factionRelation: 0, config: config.trade,\n});\nexpect(outcome.receipt.result).toBe(TradeResult.INSUFFICIENT_MONEY);\n```\n\n---\n\n## Module map\n\n```\nsrc/\n├── plugin/     EconomyPlugin (IALifePlugin — owns inventory + traders + quests)\n├── inventory/  Inventory (slot container, stack limits, serialize/restore)\n├── trade/      TraderInventory, executeBuy, executeSell, executeGift,\n│               PricingEngine, OfflineTradeScheduler\n├── quest/      QuestEngine (FSM, events, prerequisites, terrain effects)\n├── ports/      EconomyPorts tokens (TerrainLock / CoLocationSource / ItemCatalogue)\n└── types/      IEconomyConfig, ITradeConfig, IInventoryConfig,\n                IQuestDefinition, IQuestObjective, QuestStatus, ObjectiveType, …\n```\n","readmeFilename":"README.md"}