@ai-rpg-engine/starter-template 2.3.6 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MCP Tool Shop
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
- # Starter Template
2
-
3
- Copy this directory to `packages/starter-yourname/` to create a new game.
4
-
5
- ```bash
6
- cp -r templates/starter packages/starter-mygame
7
- ```
8
-
9
- Then edit:
10
- - `package.json` — name and description
11
- - `src/ruleset.ts` — your stats, resources, verbs
12
- - `src/content.ts` — entities and zones
13
- - `src/setup.ts` — wire your modules alongside `buildCombatStack`
14
-
15
- See [Chapter 58 — Create Your Own Starter](../../site/src/content/docs/handbook/58-create-your-own-starter.md) for the full walkthrough.
1
+ # Starter Template
2
+
3
+ Copy this directory to `packages/starter-yourname/` to create a new game.
4
+
5
+ ```bash
6
+ cp -r templates/starter packages/starter-mygame
7
+ ```
8
+
9
+ Then edit:
10
+ - `package.json` — name and description
11
+ - `src/ruleset.ts` — your stats, resources, verbs
12
+ - `src/content.ts` — entities and zones
13
+ - `src/setup.ts` — wire your modules alongside `buildCombatStack`
14
+
15
+ See [Chapter 58 — Create Your Own Starter](../../site/src/content/docs/handbook/58-create-your-own-starter.md) for the full walkthrough.
package/package.json CHANGED
@@ -1,35 +1,36 @@
1
- {
2
- "name": "@ai-rpg-engine/starter-template",
3
- "version": "2.3.6",
4
- "description": "Starter template for ai-rpg-engine — copy, rename, and build your own game",
5
- "type": "module",
6
- "files": [
7
- "src",
8
- "README.md",
9
- "tsconfig.json"
10
- ],
11
- "scripts": {
12
- "build": "tsc",
13
- "test": "vitest run"
14
- },
15
- "dependencies": {
16
- "@ai-rpg-engine/core": "*",
17
- "@ai-rpg-engine/content-schema": "*",
18
- "@ai-rpg-engine/modules": "*"
19
- },
20
- "license": "MIT",
21
- "author": "mcp-tool-shop",
22
- "homepage": "https://mcp-tool-shop-org.github.io/ai-rpg-engine/",
23
- "repository": {
24
- "type": "git",
25
- "url": "https://github.com/mcp-tool-shop-org/ai-rpg-engine.git",
26
- "directory": "templates/starter"
27
- },
28
- "keywords": [
29
- "rpg",
30
- "game-engine",
31
- "starter",
32
- "template",
33
- "ai-rpg-engine"
34
- ]
35
- }
1
+ {
2
+ "name": "@ai-rpg-engine/starter-template",
3
+ "version": "2.5.0",
4
+ "description": "Starter template for ai-rpg-engine — copy, rename, and build your own game",
5
+ "type": "module",
6
+ "files": [
7
+ "src",
8
+ "README.md",
9
+ "LICENSE",
10
+ "tsconfig.json"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "test": "vitest run"
15
+ },
16
+ "dependencies": {
17
+ "@ai-rpg-engine/core": "*",
18
+ "@ai-rpg-engine/content-schema": "*",
19
+ "@ai-rpg-engine/modules": "*"
20
+ },
21
+ "license": "MIT",
22
+ "author": "mcp-tool-shop",
23
+ "homepage": "https://mcp-tool-shop-org.github.io/ai-rpg-engine/",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/mcp-tool-shop-org/ai-rpg-engine.git",
27
+ "directory": "templates/starter"
28
+ },
29
+ "keywords": [
30
+ "rpg",
31
+ "game-engine",
32
+ "starter",
33
+ "template",
34
+ "ai-rpg-engine"
35
+ ]
36
+ }
package/src/content.ts CHANGED
@@ -1,80 +1,86 @@
1
- // Starter content — entities, zones, and manifest
2
- //
3
- // Replace this with your game's content.
4
-
5
- import type { EntityState, ZoneState, GameManifest } from '@ai-rpg-engine/core';
6
-
7
- export const manifest: GameManifest = {
8
- id: 'my-game',
9
- title: 'My Game',
10
- version: '0.1.0',
11
- engineVersion: '0.1.0',
12
- ruleset: 'my-game',
13
- modules: ['traversal-core', 'status-core', 'combat-core'],
14
- contentPacks: ['my-game'],
15
- };
16
-
17
- // ═══════════════════════════════════════════════════════════════════
18
- // PACK METADATA (used by the CLI pack selector)
19
- // ═══════════════════════════════════════════════════════════════════
20
-
21
- export const packMeta = {
22
- id: 'my-game',
23
- name: 'My Game',
24
- description: 'A brief description of your game',
25
- version: '0.1.0',
26
- };
27
-
28
- // ═══════════════════════════════════════════════════════════════════
29
- // PLAYER
30
- // ═══════════════════════════════════════════════════════════════════
31
-
32
- export const player: EntityState = {
33
- id: 'player',
34
- blueprintId: 'player',
35
- type: 'player',
36
- name: 'Hero',
37
- tags: ['player'],
38
- stats: { power: 5, speed: 4, grit: 3 },
39
- resources: { hp: 25, stamina: 10, tension: 0 },
40
- statuses: [],
41
- inventory: [],
42
- zoneId: 'start',
43
- };
44
-
45
- // ═══════════════════════════════════════════════════════════════════
46
- // ENEMIES
47
- // ═══════════════════════════════════════════════════════════════════
48
-
49
- export const enemy: EntityState = {
50
- id: 'grunt',
51
- blueprintId: 'grunt',
52
- type: 'npc',
53
- name: 'Grunt',
54
- tags: ['enemy', 'hostile'],
55
- stats: { power: 3, speed: 3, grit: 2 },
56
- resources: { hp: 12 },
57
- statuses: [],
58
- zoneId: 'danger-zone',
59
- };
60
-
61
- // ═══════════════════════════════════════════════════════════════════
62
- // ZONES
63
- // ═══════════════════════════════════════════════════════════════════
64
-
65
- export const zones: ZoneState[] = [
66
- {
67
- id: 'start',
68
- roomId: 'room-1',
69
- name: 'Starting Area',
70
- tags: ['safe'],
71
- neighbors: ['danger-zone'],
72
- },
73
- {
74
- id: 'danger-zone',
75
- roomId: 'room-2',
76
- name: 'The Danger Zone',
77
- tags: ['hostile'],
78
- neighbors: ['start'],
79
- },
80
- ];
1
+ // Starter content — entities, zones, and manifest
2
+ //
3
+ // Replace this with your game's content.
4
+
5
+ import type { EntityState, ZoneState, GameManifest } from '@ai-rpg-engine/core';
6
+
7
+ export const manifest: GameManifest = {
8
+ id: 'my-game',
9
+ title: 'My Game',
10
+ version: '0.1.0',
11
+ engineVersion: '0.1.0',
12
+ ruleset: 'my-game',
13
+ modules: ['traversal-core', 'status-core', 'combat-core'],
14
+ contentPacks: ['my-game'],
15
+ };
16
+
17
+ // ═══════════════════════════════════════════════════════════════════
18
+ // PACK METADATA
19
+ // This is a minimal subset to keep the template dependency-light. To list
20
+ // your pack in the CLI pack selector, export the full PackMetadata shape
21
+ // from @ai-rpg-engine/pack-registry (adds: tagline, genres, difficulty,
22
+ // tones, tags, engineVersion, narratorTone) plus a BuildCatalog from
23
+ // @ai-rpg-engine/character-creation — see any packages/starter-* content.ts
24
+ // for a complete example.
25
+ // ═══════════════════════════════════════════════════════════════════
26
+
27
+ export const packMeta = {
28
+ id: 'my-game',
29
+ name: 'My Game',
30
+ description: 'A brief description of your game',
31
+ version: '0.1.0',
32
+ };
33
+
34
+ // ═══════════════════════════════════════════════════════════════════
35
+ // PLAYER
36
+ // ═══════════════════════════════════════════════════════════════════
37
+
38
+ export const player: EntityState = {
39
+ id: 'player',
40
+ blueprintId: 'player',
41
+ type: 'player',
42
+ name: 'Hero',
43
+ tags: ['player'],
44
+ stats: { power: 5, speed: 4, grit: 3 },
45
+ resources: { hp: 25, stamina: 10, tension: 0 },
46
+ statuses: [],
47
+ inventory: [],
48
+ zoneId: 'start',
49
+ };
50
+
51
+ // ═══════════════════════════════════════════════════════════════════
52
+ // ENEMIES
53
+ // ═══════════════════════════════════════════════════════════════════
54
+
55
+ export const enemy: EntityState = {
56
+ id: 'grunt',
57
+ blueprintId: 'grunt',
58
+ type: 'npc',
59
+ name: 'Grunt',
60
+ tags: ['enemy', 'hostile'],
61
+ stats: { power: 3, speed: 3, grit: 2 },
62
+ resources: { hp: 12 },
63
+ statuses: [],
64
+ zoneId: 'danger-zone',
65
+ };
66
+
67
+ // ═══════════════════════════════════════════════════════════════════
68
+ // ZONES
69
+ // ═══════════════════════════════════════════════════════════════════
70
+
71
+ export const zones: ZoneState[] = [
72
+ {
73
+ id: 'start',
74
+ roomId: 'room-1',
75
+ name: 'Starting Area',
76
+ tags: ['safe'],
77
+ neighbors: ['danger-zone'],
78
+ },
79
+ {
80
+ id: 'danger-zone',
81
+ roomId: 'room-2',
82
+ name: 'The Danger Zone',
83
+ tags: ['hostile'],
84
+ neighbors: ['start'],
85
+ },
86
+ ];
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- // @ai-rpg-engine/starter-YOURNAME
2
-
3
- export { createGame } from './setup.js';
4
- export { manifest, packMeta } from './content.js';
5
- export { myRuleset } from './ruleset.js';
1
+ // @ai-rpg-engine/starter-YOURNAME
2
+
3
+ export { createGame } from './setup.js';
4
+ export { manifest, packMeta } from './content.js';
5
+ export { myRuleset } from './ruleset.js';
package/src/ruleset.ts CHANGED
@@ -1,65 +1,65 @@
1
- // Ruleset — declarative contract for your game's stats, resources, and verbs
2
-
3
- import type { RulesetDefinition } from '@ai-rpg-engine/core';
4
-
5
- export const myRuleset: RulesetDefinition = {
6
- id: 'my-game',
7
- name: 'My Game',
8
- version: '0.1.0',
9
-
10
- // ═══════════════════════════════════════════════════════════════
11
- // STATS — three combat dimensions + any exploration/social stats
12
- // ═══════════════════════════════════════════════════════════════
13
- stats: [
14
- // Combat dimensions (required — mapped in buildCombatStack)
15
- { id: 'power', name: 'Power', min: 1, max: 20, default: 5 }, // → attack
16
- { id: 'speed', name: 'Speed', min: 1, max: 20, default: 5 }, // → precision
17
- { id: 'grit', name: 'Grit', min: 1, max: 20, default: 5 }, // → resolve
18
- // Add exploration/social stats here if needed
19
- ],
20
-
21
- // ═══════════════════════════════════════════════════════════════
22
- // RESOURCES — hp is required; add your starter-specific resource
23
- // ═══════════════════════════════════════════════════════════════
24
- resources: [
25
- { id: 'hp', name: 'HP', min: 0, max: 100, default: 25 },
26
- { id: 'stamina', name: 'Stamina', min: 0, max: 50, default: 10, regenRate: 1 },
27
- // YOUR RESOURCE: the one thing that makes your game feel different
28
- { id: 'tension', name: 'Tension', min: 0, max: 100, default: 0 },
29
- ],
30
-
31
- // ═══════════════════════════════════════════════════════════════
32
- // VERBS — actions available to players and AI
33
- // ═══════════════════════════════════════════════════════════════
34
- verbs: [
35
- { id: 'move', name: 'Move', description: 'Travel to an adjacent zone' },
36
- { id: 'inspect', name: 'Inspect', description: 'Examine surroundings' },
37
- { id: 'attack', name: 'Attack', tags: ['combat'], description: 'Strike a target' },
38
- { id: 'guard', name: 'Guard', tags: ['combat', 'defensive'], description: 'Reduce incoming damage' },
39
- { id: 'brace', name: 'Brace', tags: ['combat', 'defensive'], description: 'Stabilize and resist status effects' },
40
- { id: 'disengage', name: 'Disengage', tags: ['combat', 'movement'], description: 'Break from combat' },
41
- { id: 'reposition', name: 'Reposition', tags: ['combat', 'movement'], description: 'Shift engagement state' },
42
- { id: 'use', name: 'Use', description: 'Use an item' },
43
- { id: 'speak', name: 'Speak', tags: ['dialogue'], description: 'Talk to an NPC' },
44
- { id: 'choose', name: 'Choose', tags: ['dialogue'], description: 'Select a dialogue option' },
45
- { id: 'use-ability', name: 'Use Ability', tags: ['ability'], description: 'Activate a special ability' },
46
- ],
47
-
48
- formulas: [
49
- { id: 'hit-chance', name: 'Hit Chance', description: 'Standard: 50 + speed*5 - target.speed*3', inputs: ['attacker.speed', 'target.speed'], output: 'number (0-100)' },
50
- { id: 'damage', name: 'Damage', description: 'Standard: attacker.power', inputs: ['attacker.power'], output: 'number' },
51
- ],
52
-
53
- defaultModules: [
54
- 'traversal-core',
55
- 'combat-core',
56
- 'cognition-core',
57
- 'engagement-core',
58
- 'combat-tactics',
59
- 'combat-intent',
60
- 'combat-recovery',
61
- 'combat-state-narration',
62
- ],
63
-
64
- progressionModels: [],
65
- };
1
+ // Ruleset — declarative contract for your game's stats, resources, and verbs
2
+
3
+ import type { RulesetDefinition } from '@ai-rpg-engine/core';
4
+
5
+ export const myRuleset: RulesetDefinition = {
6
+ id: 'my-game',
7
+ name: 'My Game',
8
+ version: '0.1.0',
9
+
10
+ // ═══════════════════════════════════════════════════════════════
11
+ // STATS — three combat dimensions + any exploration/social stats
12
+ // ═══════════════════════════════════════════════════════════════
13
+ stats: [
14
+ // Combat dimensions (required — mapped in buildCombatStack)
15
+ { id: 'power', name: 'Power', min: 1, max: 20, default: 5 }, // → attack
16
+ { id: 'speed', name: 'Speed', min: 1, max: 20, default: 5 }, // → precision
17
+ { id: 'grit', name: 'Grit', min: 1, max: 20, default: 5 }, // → resolve
18
+ // Add exploration/social stats here if needed
19
+ ],
20
+
21
+ // ═══════════════════════════════════════════════════════════════
22
+ // RESOURCES — hp is required; add your starter-specific resource
23
+ // ═══════════════════════════════════════════════════════════════
24
+ resources: [
25
+ { id: 'hp', name: 'HP', min: 0, max: 100, default: 25 },
26
+ { id: 'stamina', name: 'Stamina', min: 0, max: 50, default: 10, regenRate: 1 },
27
+ // YOUR RESOURCE: the one thing that makes your game feel different
28
+ { id: 'tension', name: 'Tension', min: 0, max: 100, default: 0 },
29
+ ],
30
+
31
+ // ═══════════════════════════════════════════════════════════════
32
+ // VERBS — actions available to players and AI
33
+ // ═══════════════════════════════════════════════════════════════
34
+ verbs: [
35
+ { id: 'move', name: 'Move', description: 'Travel to an adjacent zone' },
36
+ { id: 'inspect', name: 'Inspect', description: 'Examine surroundings' },
37
+ { id: 'attack', name: 'Attack', tags: ['combat'], description: 'Strike a target' },
38
+ { id: 'guard', name: 'Guard', tags: ['combat', 'defensive'], description: 'Reduce incoming damage' },
39
+ { id: 'brace', name: 'Brace', tags: ['combat', 'defensive'], description: 'Stabilize and resist status effects' },
40
+ { id: 'disengage', name: 'Disengage', tags: ['combat', 'movement'], description: 'Break from combat' },
41
+ { id: 'reposition', name: 'Reposition', tags: ['combat', 'movement'], description: 'Shift engagement state' },
42
+ { id: 'use', name: 'Use', description: 'Use an item' },
43
+ { id: 'speak', name: 'Speak', tags: ['dialogue'], description: 'Talk to an NPC' },
44
+ { id: 'choose', name: 'Choose', tags: ['dialogue'], description: 'Select a dialogue option' },
45
+ { id: 'use-ability', name: 'Use Ability', tags: ['ability'], description: 'Activate a special ability' },
46
+ ],
47
+
48
+ formulas: [
49
+ { id: 'hit-chance', name: 'Hit Chance', description: 'Standard: 50 + speed*5 - target.speed*3', inputs: ['attacker.speed', 'target.speed'], output: 'number (0-100)' },
50
+ { id: 'damage', name: 'Damage', description: 'Standard: attacker.power', inputs: ['attacker.power'], output: 'number' },
51
+ ],
52
+
53
+ defaultModules: [
54
+ 'traversal-core',
55
+ 'combat-core',
56
+ 'cognition-core',
57
+ 'engagement-core',
58
+ 'combat-tactics',
59
+ 'combat-intent',
60
+ 'combat-recovery',
61
+ 'combat-state-narration',
62
+ ],
63
+
64
+ progressionModels: [],
65
+ };
package/src/setup.ts CHANGED
@@ -1,95 +1,112 @@
1
- // Game setup — wire content + combat stack into the engine
2
- //
3
- // ╔══════════════════════════════════════════════════════════════════╗
4
- // ║ COMPOSITION CONTRACT ║
5
- // ║ buildCombatStack owns: cognition, engagement, review, ║
6
- // ║ combatCore, tactics, resources, intent, recovery, narration ║
7
- // ║ YOU own: traversal, status, inventory, dialogue, progression, ║
8
- // ║ perception, environment, abilities, and your custom modules ║
9
- // ╚══════════════════════════════════════════════════════════════════╝
10
-
11
- import { Engine } from '@ai-rpg-engine/core';
12
- import type { EngineModule, ResolvedEvent, WorldState } from '@ai-rpg-engine/core';
13
- import {
14
- traversalCore,
15
- statusCore,
16
- buildCombatStack,
17
- } from '@ai-rpg-engine/modules';
18
- import { manifest, player, enemy, zones } from './content.js';
19
- import { myRuleset } from './ruleset.js';
20
-
21
- // ═══════════════════════════════════════════════════════════════════
22
- // STARTER-OWNED SYSTEMS
23
- // Add your game-specific modules here. These are the things that make
24
- // your starter feel different from other starters.
25
- // ═══════════════════════════════════════════════════════════════════
26
-
27
- // Example: a custom module that ticks your "tension" resource each combat round
28
- function createTensionPressure(): EngineModule {
29
- return {
30
- id: 'tension-pressure',
31
- version: '1.0.0',
32
- register(ctx) {
33
- ctx.events.on('combat.round.end', (event: ResolvedEvent, world: WorldState) => {
34
- const p = world.entities['player'];
35
- if (p && p.resources.tension !== undefined) {
36
- p.resources.tension = Math.min(100, (p.resources.tension ?? 0) + 5);
37
- }
38
- });
39
- },
40
- };
41
- }
42
-
43
- // ═══════════════════════════════════════════════════════════════════
44
- // GAME FACTORY
45
- // ═══════════════════════════════════════════════════════════════════
46
-
47
- export function createGame(seed?: number): Engine {
48
- // Combat stack: map your three stats → the combat dimensions
49
- const combat = buildCombatStack({
50
- statMapping: { attack: 'power', precision: 'speed', resolve: 'grit' },
51
- playerId: 'player',
52
- // resourceProfile uncomment and customize for your game's resource pressure:
53
- // resourceProfile: {
54
- // spends: [
55
- // { verbId: 'attack', costStat: 'stamina', amount: 2 },
56
- // ],
57
- // },
58
- // biasTags entity tags that cognition uses for faction bias:
59
- // biasTags: ['enemy'],
60
- // engagement — customize if your game uses ranged/melee distance:
61
- // engagement: { defaultRange: 'melee' },
62
- // recovery safe zone recovery:
63
- recovery: { safeZoneTags: ['safe'] },
64
- });
65
-
66
- const engine = new Engine({
67
- manifest,
68
- seed: seed ?? 42,
69
- ruleset: myRuleset,
70
- modules: [
71
- traversalCore,
72
- statusCore,
73
- ...combat.modules, // the full combat stack
74
- // ──────────────────────────────────────────────
75
- // YOUR MODULES GO HERE
76
- createTensionPressure(),
77
- // ──────────────────────────────────────────────
78
- ],
79
- });
80
-
81
- // Register zones
82
- for (const zone of zones) {
83
- engine.store.addZone(zone);
84
- }
85
-
86
- // Register entities
87
- engine.store.addEntity({ ...player });
88
- engine.store.addEntity({ ...enemy });
89
-
90
- // Set player context
91
- engine.store.state.playerId = 'player';
92
- engine.store.state.locationId = 'start';
93
-
94
- return engine;
95
- }
1
+ // Game setup — wire content + combat stack into the engine
2
+ //
3
+ // ╔══════════════════════════════════════════════════════════════════╗
4
+ // ║ COMPOSITION CONTRACT ║
5
+ // ║ buildCombatStack owns: cognition, engagement, review, ║
6
+ // ║ combatCore, tactics, resources, intent, recovery, narration ║
7
+ // ║ YOU own: traversal, status, inventory, dialogue, progression, ║
8
+ // ║ perception, environment, abilities, and your custom modules ║
9
+ // ╚══════════════════════════════════════════════════════════════════╝
10
+
11
+ import { Engine } from '@ai-rpg-engine/core';
12
+ import type { EngineModule, ResolvedEvent, WorldState } from '@ai-rpg-engine/core';
13
+ import {
14
+ traversalCore,
15
+ statusCore,
16
+ buildCombatStack,
17
+ } from '@ai-rpg-engine/modules';
18
+ import { manifest, player, enemy, zones } from './content.js';
19
+ import { myRuleset } from './ruleset.js';
20
+
21
+ // ═══════════════════════════════════════════════════════════════════
22
+ // STARTER-OWNED SYSTEMS
23
+ // Add your game-specific modules here. These are the things that make
24
+ // your starter feel different from other starters.
25
+ // ═══════════════════════════════════════════════════════════════════
26
+
27
+ // Example: a custom module that raises your "tension" resource whenever
28
+ // damage lands in combat. It listens on 'combat.damage.applied' — a real
29
+ // event emitted by combat-core every time an attack connects (other engine
30
+ // events you can hook: 'combat.contact.hit', 'combat.contact.miss',
31
+ // 'combat.entity.defeated', 'status.applied', 'world.zone.entered').
32
+ function createTensionPressure(): EngineModule {
33
+ return {
34
+ id: 'tension-pressure',
35
+ version: '1.0.0',
36
+ register(ctx) {
37
+ ctx.events.on('combat.damage.applied', (event: ResolvedEvent, world: WorldState) => {
38
+ const p = world.entities['player'];
39
+ if (p && p.resources.tension !== undefined) {
40
+ p.resources.tension = Math.min(100, (p.resources.tension ?? 0) + 5);
41
+ }
42
+ });
43
+ },
44
+ };
45
+ }
46
+
47
+ // ═══════════════════════════════════════════════════════════════════
48
+ // GAME FACTORY
49
+ // ═══════════════════════════════════════════════════════════════════
50
+
51
+ export function createGame(seed?: number): Engine {
52
+ // Combat stack: map your three stats the combat dimensions
53
+ const combat = buildCombatStack({
54
+ statMapping: { attack: 'power', precision: 'speed', resolve: 'grit' },
55
+ playerId: 'player',
56
+ // resourceProfile — uncomment and customize for your game's resource
57
+ // pressure. A CombatResourceProfile needs packId + the four arrays
58
+ // (gains / spends / drains / aiModifiers empty arrays are fine):
59
+ // resourceProfile: {
60
+ // packId: 'my-game',
61
+ // gains: [
62
+ // // +2 tension every time an attack you make lands
63
+ // { trigger: 'attack-hit', resourceId: 'tension', amount: 2 },
64
+ // ],
65
+ // spends: [
66
+ // // spend 2 stamina on each attack for +1 damage
67
+ // { action: 'attack', resourceId: 'stamina', amount: 2, effects: { damageBonus: 1 } },
68
+ // ],
69
+ // drains: [],
70
+ // aiModifiers: [],
71
+ // },
72
+ // biasTags — built-in pack bias tags that shape combat AI intent.
73
+ // Must come from PACK_BIAS_TAGS (exported by @ai-rpg-engine/modules,
74
+ // e.g. 'undead', 'beast', 'feral'); unknown tags warn and are dropped:
75
+ // biasTags: ['undead', 'beast'],
76
+ // engagement — backline/protector behavior for ranged parties
77
+ // (fields: backlineTags, protectorTags, chokepointTag, ambushTag):
78
+ // engagement: { backlineTags: ['ranged', 'caster'], protectorTags: ['bodyguard'] },
79
+ // recovery — safe zone recovery:
80
+ recovery: { safeZoneTags: ['safe'] },
81
+ });
82
+
83
+ const engine = new Engine({
84
+ manifest,
85
+ seed: seed ?? 42,
86
+ ruleset: myRuleset,
87
+ modules: [
88
+ traversalCore,
89
+ statusCore,
90
+ ...combat.modules, // the full combat stack
91
+ // ──────────────────────────────────────────────
92
+ // YOUR MODULES GO HERE
93
+ createTensionPressure(),
94
+ // ──────────────────────────────────────────────
95
+ ],
96
+ });
97
+
98
+ // Register zones
99
+ for (const zone of zones) {
100
+ engine.store.addZone(zone);
101
+ }
102
+
103
+ // Register entities
104
+ engine.store.addEntity({ ...player });
105
+ engine.store.addEntity({ ...enemy });
106
+
107
+ // Set player context
108
+ engine.store.state.playerId = 'player';
109
+ engine.store.state.locationId = 'start';
110
+
111
+ return engine;
112
+ }
@@ -1,35 +1,47 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { validateRulesetDefinition } from '@ai-rpg-engine/content-schema';
3
- import { myRuleset } from './ruleset.js';
4
- import { createGame } from './setup.js';
5
-
6
- describe('starter template', () => {
7
- it('ruleset validates against schema', () => {
8
- const r = validateRulesetDefinition(myRuleset);
9
- expect(r.ok).toBe(true);
10
- expect(r.errors).toHaveLength(0);
11
- });
12
-
13
- it('declares combat stats mapped in buildCombatStack', () => {
14
- const statIds = myRuleset.stats.map((s) => s.id);
15
- expect(statIds).toContain('power');
16
- expect(statIds).toContain('speed');
17
- expect(statIds).toContain('grit');
18
- });
19
-
20
- it('declares a starter-specific resource', () => {
21
- const resIds = myRuleset.resources.map((r) => r.id);
22
- expect(resIds).toContain('tension');
23
- });
24
-
25
- it('createGame boots without throwing', () => {
26
- const engine = createGame(1);
27
- expect(engine).toBeDefined();
28
- });
29
-
30
- it('tension pressure module is wired', () => {
31
- const engine = createGame(1);
32
- const p = engine.world.entities['player'];
33
- expect(p?.resources.tension).toBe(0);
34
- });
35
- });
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateRulesetDefinition } from '@ai-rpg-engine/content-schema';
3
+ import { myRuleset } from './ruleset.js';
4
+ import { createGame } from './setup.js';
5
+
6
+ describe('starter template', () => {
7
+ it('ruleset validates against schema', () => {
8
+ const r = validateRulesetDefinition(myRuleset);
9
+ expect(r.ok).toBe(true);
10
+ expect(r.errors).toHaveLength(0);
11
+ });
12
+
13
+ it('declares combat stats mapped in buildCombatStack', () => {
14
+ const statIds = myRuleset.stats.map((s) => s.id);
15
+ expect(statIds).toContain('power');
16
+ expect(statIds).toContain('speed');
17
+ expect(statIds).toContain('grit');
18
+ });
19
+
20
+ it('declares a starter-specific resource', () => {
21
+ const resIds = myRuleset.resources.map((r) => r.id);
22
+ expect(resIds).toContain('tension');
23
+ });
24
+
25
+ it('createGame boots without throwing', () => {
26
+ const engine = createGame(1);
27
+ expect(engine).toBeDefined();
28
+ });
29
+
30
+ it('tension rises when combat damage lands (tension-pressure fires)', () => {
31
+ const engine = createGame(1);
32
+ expect(engine.world.entities['player']?.resources.tension).toBe(0);
33
+
34
+ // Walk into the danger zone and fight. combat-core emits
35
+ // 'combat.damage.applied' whenever a hit lands — that is the event
36
+ // the tension-pressure module listens on.
37
+ engine.submitAction('move', { targetIds: ['danger-zone'] });
38
+ for (let i = 0; i < 8; i++) {
39
+ engine.submitAction('attack', { targetIds: ['grunt'] });
40
+ if ((engine.world.entities['player']?.resources.tension ?? 0) > 0) break;
41
+ }
42
+
43
+ // Meta-test property: deleting the tension-pressure listener in
44
+ // setup.ts turns this RED — nothing else writes the tension resource.
45
+ expect(engine.world.entities['player']?.resources.tension).toBeGreaterThan(0);
46
+ });
47
+ });
package/tsconfig.json CHANGED
@@ -1,14 +1,24 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src"
6
- },
7
- "include": ["src"],
8
- "references": [
9
- { "path": "../../packages/core" },
10
- { "path": "../../packages/content-schema" },
11
- { "path": "../../packages/modules" }
12
- ],
13
- "exclude": ["**/*.test.ts"]
14
- }
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": [
8
+ "src"
9
+ ],
10
+ "references": [
11
+ {
12
+ "path": "../../packages/core"
13
+ },
14
+ {
15
+ "path": "../../packages/content-schema"
16
+ },
17
+ {
18
+ "path": "../../packages/modules"
19
+ }
20
+ ],
21
+ "exclude": [
22
+ "**/*.test.ts"
23
+ ]
24
+ }