@alexkroman1/aai-cli 5.1.0 → 5.2.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/dist/{_agent-D39qatZJ.mjs → _agent-D8zBb3M5.mjs} +2 -3
- package/dist/_agent.d.ts +39 -0
- package/dist/{_api-client-a6cPMebU.mjs → _api-client-MenP4-O7.mjs} +3 -1
- package/dist/_api-client.d.ts +37 -0
- package/dist/{_bundler-DdmuZzeR.mjs → _bundler-DFS4xxqE.mjs} +3 -13
- package/dist/_bundler.d.ts +46 -0
- package/dist/{_config-Czjhvaax.mjs → _config-D3F9km8X.mjs} +3 -3
- package/dist/_config.d.ts +57 -0
- package/dist/_default-html.d.ts +12 -0
- package/dist/_deploy.d.ts +28 -0
- package/dist/{_dev-server-BCbEUhmv.mjs → _dev-server-BBUWo0sb.mjs} +12 -14
- package/dist/_dev-server.d.ts +74 -0
- package/dist/{_init-H-lw7czL.mjs → _init-DU-sXH6S.mjs} +47 -65
- package/dist/_init.d.ts +7 -0
- package/dist/_mock-api.d.ts +22 -0
- package/dist/_mock-registry.d.ts +17 -0
- package/dist/_output.d.ts +50 -0
- package/dist/{_server-common-B75oco06.mjs → _server-common-CnaP_Urf.mjs} +2 -1
- package/dist/_server-common.d.ts +17 -0
- package/dist/_slug-api-19R5kZ0U.mjs +27 -0
- package/dist/_slug-api.d.ts +15 -0
- package/dist/_templates.d.ts +21 -0
- package/dist/{_typecheck-gate-B1GcKZX-.mjs → _typecheck-gate-9IHWDnl1.mjs} +2 -2
- package/dist/_typecheck-gate.d.ts +7 -0
- package/dist/{_ui-DXZ9prrM.mjs → _ui-8kOEB-JH.mjs} +6 -3
- package/dist/_ui.d.ts +22 -0
- package/dist/{_utils-C502jKo8.mjs → _utils-Ch0J4s6a.mjs} +17 -1
- package/dist/_utils.d.ts +48 -0
- package/dist/_vite-env.d.ts +8 -0
- package/dist/build-Xq1xxoSg.mjs +38 -0
- package/dist/build.d.ts +16 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +35 -52
- package/dist/{client-bundler-DO3GgO6p.mjs → client-bundler-BWDkUeEP.mjs} +5 -6
- package/dist/client-bundler.d.ts +21 -0
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-K24hW1ab.mjs → delete-kSXFLxek.mjs} +4 -4
- package/dist/delete.d.ts +18 -0
- package/dist/{deploy-D-dnt9L8.mjs → deploy-Ci0X1gp2.mjs} +8 -8
- package/dist/deploy.d.ts +15 -0
- package/dist/{dev-C8o-p13B.mjs → dev-g7sGt8qX.mjs} +3 -3
- package/dist/dev.d.ts +13 -0
- package/dist/{init-B53rR3yD.mjs → init-BjeK8crW.mjs} +10 -17
- package/dist/init.d.ts +24 -0
- package/dist/scaffold/.env.example +11 -0
- package/dist/scaffold/CLAUDE.md +919 -0
- package/dist/scaffold/global.d.ts +1 -0
- package/dist/scaffold/package.json +32 -0
- package/dist/scaffold/pnpm-workspace.yaml +13 -0
- package/dist/scaffold/tsconfig.json +19 -0
- package/dist/scaffold/vite.config.ts +14 -0
- package/dist/scaffold/vitest.config.ts +24 -0
- package/dist/{secret-C4unwCEw.mjs → secret-DOva9OGk.mjs} +21 -20
- package/dist/secret.d.ts +25 -0
- package/dist/{storage-tZM1J8qj.mjs → storage-vM6HjDZZ.mjs} +6 -13
- package/dist/storage.d.ts +22 -0
- package/dist/templates/code-interpreter/agent.ts +11 -0
- package/dist/templates/code-interpreter/system-prompt.md +18 -0
- package/dist/templates/dispatch-center/agent.test.ts +153 -0
- package/dist/templates/dispatch-center/agent.ts +50 -0
- package/dist/templates/dispatch-center/client.tsx +378 -0
- package/dist/templates/dispatch-center/shared.ts +631 -0
- package/dist/templates/dispatch-center/system-prompt.md +39 -0
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +27 -0
- package/dist/templates/dispatch-center/tools/incident_create.ts +79 -0
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +88 -0
- package/dist/templates/dispatch-center/tools/incident_get.ts +36 -0
- package/dist/templates/dispatch-center/tools/incident_triage.ts +78 -0
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +68 -0
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +52 -0
- package/dist/templates/dispatch-center/tools/ops_protocols.ts +27 -0
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +149 -0
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +97 -0
- package/dist/templates/dispatch-center/tools/resources_get_available.ts +36 -0
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +62 -0
- package/dist/templates/embedded-assets/agent.ts +55 -0
- package/dist/templates/embedded-assets/knowledge.json +20 -0
- package/dist/templates/health-assistant/agent.ts +174 -0
- package/dist/templates/health-assistant/system-prompt.md +17 -0
- package/dist/templates/infocom-adventure/agent.ts +119 -0
- package/dist/templates/infocom-adventure/client.tsx +279 -0
- package/dist/templates/infocom-adventure/shared.ts +38 -0
- package/dist/templates/infocom-adventure/system-prompt.md +43 -0
- package/dist/templates/math-buddy/agent.ts +16 -0
- package/dist/templates/math-buddy/system-prompt.md +12 -0
- package/dist/templates/night-owl/agent.ts +76 -0
- package/dist/templates/night-owl/client.tsx +130 -0
- package/dist/templates/night-owl/shared.ts +5 -0
- package/dist/templates/personal-finance/agent.ts +11 -0
- package/dist/templates/personal-finance/system-prompt.md +17 -0
- package/dist/templates/pipeline-simple/agent.test.ts +32 -0
- package/dist/templates/pipeline-simple/agent.ts +13 -0
- package/dist/templates/pizza-ordering/agent.test.ts +235 -0
- package/dist/templates/pizza-ordering/agent.ts +174 -0
- package/dist/templates/pizza-ordering/client.tsx +120 -0
- package/dist/templates/pizza-ordering/shared.ts +142 -0
- package/dist/templates/pizza-ordering/system-prompt.md +17 -0
- package/dist/templates/simple/agent.ts +5 -0
- package/dist/templates/solo-rpg/agent.test.ts +477 -0
- package/dist/templates/solo-rpg/agent.ts +37 -0
- package/dist/templates/solo-rpg/client.tsx +809 -0
- package/dist/templates/solo-rpg/shared.ts +747 -0
- package/dist/templates/solo-rpg/system-prompt.md +115 -0
- package/dist/templates/solo-rpg/tools/action_roll.ts +98 -0
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +69 -0
- package/dist/templates/solo-rpg/tools/check_state.ts +11 -0
- package/dist/templates/solo-rpg/tools/load_game.ts +25 -0
- package/dist/templates/solo-rpg/tools/oracle.ts +248 -0
- package/dist/templates/solo-rpg/tools/save_game.ts +20 -0
- package/dist/templates/solo-rpg/tools/setup_character.ts +208 -0
- package/dist/templates/solo-rpg/tools/update_state.ts +170 -0
- package/dist/templates/web-researcher/agent.ts +11 -0
- package/dist/{test-CgWhus_Z.mjs → test-3Gq4pqH_.mjs} +5 -7
- package/dist/test.d.ts +39 -0
- package/dist/tsdown.config.d.ts +2 -0
- package/dist/typecheck.d.ts +26 -0
- package/dist/typecheck.mjs +6 -10
- package/dist/worker-bundler.d.ts +29 -0
- package/package.json +9 -6
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
You are the Narrator of a solo tabletop RPG engine. You guide the player through a narrative adventure using proven game mechanics adapted from Ironsworn/Starforged, Mythic GME, and Blades in the Dark.
|
|
2
|
+
|
|
3
|
+
CHARACTER CREATION — ONE TURN SETUP:
|
|
4
|
+
The player only needs to give you ONE thing to start: a name, a genre, a character idea, or just say "go". That is enough. You fill in the rest.
|
|
5
|
+
|
|
6
|
+
From whatever the player gives you, infer the best genre, tone, and archetype. If they say "cyberpunk hacker named Kai", you have everything. If they just say "Luna", pick a genre and archetype that sounds interesting and go. If they say "surprise me", pick everything yourself.
|
|
7
|
+
|
|
8
|
+
Available genres: Dark Fantasy, High Fantasy, Sci-Fi, Horror / Mystery, Steampunk, Cyberpunk, Urban Fantasy, Victorian Crime, Historical / Roman, Fairy Tale World, Slice of Life 1990s, Outdoor Survival.
|
|
9
|
+
Available tones: Dark & Gritty, Serious but Fair, Melancholic, Absurd & Grotesque, Slow-Burn Horror, Cheerful & Fun, Romantic, Slapstick, Epic & Heroic, Tarantino-Style, Cozy & Comfy, Tragicomic.
|
|
10
|
+
Available archetypes: Outsider / Loner, Investigator / Curious, Trickster / Charmer, Protector / Warrior, Hardboiled / Veteran, Scholar / Mystic, Healer / Medic, Crafter / Inventor, Artist / Bard.
|
|
11
|
+
|
|
12
|
+
Once you have the player's input, immediately call setup_character with ALL fields filled in: genre, tone, archetype, playerName, characterConcept, settingDescription, startingLocation, locationDesc, timeOfDay, openingSituation, npc1Name, npc1Desc, npc1Disposition, npc1Agenda, threatClockName, threatClockDesc. You must generate all of these yourself based on the player's input. setup_character handles all state initialization — stats, NPCs, clocks, story blueprint, everything. After it returns, just narrate the opening scene. Do NOT call update_state after setup_character — it is already done.
|
|
13
|
+
|
|
14
|
+
Do all of this in ONE turn. Never ask follow-up questions before starting. The player can always change things later by telling you.
|
|
15
|
+
|
|
16
|
+
IMPORTANT FOR SPEECH: Never list more than three or four options. Keep all responses punchy and conversational. No long lists — they sound terrible spoken aloud.
|
|
17
|
+
|
|
18
|
+
CORE MECHANIC - ACTION ROLL (Ironsworn):
|
|
19
|
+
When the player attempts something risky, use the action_roll tool. You choose the move and stat:
|
|
20
|
+
- edge for speed, agility, precision, ranged combat
|
|
21
|
+
- heart for courage, willpower, empathy, leadership
|
|
22
|
+
- iron for strength, endurance, melee combat
|
|
23
|
+
- shadow for stealth, deception, cunning
|
|
24
|
+
- wits for expertise, knowledge, observation
|
|
25
|
+
|
|
26
|
+
The system rolls 2d6 + stat (capped at 10) vs 2d10.
|
|
27
|
+
- Strong Hit: beat both d10s. Clean success.
|
|
28
|
+
- Weak Hit: beat one d10. Success with a cost or complication.
|
|
29
|
+
- Miss: beat neither. Failure with consequences.
|
|
30
|
+
- Match (both d10s same): amplifies the result. Strong Hit + Match = exceptional. Miss + Match = dire escalation.
|
|
31
|
+
|
|
32
|
+
MOVES (12 mechanical actions):
|
|
33
|
+
- face_danger: overcome obstacles, act under pressure
|
|
34
|
+
- gather_information: search, investigate, observe
|
|
35
|
+
- secure_advantage: prepare, scout, gain edge
|
|
36
|
+
- world_shaping: player introduces new world elements
|
|
37
|
+
- compel: persuade, negotiate, manipulate
|
|
38
|
+
- make_connection: bond with someone, establish relationship
|
|
39
|
+
- test_bond: rely on relationship, call in favor
|
|
40
|
+
- clash: opposed combat (melee/range)
|
|
41
|
+
- strike: attack when opponent cannot react
|
|
42
|
+
- endure_harm: suffer physical damage
|
|
43
|
+
- endure_stress: suffer mental/emotional damage
|
|
44
|
+
- resupply: restore supply track
|
|
45
|
+
|
|
46
|
+
Pure conversation has no risk and no move — do NOT call action_roll for it. Just respond in character.
|
|
47
|
+
|
|
48
|
+
POSITION & EFFECT (Blades in the Dark):
|
|
49
|
+
Every risky action has a position and effect:
|
|
50
|
+
Position (how dangerous):
|
|
51
|
+
- Controlled: upper hand, failure is mild
|
|
52
|
+
- Risky: default, real consequences on failure
|
|
53
|
+
- Desperate: in trouble, failure hits hard
|
|
54
|
+
|
|
55
|
+
Effect (what can be achieved):
|
|
56
|
+
- Limited: partial success even on strong hit
|
|
57
|
+
- Standard: full success as described
|
|
58
|
+
- Great: exceeds expectations, bonus outcome
|
|
59
|
+
|
|
60
|
+
Position scales damage on miss. Effect scales momentum gain on strong hit.
|
|
61
|
+
|
|
62
|
+
MOMENTUM (Ironsworn):
|
|
63
|
+
- Starts at 2, range -6 to +10
|
|
64
|
+
- Weak Hit: +1. Strong Hit: +2 (or +3 with great effect)
|
|
65
|
+
- Miss: -2 (or -3 if desperate)
|
|
66
|
+
- Burn: the player can spend momentum to upgrade the most recent roll — call burn_momentum (no arguments; it validates against the stored roll). Momentum beating both challenge dice upgrades fully; beating one turns a Miss into a Weak Hit. Burning reverts the original result's consequences, applies the upgraded ones, and resets momentum to +2.
|
|
67
|
+
|
|
68
|
+
CHAOS FACTOR (Mythic GME):
|
|
69
|
+
- Range 3-9, starts at 5
|
|
70
|
+
- Miss: chaos +1 (max 9). Strong Hit: chaos -1 (min 3). Weak Hit: no change.
|
|
71
|
+
- Scene interrupt probability: (chaos - 3) x 10%. Chaos 5 = 20%, chaos 9 = 60%.
|
|
72
|
+
- When interrupt triggers, something unexpected disrupts the scene.
|
|
73
|
+
|
|
74
|
+
CLOCKS (Blades in the Dark):
|
|
75
|
+
Clocks track threats, progress, and NPC schemes:
|
|
76
|
+
- Threat clocks advance on misses. When full, the threat strikes.
|
|
77
|
+
- Progress clocks track long-term goals.
|
|
78
|
+
- Scheme clocks track NPC agendas (advance every 5 scenes).
|
|
79
|
+
|
|
80
|
+
NPCs:
|
|
81
|
+
NPCs have dispositions: hostile, distrustful, neutral, friendly, loyal.
|
|
82
|
+
Social strong hits shift disposition favorably. Social misses damage bonds.
|
|
83
|
+
NPCs have agendas that drive their behavior.
|
|
84
|
+
Track up to 12 active NPCs.
|
|
85
|
+
|
|
86
|
+
CRISIS:
|
|
87
|
+
- Health or spirit at 0 = crisis mode. Both at 0 = game over.
|
|
88
|
+
- In crisis, every miss is more dangerous.
|
|
89
|
+
|
|
90
|
+
KID MODE:
|
|
91
|
+
If kidMode is true: no explicit violence, no death, hopeful tone, age-appropriate content. Enemies are "defeated" not "killed". Think Studio Ghibli, Zelda.
|
|
92
|
+
|
|
93
|
+
STORY BLUEPRINT:
|
|
94
|
+
The story follows either a 3-act structure or Kishotenketsu (4-part). Created at game start. Track act transitions based on narrative conditions, not scene numbers.
|
|
95
|
+
|
|
96
|
+
CORRECTION SYSTEM:
|
|
97
|
+
If the player starts a message with ##, treat it as a correction to the previous turn. Acknowledge the correction and rewrite the scene.
|
|
98
|
+
|
|
99
|
+
FLOW:
|
|
100
|
+
1. Call check_state as your FIRST tool call every turn, before narrating or rolling. Read the returned values as ground truth. NEVER remember or guess stats from prior turns.
|
|
101
|
+
2. Present situations with tension and choice. Two to three options, but accept anything.
|
|
102
|
+
3. For ANY risky action, you MUST call action_roll. NEVER narrate success or failure without rolling. NEVER reduce health, spirit, supply, or momentum yourself — action_roll does this through code. If you narrate damage without calling action_roll, the sidebar will be wrong and the game will break.
|
|
103
|
+
4. After location changes, new NPCs, or other world changes, call update_state. But NEVER manually set health, spirit, supply, or momentum in update_state unless the player is resting or trading — action_roll handles combat and risk.
|
|
104
|
+
5. The chaos interrupt check happens automatically inside action_roll. If the result includes a chaosInterrupt, weave that disruption into your narration.
|
|
105
|
+
6. Every 3 scenes, consider story arc progression and NPC development via update_state.
|
|
106
|
+
|
|
107
|
+
VOICE:
|
|
108
|
+
- Keep narration to 2-4 sentences. Optimized for spoken conversation.
|
|
109
|
+
- Short, punchy sentences. No visual formatting.
|
|
110
|
+
- Never mention "search results" or "sources".
|
|
111
|
+
- No exclamation points. Calm, conversational tone.
|
|
112
|
+
- One vivid detail per scene. Let the player's imagination do the rest.
|
|
113
|
+
- NPCs speak in character. Brief, natural dialog fragments.
|
|
114
|
+
- Never over-describe. If the player wants more detail, they will ask.
|
|
115
|
+
- Describe consequences naturally within the narration, do not list them.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { tool } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
applyConsequences,
|
|
5
|
+
canBurnMomentum,
|
|
6
|
+
checkChaosInterrupt,
|
|
7
|
+
getGameState,
|
|
8
|
+
MOVE_LABELS,
|
|
9
|
+
MOVES,
|
|
10
|
+
RESULT_LABELS,
|
|
11
|
+
rollAction,
|
|
12
|
+
updateChaosFactor,
|
|
13
|
+
} from "../shared.ts";
|
|
14
|
+
|
|
15
|
+
export const actionRoll = tool({
|
|
16
|
+
description:
|
|
17
|
+
"Core mechanic. Roll 2d6 + stat (capped at 10) vs 2d10 challenge dice. Also applies consequences (health/spirit/supply/momentum changes, clock advancement) based on move type, position, and result. Call for ANY risky action. Pure conversation needs no roll.",
|
|
18
|
+
parameters: z.object({
|
|
19
|
+
move: z.enum(MOVES).describe("Which move the player is making"),
|
|
20
|
+
stat: z.enum(["edge", "heart", "iron", "shadow", "wits"]).describe("Which stat to roll"),
|
|
21
|
+
position: z
|
|
22
|
+
.enum(["controlled", "risky", "desperate"])
|
|
23
|
+
.describe("How dangerous the situation is"),
|
|
24
|
+
effect: z.enum(["limited", "standard", "great"]).describe("What can realistically be achieved"),
|
|
25
|
+
purpose: z.string().max(300).describe("What the character is attempting"),
|
|
26
|
+
targetNpcId: z.string().max(32).describe("Target NPC id for social moves").optional(),
|
|
27
|
+
}),
|
|
28
|
+
async execute(args, ctx) {
|
|
29
|
+
const state = getGameState(ctx);
|
|
30
|
+
const statValue = state[args.stat];
|
|
31
|
+
const roll = rollAction(args.stat, statValue, args.move);
|
|
32
|
+
|
|
33
|
+
// Apply consequences
|
|
34
|
+
const { consequences, clockEvents, deltas } = applyConsequences(
|
|
35
|
+
state,
|
|
36
|
+
roll,
|
|
37
|
+
args.position,
|
|
38
|
+
args.effect,
|
|
39
|
+
args.targetNpcId ?? null,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Persist the roll (with the exact deltas applied) so burn_momentum can
|
|
43
|
+
// validate against it and revert it — the model never supplies dice.
|
|
44
|
+
state.lastRoll = {
|
|
45
|
+
...roll,
|
|
46
|
+
position: args.position,
|
|
47
|
+
effect: args.effect,
|
|
48
|
+
targetNpcId: args.targetNpcId ?? null,
|
|
49
|
+
deltas,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Update chaos factor
|
|
53
|
+
updateChaosFactor(state, roll.result);
|
|
54
|
+
|
|
55
|
+
// Check for chaos interrupt
|
|
56
|
+
const interrupt = checkChaosInterrupt(state);
|
|
57
|
+
|
|
58
|
+
// Increment scene count
|
|
59
|
+
state.sceneCount++;
|
|
60
|
+
|
|
61
|
+
// Can burn momentum?
|
|
62
|
+
const burnTarget = canBurnMomentum(state, roll);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
purpose: args.purpose,
|
|
66
|
+
move: MOVE_LABELS[args.move] || args.move,
|
|
67
|
+
moveCode: args.move,
|
|
68
|
+
stat: args.stat,
|
|
69
|
+
statValue,
|
|
70
|
+
actionDice: [roll.d1, roll.d2],
|
|
71
|
+
challengeDice: [roll.c1, roll.c2],
|
|
72
|
+
actionScore: roll.actionScore,
|
|
73
|
+
result: RESULT_LABELS[roll.result],
|
|
74
|
+
resultCode: roll.result,
|
|
75
|
+
match: roll.match,
|
|
76
|
+
matchNote: roll.match
|
|
77
|
+
? roll.result === "STRONG_HIT" || roll.result === "WEAK_HIT"
|
|
78
|
+
? "Fateful roll. Both challenge dice match. An unexpected advantage or twist."
|
|
79
|
+
: "Fateful roll. Both challenge dice match. A dire and dramatic escalation."
|
|
80
|
+
: undefined,
|
|
81
|
+
position: args.position,
|
|
82
|
+
effect: args.effect,
|
|
83
|
+
consequences,
|
|
84
|
+
clockEvents,
|
|
85
|
+
chaosInterrupt: interrupt,
|
|
86
|
+
currentHealth: state.health,
|
|
87
|
+
currentSpirit: state.spirit,
|
|
88
|
+
currentSupply: state.supply,
|
|
89
|
+
currentMomentum: state.momentum,
|
|
90
|
+
chaosFactor: state.chaosFactor,
|
|
91
|
+
crisisMode: state.crisisMode,
|
|
92
|
+
gameOver: state.gameOver,
|
|
93
|
+
sceneCount: state.sceneCount,
|
|
94
|
+
canBurnMomentum: Boolean(burnTarget),
|
|
95
|
+
burnWouldYield: burnTarget ? RESULT_LABELS[burnTarget] : undefined,
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { tool } from "@alexkroman1/aai";
|
|
2
|
+
import {
|
|
3
|
+
applyConsequences,
|
|
4
|
+
canBurnMomentum,
|
|
5
|
+
getGameState,
|
|
6
|
+
MOMENTUM_RESET,
|
|
7
|
+
RESULT_LABELS,
|
|
8
|
+
revertConsequences,
|
|
9
|
+
} from "../shared.ts";
|
|
10
|
+
|
|
11
|
+
export const burnMomentum = tool({
|
|
12
|
+
description:
|
|
13
|
+
"Burn momentum to upgrade the most recent action roll. Only valid when current momentum beats the roll's challenge dice (both dice for a full upgrade, one for Miss to Weak Hit). Reverts the original result's consequences, applies the upgraded result, and resets momentum to +2.",
|
|
14
|
+
async execute(_args, ctx) {
|
|
15
|
+
const state = getGameState(ctx);
|
|
16
|
+
const last = state.lastRoll;
|
|
17
|
+
if (!last) return { error: "No recent action roll to upgrade. Roll first." };
|
|
18
|
+
if (last.result === "STRONG_HIT") {
|
|
19
|
+
return { error: "The last roll was already a Strong Hit. Nothing to upgrade." };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const upgrade = canBurnMomentum(state, last);
|
|
23
|
+
if (!upgrade) {
|
|
24
|
+
return {
|
|
25
|
+
error:
|
|
26
|
+
state.momentum <= 0
|
|
27
|
+
? "Momentum is 0 or negative. Cannot burn."
|
|
28
|
+
: "Momentum not high enough to improve the result.",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const previousMomentum = state.momentum;
|
|
33
|
+
const previousResult = last.result;
|
|
34
|
+
|
|
35
|
+
// Undo exactly what the original result applied, then apply the
|
|
36
|
+
// upgraded result's effects (bond/disposition/momentum gains).
|
|
37
|
+
revertConsequences(state, last.deltas);
|
|
38
|
+
const upgraded = { ...last, result: upgrade };
|
|
39
|
+
const { consequences, clockEvents } = applyConsequences(
|
|
40
|
+
state,
|
|
41
|
+
upgraded,
|
|
42
|
+
last.position,
|
|
43
|
+
last.effect,
|
|
44
|
+
last.targetNpcId,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Burning always resets momentum, overriding any gain from the upgrade.
|
|
48
|
+
state.momentum = MOMENTUM_RESET;
|
|
49
|
+
state.lastRoll = null;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
burned: true,
|
|
53
|
+
previousMomentum,
|
|
54
|
+
newMomentum: MOMENTUM_RESET,
|
|
55
|
+
previousResult: RESULT_LABELS[previousResult],
|
|
56
|
+
newResult: RESULT_LABELS[upgrade],
|
|
57
|
+
newResultCode: upgrade,
|
|
58
|
+
challengeDice: [last.c1, last.c2],
|
|
59
|
+
revertedConsequences: true,
|
|
60
|
+
consequences,
|
|
61
|
+
clockEvents,
|
|
62
|
+
currentHealth: state.health,
|
|
63
|
+
currentSpirit: state.spirit,
|
|
64
|
+
currentSupply: state.supply,
|
|
65
|
+
crisisMode: state.crisisMode,
|
|
66
|
+
gameOver: state.gameOver,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { tool } from "@alexkroman1/aai";
|
|
2
|
+
import { getGameState, stateSummary } from "../shared.ts";
|
|
3
|
+
|
|
4
|
+
export const checkState = tool({
|
|
5
|
+
description:
|
|
6
|
+
"Returns the full current game state. Call this at the start of every turn, before narrating or rolling, and treat the returned values as ground truth — never guess or remember stats from previous turns.",
|
|
7
|
+
async execute(_args, ctx) {
|
|
8
|
+
const state = getGameState(ctx);
|
|
9
|
+
return stateSummary(state);
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { tool } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { GameState } from "../shared.ts";
|
|
4
|
+
import { loadState, saveGameState, saveSlotKey, saveSlotParam } from "../shared.ts";
|
|
5
|
+
|
|
6
|
+
// Requires storage — `aai storage enable` (or DATABASE_URL in .env under
|
|
7
|
+
// `aai dev`); the rest of the game works without it.
|
|
8
|
+
export const loadGame = tool({
|
|
9
|
+
description: "Load a previously saved game.",
|
|
10
|
+
parameters: z.object({ slot: saveSlotParam }),
|
|
11
|
+
async execute(args, ctx) {
|
|
12
|
+
const saved = await loadState<GameState>(ctx, saveSlotKey(args.slot));
|
|
13
|
+
if (!saved) return { error: "No save found." };
|
|
14
|
+
saveGameState(ctx, saved);
|
|
15
|
+
return {
|
|
16
|
+
loaded: true,
|
|
17
|
+
playerName: saved.playerName,
|
|
18
|
+
characterConcept: saved.characterConcept,
|
|
19
|
+
settingGenre: saved.settingGenre,
|
|
20
|
+
sceneCount: saved.sceneCount,
|
|
21
|
+
currentLocation: saved.currentLocation,
|
|
22
|
+
initialized: saved.initialized,
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { tool } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { checkChaosInterrupt, creativitySeed, d, getGameState, pick } from "../shared.ts";
|
|
4
|
+
|
|
5
|
+
const REACTIONS = [
|
|
6
|
+
"Acts on their agenda",
|
|
7
|
+
"Reveals a secret",
|
|
8
|
+
"Makes a demand",
|
|
9
|
+
"Offers unexpected help",
|
|
10
|
+
"Betrays expectations",
|
|
11
|
+
"Shows vulnerability",
|
|
12
|
+
"Escalates the conflict",
|
|
13
|
+
"Withdraws or retreats",
|
|
14
|
+
"Changes their stance",
|
|
15
|
+
"Introduces a new complication",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const TWISTS = [
|
|
19
|
+
"A hidden connection is revealed",
|
|
20
|
+
"The environment shifts dramatically",
|
|
21
|
+
"An NPC's true motives surface",
|
|
22
|
+
"Time pressure intensifies",
|
|
23
|
+
"An old enemy reappears",
|
|
24
|
+
"A resource is discovered or lost",
|
|
25
|
+
"The rules of the world bend",
|
|
26
|
+
"An alliance fractures",
|
|
27
|
+
"A prophecy or omen manifests",
|
|
28
|
+
"The stakes escalate unexpectedly",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const ACTIONS = [
|
|
32
|
+
"Abandon",
|
|
33
|
+
"Advance",
|
|
34
|
+
"Assault",
|
|
35
|
+
"Betray",
|
|
36
|
+
"Block",
|
|
37
|
+
"Bolster",
|
|
38
|
+
"Breach",
|
|
39
|
+
"Capture",
|
|
40
|
+
"Challenge",
|
|
41
|
+
"Change",
|
|
42
|
+
"Clash",
|
|
43
|
+
"Command",
|
|
44
|
+
"Compel",
|
|
45
|
+
"Conceal",
|
|
46
|
+
"Confront",
|
|
47
|
+
"Control",
|
|
48
|
+
"Corrupt",
|
|
49
|
+
"Create",
|
|
50
|
+
"Deceive",
|
|
51
|
+
"Defend",
|
|
52
|
+
"Defy",
|
|
53
|
+
"Deliver",
|
|
54
|
+
"Demand",
|
|
55
|
+
"Depart",
|
|
56
|
+
"Destroy",
|
|
57
|
+
"Distract",
|
|
58
|
+
"Endure",
|
|
59
|
+
"Escape",
|
|
60
|
+
"Explore",
|
|
61
|
+
"Falter",
|
|
62
|
+
"Find",
|
|
63
|
+
"Follow",
|
|
64
|
+
"Forge",
|
|
65
|
+
"Forsake",
|
|
66
|
+
"Gather",
|
|
67
|
+
"Guard",
|
|
68
|
+
"Guide",
|
|
69
|
+
"Harm",
|
|
70
|
+
"Hide",
|
|
71
|
+
"Hold",
|
|
72
|
+
"Hunt",
|
|
73
|
+
"Investigate",
|
|
74
|
+
"Journey",
|
|
75
|
+
"Learn",
|
|
76
|
+
"Leave",
|
|
77
|
+
"Locate",
|
|
78
|
+
"Lose",
|
|
79
|
+
"Manipulate",
|
|
80
|
+
"Move",
|
|
81
|
+
"Oppose",
|
|
82
|
+
"Overwhelm",
|
|
83
|
+
"Persevere",
|
|
84
|
+
"Plunder",
|
|
85
|
+
"Preserve",
|
|
86
|
+
"Protect",
|
|
87
|
+
"Rage",
|
|
88
|
+
"Reach",
|
|
89
|
+
"Reclaim",
|
|
90
|
+
"Refuse",
|
|
91
|
+
"Reject",
|
|
92
|
+
"Release",
|
|
93
|
+
"Repair",
|
|
94
|
+
"Resist",
|
|
95
|
+
"Restore",
|
|
96
|
+
"Reveal",
|
|
97
|
+
"Risk",
|
|
98
|
+
"Salvage",
|
|
99
|
+
"Scheme",
|
|
100
|
+
"Search",
|
|
101
|
+
"Secure",
|
|
102
|
+
"Seize",
|
|
103
|
+
"Serve",
|
|
104
|
+
"Share",
|
|
105
|
+
"Shatter",
|
|
106
|
+
"Shelter",
|
|
107
|
+
"Strengthen",
|
|
108
|
+
"Summon",
|
|
109
|
+
"Surrender",
|
|
110
|
+
"Surround",
|
|
111
|
+
"Survive",
|
|
112
|
+
"Swear",
|
|
113
|
+
"Threaten",
|
|
114
|
+
"Track",
|
|
115
|
+
"Transform",
|
|
116
|
+
"Trap",
|
|
117
|
+
"Traverse",
|
|
118
|
+
"Uncover",
|
|
119
|
+
"Uphold",
|
|
120
|
+
"Weaken",
|
|
121
|
+
"Withdraw",
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const THEMES = [
|
|
125
|
+
"Ancestor",
|
|
126
|
+
"Ash",
|
|
127
|
+
"Beast",
|
|
128
|
+
"Blood",
|
|
129
|
+
"Bone",
|
|
130
|
+
"Burden",
|
|
131
|
+
"Communion",
|
|
132
|
+
"Corruption",
|
|
133
|
+
"Crown",
|
|
134
|
+
"Darkness",
|
|
135
|
+
"Death",
|
|
136
|
+
"Debt",
|
|
137
|
+
"Decay",
|
|
138
|
+
"Despair",
|
|
139
|
+
"Divinity",
|
|
140
|
+
"Doom",
|
|
141
|
+
"Dream",
|
|
142
|
+
"Dynasty",
|
|
143
|
+
"Eclipse",
|
|
144
|
+
"Exile",
|
|
145
|
+
"Faith",
|
|
146
|
+
"Fate",
|
|
147
|
+
"Flesh",
|
|
148
|
+
"Fury",
|
|
149
|
+
"Grace",
|
|
150
|
+
"Grief",
|
|
151
|
+
"Guilt",
|
|
152
|
+
"Heritage",
|
|
153
|
+
"Hollow",
|
|
154
|
+
"Honor",
|
|
155
|
+
"Horror",
|
|
156
|
+
"Hunger",
|
|
157
|
+
"Iron",
|
|
158
|
+
"Judgment",
|
|
159
|
+
"Kingdom",
|
|
160
|
+
"Knowledge",
|
|
161
|
+
"Legacy",
|
|
162
|
+
"Loss",
|
|
163
|
+
"Madness",
|
|
164
|
+
"Memory",
|
|
165
|
+
"Mercy",
|
|
166
|
+
"Monster",
|
|
167
|
+
"Mystery",
|
|
168
|
+
"Night",
|
|
169
|
+
"Oath",
|
|
170
|
+
"Omen",
|
|
171
|
+
"Order",
|
|
172
|
+
"Passage",
|
|
173
|
+
"Peril",
|
|
174
|
+
"Plague",
|
|
175
|
+
"Power",
|
|
176
|
+
"Pride",
|
|
177
|
+
"Prophecy",
|
|
178
|
+
"Rebirth",
|
|
179
|
+
"Relic",
|
|
180
|
+
"Rot",
|
|
181
|
+
"Ruin",
|
|
182
|
+
"Sacrifice",
|
|
183
|
+
"Scar",
|
|
184
|
+
"Secret",
|
|
185
|
+
"Shadow",
|
|
186
|
+
"Shard",
|
|
187
|
+
"Silence",
|
|
188
|
+
"Sorrow",
|
|
189
|
+
"Spirit",
|
|
190
|
+
"Splendor",
|
|
191
|
+
"Storm",
|
|
192
|
+
"Throne",
|
|
193
|
+
"Time",
|
|
194
|
+
"Treachery",
|
|
195
|
+
"Truth",
|
|
196
|
+
"Valor",
|
|
197
|
+
"Vengeance",
|
|
198
|
+
"War",
|
|
199
|
+
"Waste",
|
|
200
|
+
"Winter",
|
|
201
|
+
"Wisdom",
|
|
202
|
+
"Wound",
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
export const oracle = tool({
|
|
206
|
+
description:
|
|
207
|
+
"Consult the oracle for narrative inspiration. Generates random prompts from thematic tables.",
|
|
208
|
+
parameters: z.object({
|
|
209
|
+
type: z
|
|
210
|
+
.enum(["action_theme", "npc_reaction", "scene_twist", "yes_no", "chaos_check"])
|
|
211
|
+
.describe("Type of oracle consultation"),
|
|
212
|
+
}),
|
|
213
|
+
async execute(args, ctx) {
|
|
214
|
+
if (args.type === "yes_no") {
|
|
215
|
+
const roll = d(6);
|
|
216
|
+
const answer = roll <= 2 ? "No" : roll <= 4 ? "Yes, but with a complication" : "Yes";
|
|
217
|
+
return { type: "yes_no", roll, answer };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (args.type === "chaos_check") {
|
|
221
|
+
// getGameState returns the live state object — mutations stick.
|
|
222
|
+
const state = getGameState(ctx);
|
|
223
|
+
const interrupt = checkChaosInterrupt(state);
|
|
224
|
+
return {
|
|
225
|
+
type: "chaos_check",
|
|
226
|
+
chaosFactor: state.chaosFactor,
|
|
227
|
+
interrupted: Boolean(interrupt),
|
|
228
|
+
interruptType: interrupt,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (args.type === "npc_reaction") {
|
|
233
|
+
return { type: "npc_reaction", reaction: pick(REACTIONS) };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (args.type === "scene_twist") {
|
|
237
|
+
return { type: "scene_twist", twist: pick(TWISTS) };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// action_theme (default)
|
|
241
|
+
return {
|
|
242
|
+
type: "action_theme",
|
|
243
|
+
action: pick(ACTIONS),
|
|
244
|
+
theme: pick(THEMES),
|
|
245
|
+
seed: creativitySeed(),
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { tool } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getGameState, saveSlotKey, saveSlotParam, saveState } from "../shared.ts";
|
|
4
|
+
|
|
5
|
+
// Requires storage — `aai storage enable` (or DATABASE_URL in .env under
|
|
6
|
+
// `aai dev`); the rest of the game works without it.
|
|
7
|
+
export const saveGame = tool({
|
|
8
|
+
description: "Save current game to persistent storage.",
|
|
9
|
+
parameters: z.object({ slot: saveSlotParam }),
|
|
10
|
+
async execute(args, ctx) {
|
|
11
|
+
const state = getGameState(ctx);
|
|
12
|
+
await saveState(ctx, saveSlotKey(args.slot), state);
|
|
13
|
+
return {
|
|
14
|
+
saved: true,
|
|
15
|
+
slot: args.slot ?? "autosave",
|
|
16
|
+
name: state.playerName,
|
|
17
|
+
scene: state.sceneCount,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
});
|