Skip to content

Commit c48fc90

Browse files
ShemJMclaude
andauthored
Merge pull request #6 from ShemJM/claude/agent-game-creation-plan-09a1jz
Phase D: adventure_demo — the full RPG loop end-to-end + docs pass games/adventure_demo.rpgc chains every v5 system in one game: quest NPC grants gold -> shop (buy potion + sword) -> armory equips it (has_item branch, +6 atk) -> unwinnable-without-gear Ogre fight using battle_item mid-battle (HP totals chosen so the outcome is variance-proof) -> win branch breaks the gate seal -> transfer to the sanctum -> claim the crystal. The scenario asserts gold/stock/stats/HP bounds/battle result/ switches/transfer position at every step. Docs: CLAUDE.md points to it as the canonical full-loop example; CURRENT_STATE.md moves party/shops/battle to "what works" with the remaining gaps listed; ROADMAP.md marks the Phase 7 v1 subset done. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012y1ompK7McTvuvy5jmVcio
2 parents e675b8f + 96f4994 commit c48fc90

27 files changed

Lines changed: 2343 additions & 44 deletions

CLAUDE.md

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ rpg-creator is an RPG Maker-style tool built in **Godot 4.6 / GDScript**. Humans
1212

1313
Setup: `make setup` resolves or downloads a Godot 4.6 binary into `bin/godot` (a SessionStart hook does this automatically in Claude Code web sessions; it needs network access to `github.com/godotengine`, or set `$GODOT` to an existing binary). There is no build step — GDScript is interpreted.
1414

15-
Canonical worked example: `games/lost_crystal.rpgm` + `games/lost_crystal_scenario.json`.
15+
Canonical worked examples: `games/lost_crystal.rpgm` (dialogue/switch adventure) and `games/adventure_demo.rpgc` (full RPG loop: quest → shop → equip → boss battle with a mid-fight item → transfer), each with a passing `*_scenario.json`.
1616

1717
## Headless CLI
1818

@@ -36,10 +36,11 @@ Written/read by `ProjectState.serialize()/deserialize()` (`scripts/autoloads/pro
3636

3737
```json
3838
{
39-
"version": 4,
39+
"version": 5,
4040
"maps": [ ... ],
4141
"tileset": [ ... ], // optional — omit to get the default 5-tile set
4242
"player_graphic": null, // optional charset; null = colored-block player
43+
"system": { "starting_party": [0], "starting_gold": 0 }, // optional
4344
"database": { "actors": [], "classes": [], "items": [], "equipment": [] }
4445
}
4546
```
@@ -92,7 +93,7 @@ Written/read by `ProjectState.serialize()/deserialize()` (`scripts/autoloads/pro
9293
| 1 | SHOW_CHOICES | `{ "choices": ["Yes","No"], "cancel_index": -1 }` |
9394
| 2 | CONTROL_SWITCHES | `{ "ids": [1], "value": true }` |
9495
| 3 | CONTROL_VARIABLES | `{ "ids": [2], "op": "set"\|"add"\|"sub"\|"mul"\|"div", "value": 5 }` |
95-
| 4 | CONDITIONAL_BRANCH | `{ "condition_type": "switch"\|"variable_gte"\|"self_switch", "id": 1, "value": true, "commands_if": [...], "commands_else": [...] }`for `switch`, `value` is bool; for `variable_gte`, condition is variable[id] ≥ value; for `self_switch`, `value` is the letter (`"A"`) |
96+
| 4 | CONDITIONAL_BRANCH | `{ "condition_type": "switch"\|"variable_gte"\|"self_switch"\|"gold_gte"\|"has_item", "id": 1, "value": true, "commands_if": [...], "commands_else": [...] }``switch`: `value` bool; `variable_gte`: variable[id] ≥ value; `self_switch`: `value` is the letter; `gold_gte`: gold ≥ value; `has_item`: `{ "kind": "item"\|"equip", "id": n, "value": min count }` |
9697
| 5 | TRANSFER_PLAYER | `{ "map_id": 1, "x": 5, "y": 7 }` |
9798
| 6 | SET_SELF_SWITCH | `{ "letter": "A", "value": true }` |
9899
| 7 | WAIT | `{ "frames": 60 }` |
@@ -104,12 +105,26 @@ Written/read by `ProjectState.serialize()/deserialize()` (`scripts/autoloads/pro
104105
| 13 | JUMP_TO_LABEL | `{ "name": "loop" }` |
105106
| 14 | GAME_OVER | `{}` |
106107
| 15 | MOVE_ROUTE | `{ "target": "player"\|"this", "steps": ["up","down","left","right","wait","face_up","face_down","face_left","face_right","turn_toward_player"], "wait_for_completion": true }` |
108+
| 16 | CHANGE_GOLD | `{ "op": "add"\|"sub"\|"set", "value": 100 }` — gold clamps at 0 |
109+
| 17 | CHANGE_ITEMS | `{ "kind": "item"\|"equip", "id": 0, "op": "add"\|"sub"\|"set", "count": 1 }` — stock clamps at 0 |
110+
| 18 | CHANGE_HP | `{ "actor_id": -1 (whole party) \| id, "op": "add"\|"sub"\|"set", "value": 20, "allow_ko": false }` — without `allow_ko` HP floors at 1; with it, a full party wipe triggers game over |
111+
| 19 | CHANGE_EQUIPMENT | `{ "actor_id": 0, "slot": "weapon"\|"head"\|"body"\|"accessory", "equip_id": 3 }``-1` unequips; equipping consumes the piece from equip stock (grant it with CHANGE_ITEMS `kind:"equip"` first), unequipping returns it |
112+
| 20 | USE_ITEM | `{ "item_id": 0, "actor_id": 0 }` — applies the item's `effect` `{"hp": n, "mp": n}` restore; consumables decrement; no-op (traced `ok:false`) if out of stock |
113+
| 21 | SHOP_PROCESSING | `{ "entries": [ { "kind": "item"\|"equip", "id": 0, "price": 30 } ] }``price` optional (defaults to database price); sell price = floor(db price / 2); **blocks the event until the shop closes** — drive it with the `shop_buy`/`shop_sell`/`shop_close` scenario actions |
114+
| 22 | BATTLE_PROCESSING | `{ "enemies": [enemy_id, ...], "can_flee": true, "commands_win": [...], "commands_lose": [...] }` — blocks until the battle ends; `win` splices `commands_win`, `lose` splices `commands_lose` (**empty `commands_lose` = game over**), `flee` continues past the command |
107115

108116
Follow-up branching after SHOW_CHOICES: the chosen index is written to **variable 0** — branch with CONDITIONAL_BRANCH on `condition_type: "variable"`. There are **100 switches and 100 variables** (ids 0–99), reset each play-test. Self-switches are per-event letters A–D.
109117

110-
### Database (authoring-only for now — not consumed at runtime)
118+
### Database & party (live at runtime since v5)
111119

112-
`actors` (`actor_name`, `class_id`, `initial_level`, `stats`, `graphic`, `note`), `classes` (`class_name`, `stats`, `note`), `items` (`item_name`, `description`, `price`, `consumable`, `effect`), `equipment` (`equip_name`, `kind`: `"weapon"`/`"armor"`, `slot`, `price`, `stat_mods`, `note`). `stats`/`stat_mods` is a flat block: `max_hp, max_mp, atk, def, mat, mdf, agi, luk`. Database ids: unique, conventionally max+1.
120+
`actors` (`actor_name`, `class_id`, `initial_level`, `stats`, `graphic`, `note`), `classes` (`class_name`, `stats`, `note`), `items` (`item_name`, `description`, `price`, `consumable`, `effect`: `{"hp": n, "mp": n}` restore), `equipment` (`equip_name`, `kind`: `"weapon"`/`"armor"`, `slot`: `"weapon"|"head"|"body"|"accessory"`, `price`, `stat_mods`, `note`), `enemies` (`enemy_name`, `stats`, `gold_reward`, `item_rewards`: `[{kind, id, count}]` — deterministic, no drop chances). `stats`/`stat_mods` is a flat block: `max_hp, max_mp, atk, def, mat, mdf, agi, luk`. Database ids: unique, conventionally max+1.
121+
122+
Runtime party rules:
123+
124+
- Play-test start builds the party from `system.starting_party` (default: the lowest actor id) with `system.starting_gold` (default 0). Party membership is fixed during play (no add/remove command yet).
125+
- **Effective stats = actor base `stats` + Σ equipped `stat_mods`** (class stats are authoring-only; no level growth yet). HP/MP start at computed max.
126+
- Items and equipment have **separate id spaces and stock pools** (`kind: "item"` vs `"equip"`).
127+
- All changes are traced (`gold_changed`, `item_changed`, `hp_changed`, `equip_changed`, `item_used`) and visible in the snapshot: `gold`, `inventory`, `equip_inventory`, `party: [{actor_id, name, hp, max_hp, mp, max_mp, stats, equipment}]`.
113128

114129
## Scenario format (`games/*_scenario.json`)
115130

@@ -132,11 +147,33 @@ Follow-up branching after SHOW_CHOICES: the chosen index is written to **variabl
132147
{ "action": "expect_event_erased", "id": 0, "value": true },
133148
{ "action": "expect_event_running", "value": false },
134149
{ "action": "expect_game_over", "value": false },
150+
{ "action": "expect_gold", "value": 40 },
151+
{ "action": "expect_item_count", "kind": "item", "id": 0, "value": 2 },
152+
{ "action": "expect_party_size", "value": 1 },
153+
{ "action": "expect_actor_hp", "actor_id": 0, "value": 90 },
154+
{ "action": "expect_actor_stat", "actor_id": 0, "stat": "atk", "value": 17 },
155+
{ "action": "shop_buy", "index": 0, "count": 1 },
156+
{ "action": "shop_sell", "kind": "item", "id": 0, "count": 1 },
157+
{ "action": "shop_close" },
158+
{ "action": "expect_shop_open", "value": false },
135159
{ "action": "snapshot" }
136160
]
137161
}
138162
```
139163

164+
Shop flow: `interact` with the shop event → `advance_dialogue` past any greeting → the shop opens (`expect_shop_open`) → `shop_buy` (index into the SHOP_PROCESSING entries) / `shop_sell` (any stocked item, half price) → `shop_close` resumes the event. A rejected buy (not enough gold) changes nothing and is traced with `ok: false`.
165+
166+
### Battle (deterministic — design scenarios around these exact rules)
167+
168+
- Party fights with **live HP** (battle damage persists after the battle). Each round waits for one command per living party member in party order — supply them with `battle_attack {"target": enemy_index}`, `battle_item {"item_id", "target_actor_id"?}` (consumes the turn), or `battle_flee` — then resolves synchronously.
169+
- Turn order: `agi` descending; ties → party before enemies, then lower index. Enemy AI: basic attack on the lowest-index living party member. A dead attack target retargets to the first living one.
170+
- **Damage** = `max(1, base * rng(90..110) / 100)` where `base = max(1, atk - def / 2)` (integer division). That rng roll is the only randomness (seed 0 unless the scenario sets `rng_seed`), so damage varies ±10% — bound assertions with `expect_enemy_hp {"index", "lte"}` / `expect_actor_hp {"gte"}`, or design HP totals so the outcome is variance-proof (e.g. 20 HP enemy vs 10–13 damage = always exactly 2 hits).
171+
- `battle_flee` succeeds iff `can_flee` (a failed attempt wastes the turn). Win grants every enemy's `gold_reward` + `item_rewards`.
172+
- Assertions: `expect_battle_active {value}`, `expect_battle_result {value: "win"|"lose"|"flee"}` (persists after the battle), `expect_enemy_hp {index, value|lte}`. Snapshot carries `battle: {active, round, pending_actor_id, enemies, last_result}`; the trace logs `battle_started/round/action/ended` with per-action damage.
173+
- Worked examples: `games/battle_demo.rpgc` with `battle_demo_scenario.json` (flee, then a 2-round win) and `battle_lose_scenario.json` (unwinnable → game over).
174+
175+
Optional top-level `"rng_seed"` (int): gameplay randomness flows through one seeded RNG (`GameState.rng`, default seed 0), so runs are always reproducible — set `rng_seed` only to explore alternate outcomes. `expect_actor_hp` also accepts `"gte"` instead of `"value"`.
176+
140177
An optional top-level `"timeout_frames"` (default 6000) fails the run with a `timeout` assertion if it doesn't finish in time. `expect_dialogue` matches against **all** dialogue seen so far (lines of one box are joined with `\n`).
141178

142179
Timing conventions that make scenarios deterministic:
@@ -146,7 +183,7 @@ Timing conventions that make scenarios deterministic:
146183
- One `advance_dialogue` per SHOW_TEXT box; dialogue must be advanced before the event continues.
147184
- Facing vectors: right `(1,0)`, left `(-1,0)`, up `(0,-1)`, down `(0,1)`.
148185

149-
Results JSON: `{ passed, failed, total, assertions: [{pass, message}], trace: [...], snapshot }`. The `trace` array logs every event start/finish, command, switch/variable/self-switch change, transfer, dialogue line, choice, game over, and player move — read it to debug why an assertion failed. Final `snapshot`: `{ map_id, map_name, player_grid, player_facing, event_facing, event_running, events_erased, switches_on, variables }` (only non-false switches / non-zero variables listed).
186+
Results JSON: `{ passed, failed, total, assertions: [{pass, message}], trace: [...], snapshot }`. The `trace` array logs every event start/finish, command, switch/variable/self-switch change, transfer, dialogue line, choice, game over, player move, and every gold/item/HP/MP/equipment change — read it to debug why an assertion failed. Final `snapshot`: `{ map_id, map_name, player_grid, player_facing, event_facing, event_running, events_erased, switches_on, variables, gold, inventory, equip_inventory, party }` (only non-false switches / non-zero variables/stock listed).
150187

151188
`make test-scenarios` runs every scenario in `games/` inside **one** engine boot (`--test-all games`), so the suite stays fast as games accumulate.
152189

CURRENT_STATE.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
- **Game over**`GAME_OVER` ends the play-test immediately.
3838
- **Parallel events** — pages with the Parallel trigger run in their own runner without blocking the player, looping while their conditions hold.
3939
- **Page re-evaluation** — event pages re-evaluate when switches, variables, or self-switches change; visuals update, parallel runners start/stop, and autorun pages fire when they *become* active (edge-triggered).
40+
- **Party & resources** — starting party from the `system` block, live HP/MP, gold, item/equipment stock, equipment slots that modify effective stats; commands `CHANGE_GOLD`, `CHANGE_ITEMS`, `CHANGE_HP`, `CHANGE_EQUIPMENT`, `USE_ITEM`; branch conditions `gold_gte` / `has_item`.
41+
- **Shops**`SHOP_PROCESSING` opens a buy/sell session (database or override prices, half-price sells) that blocks the event until closed; fully scriptable headlessly.
42+
- **Battle v1**`BATTLE_PROCESSING` runs a deterministic turn-based fight (agi order, ±10% seeded damage variance, enemy AI, items and fleeing, win/lose command branches, gold + item rewards). Managed by `BattleManager` with a minimal reactive `BattleUI`.
4043

4144
### Infrastructure
4245

@@ -55,8 +58,8 @@
5558
## Known gaps / rough edges
5659

5760
- **`PLAY_SE` is a stub** — advances immediately with no audio playback (no BGM/SE yet).
58-
- **Database is authoring-only**actors/classes/items/weapons/armor are stored but not yet consumed at runtime (party, inventory, shops, and combat come in later phases). No skills/enemies/troops yet.
59-
- **No party / inventory / combat**`GameState` still holds only switches/variables; no party, gold, inventory, or battle system yet.
61+
- **Party is fixed at play-test start**no CHANGE_PARTY command yet; class stats are authoring-only (no level growth); no skills, no random encounter zones, no troops table (BATTLE_PROCESSING takes inline enemy ids); item effects are HP/MP restore only.
62+
- **No editor UI yet for the v5 systems**the party/shop/battle commands and enemies table are authorable via JSON (agents) but not yet in the event editor panel / database panel.
6063
- **Map transfer UX** — Transfer Player command works at runtime but there is no editor UI for picking the target map by name.
6164
- **Parallel events + dialogue** — a parallel event showing text while a blocking event also waits on dialogue can cross-talk; parallel pages are best used for switch/variable/wait/move logic.
6265
- **No undo/redo** in the editor.

ROADMAP.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,17 @@ Commands that round out the scripting system:
8484

8585
---
8686

87-
## Phase 7 — Battle System (optional / stretch)
88-
89-
If the project grows to include a full RPG loop:
90-
87+
## Phase 7 — Battle System ✓ (v1 subset)
88+
89+
- [x] Party, gold, inventory, equipment live at runtime (schema v5 `system` block; commands 16–20)
90+
- [x] Shops — `SHOP_PROCESSING` + scriptable buy/sell session
91+
- [x] Turn-based battle with party vs. enemies (`BattleManager`, deterministic seeded damage)
92+
- [x] `BATTLE_PROCESSING` event command with win/lose command branches
93+
- [x] Win/lose conditions wired back to the event system (lose without a branch = game over)
94+
- [x] Enemies database table with gold/item rewards
9195
- [ ] Encounter zones — tile flag triggers a random battle
92-
- [ ] Turn-based battle scene with party vs. enemies
93-
- [ ] Character stats resource (`HP`, `MP`, `ATK`, `DEF`, `SPD`)
94-
- [ ] Skills and items as resources
95-
- [ ] `BATTLE_PROCESSING` event command — start a scripted battle
96-
- [ ] Win/lose conditions wired back to the event system
96+
- [ ] Skills / troops / drop chances / level growth / CHANGE_PARTY
97+
- [ ] Editor UI for the v5 commands and enemies table
9798

9899
---
99100

0 commit comments

Comments
 (0)