Skip to content

Commit c2af928

Browse files
committed
docs: add Draw Steel system module implementation plan
Comprehensive plan for making the Draw Steel game system fully functional in Chronicle — covering CORS admin whitelist, dynamic Foundry actor types, manifest expansion with full field mappings, reference data seeding, and generic adapter enhancements. https://claude.ai/code/session_01NnKM8NqJzGz8756CZ4PEGg
1 parent 7965c9e commit c2af928

1 file changed

Lines changed: 222 additions & 0 deletions

File tree

plan.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Draw Steel System Module — Full Implementation Plan
2+
3+
## Overview
4+
5+
Make the Draw Steel game system a complete, self-contained, "containerized" system module
6+
for Chronicle — including website reference content, character/entity presets with full
7+
Foundry VTT field mappings, and all necessary infrastructure fixes. Everything should be
8+
dynamic and modular: a system manifest drives all behavior, and the Foundry module reads
9+
field definitions from the API. No hard-coded adapters.
10+
11+
---
12+
13+
## Phase 1: Infrastructure Fixes (Bugs & Architecture)
14+
15+
### 1A. CORS — Admin-managed origin whitelist
16+
17+
**Problem:** `AllowedOrigins` in `app.go:137` is hardcoded to only `Config.BaseURL`. Foundry
18+
VTT (on a different port/origin) gets CORS-blocked.
19+
20+
**Solution:** Store allowed CORS origins in the `site_settings` table (key:
21+
`cors.allowed_origins`). Admin can manage via the admin panel. The CORS middleware reads
22+
from DB on startup and reloads on change.
23+
24+
**Files to modify:**
25+
- `internal/plugins/settings/model.go` — Add `KeyCORSAllowedOrigins` constant
26+
- `internal/plugins/settings/service.go` — Add `GetCORSOrigins()` / `UpdateCORSOrigins()` methods
27+
- `internal/plugins/settings/repository.go` — SQL for reading/writing the setting
28+
- `internal/plugins/settings/handler.go` — HTTP handler for CORS settings form
29+
- `internal/plugins/settings/routes.go` — Register new routes
30+
- `internal/plugins/admin/handler.go` — Add API settings page handler
31+
- `internal/plugins/admin/api_settings.templ`**New file**: Admin API settings page with
32+
CORS origin whitelist management (add/remove origins)
33+
- `internal/middleware/cors.go` — Accept a `OriginProvider` function that returns current
34+
origins list (called per-request, cached briefly)
35+
- `internal/app/app.go` — Wire up CORS middleware with DB-backed origin provider that
36+
includes `Config.BaseURL` + DB origins
37+
38+
### 1B. Foundry actor type — make it dynamic via manifest
39+
40+
**Problem:** `actor-sync.mjs` hardcodes `actor.type !== 'character'` in multiple places.
41+
Draw Steel uses actor type `'hero'`, not `'character'`. This must be dynamic.
42+
43+
**Solution:** Add `foundry_actor_type` field to manifest `EntityPresetDef` and export it
44+
via the character-fields API. The generic adapter and actor-sync use it instead of
45+
hardcoding `'character'`.
46+
47+
**Files to modify:**
48+
- `internal/systems/manifest.go`:
49+
- Add `FoundryActorType string` to `EntityPresetDef` struct (`json:"foundry_actor_type,omitempty"`)
50+
- Add `FoundryActorType string` to `CharacterFieldsResponse` struct
51+
- Include it in `CharacterFieldsForAPI()` and `ItemFieldsForAPI()` output
52+
- `internal/systems/drawsteel/manifest.json` — Add `"foundry_actor_type": "hero"` to
53+
the character preset
54+
- `internal/systems/dnd5e/manifest.json` — Add `"foundry_actor_type": "character"` to
55+
character preset (explicit)
56+
- `internal/systems/pathfinder2e/manifest.json` — Add `"foundry_actor_type": "character"`
57+
- `foundry-module/scripts/adapters/generic-adapter.mjs` — Read `foundry_actor_type` from
58+
API response, expose it on the returned adapter object, default to `'character'`
59+
- `foundry-module/scripts/actor-sync.mjs` — Replace all `actor.type !== 'character'`
60+
checks with `actor.type !== this._adapter.actorType`, where `actorType` comes from the
61+
adapter. Also fix `Actor.create()` to use the correct type. Fallback to `'character'`.
62+
63+
### 1C. SYSTEM_MAP_FALLBACK key fix
64+
65+
**Problem:** `sync-manager.mjs` line 21 has `drawsteel: 'drawsteel'` but the Foundry
66+
system ID is `'draw-steel'` (hyphenated), not `'drawsteel'`.
67+
68+
**Fix:** Change the key to `'draw-steel': 'drawsteel'`.
69+
70+
**File:** `foundry-module/scripts/sync-manager.mjs` line 21
71+
72+
---
73+
74+
## Phase 2: Draw Steel Manifest Expansion
75+
76+
### 2A. Character preset — full field set with Foundry paths
77+
78+
Expand the `drawsteel-character` entity preset from 4 fields to a complete Draw Steel
79+
character sheet. All fields that map to Foundry data get `foundry_path` annotations.
80+
81+
**Fields to add (with Foundry paths):**
82+
83+
| Key | Label | Type | Foundry Path | Writable |
84+
|-----|-------|------|-------------|----------|
85+
| class | Class | string | (none — derived from class item) ||
86+
| subclass | Subclass | string | (none — derived from subclass item) ||
87+
| level | Level | number | system.details.level (if exists) | yes |
88+
| ancestry | Ancestry | string | (none — derived from ancestry item) ||
89+
| career | Career | string | (none — derived from career item) ||
90+
| might | Might | number | system.characteristics.might.value | yes |
91+
| agility | Agility | number | system.characteristics.agility.value | yes |
92+
| reason | Reason | number | system.characteristics.reason.value | yes |
93+
| intuition | Intuition | number | system.characteristics.intuition.value | yes |
94+
| presence | Presence | number | system.characteristics.presence.value | yes |
95+
| stamina_current | Current Stamina | number | system.stamina.value | yes |
96+
| stamina_max | Max Stamina | number | system.stamina.max | no (derived) |
97+
| stamina_temp | Temporary Stamina | number | system.stamina.temporary | yes |
98+
| recoveries_current | Recoveries | number | system.recoveries.value | yes |
99+
| recoveries_max | Max Recoveries | number | system.recoveries.max | no (derived) |
100+
| heroic_resource | Heroic Resource | number | system.hero.primary.value | yes |
101+
| surges | Surges | number | system.hero.surges | yes |
102+
| victories | Victories | number | system.hero.victories | yes |
103+
| xp | Experience | number | system.hero.xp | yes |
104+
| renown | Renown | number | system.hero.renown | yes |
105+
| wealth | Wealth | number | system.hero.wealth | yes |
106+
| speed | Speed | number | system.movement.value | no (derived) |
107+
| stability | Stability | number | system.combat.stability | no (derived) |
108+
| size | Size | number | system.combat.size.value | no |
109+
110+
**Sections:** Group characteristics under "Characteristics", stamina/recoveries under
111+
"Resources", combat stats under "Combat", victories/xp/renown under "Progression".
112+
113+
**File:** `internal/systems/drawsteel/manifest.json`
114+
115+
### 2B. Creature preset — add with proper Draw Steel terminology
116+
117+
Add a `drawsteel-creature` entity preset for NPCs/monsters.
118+
119+
**Fields:**
120+
- level (number), role (string: "Ambusher", "Artillery", "Brute", etc.),
121+
ev (number, Encounter Value), role_type (string: "Minion", "Standard", "Elite", "Solo"),
122+
stamina (number), speed (number), stability (number), size (string),
123+
free_strike_damage (number)
124+
125+
### 2C. Kit preset — add Foundry paths
126+
127+
The existing kit preset fields are fine but need `foundry_path` annotations where applicable.
128+
Kits are items in Foundry Draw Steel, so these map to item system data.
129+
130+
### 2D. Update manifest status
131+
132+
Change `"status": "coming_soon"` to `"status": "available"`.
133+
134+
---
135+
136+
## Phase 3: Reference Data Foundation
137+
138+
### 3A. Create data directory structure
139+
140+
Create `internal/systems/drawsteel/data/` with JSON files for each category.
141+
142+
### 3B. Seed initial reference data
143+
144+
Using correct Draw Steel terminology and CC-BY-4.0 content:
145+
146+
- `data/abilities.json` — Seed with 5-10 representative abilities across different classes
147+
(e.g., a Tactician, Shadow, Fury, and Elementalist ability each). Fields: name, class,
148+
level, type (action/maneuver/triggered), keywords, description.
149+
150+
- `data/creatures.json` — Seed with 5-8 creatures covering different roles and levels.
151+
Fields: name, level, role, role_type, ev, stamina, speed, description.
152+
153+
- `data/ancestries.json` — Seed with the core ancestries (Human, Dwarf, Elf (Wode/High),
154+
Orc, Hakaan, Memonek, Revenant, Polder, Dragon Knight). Fields: name, size, speed,
155+
description.
156+
157+
---
158+
159+
## Phase 4: Generic Adapter Enhancement
160+
161+
The user explicitly wants everything dynamic — no hand-written adapters. The generic
162+
adapter must be enhanced to handle system-specific details like actor type.
163+
164+
### 4A. Enhance generic adapter
165+
166+
**File:** `foundry-module/scripts/adapters/generic-adapter.mjs`
167+
168+
- Read `foundry_actor_type` from the API response and include it in the returned object
169+
as `actorType` (default: `'character'`)
170+
- This is already largely handled by the existing generic adapter; just add the
171+
`actorType` property
172+
173+
### 4B. Actor sync — use adapter's actorType
174+
175+
**File:** `foundry-module/scripts/actor-sync.mjs`
176+
177+
- `_handleCreateActor`: Replace `actor.type !== 'character'` with
178+
`actor.type !== (this._adapter.actorType || 'character')`
179+
- `_onCharacterCreated`: Replace `type: 'character'` with
180+
`type: this._adapter.actorType || 'character'`
181+
- `getSyncedActors`: Replace `.filter(a => a.type === 'character')` with
182+
`.filter(a => a.type === (this._adapter?.actorType || 'character'))`
183+
184+
---
185+
186+
## Phase 5: Documentation Updates
187+
188+
### 5A. Update `.ai.md`
189+
190+
Update `internal/systems/drawsteel/.ai.md` to reflect completed work — mark handlers,
191+
data files, and routes as done.
192+
193+
### 5B. Update `.ai/status.md`
194+
195+
Add sprint entry documenting the Draw Steel system completion.
196+
197+
### 5C. Update `.ai/todo.md`
198+
199+
Mark Draw Steel tasks as complete, add any follow-up items.
200+
201+
---
202+
203+
## Execution Order
204+
205+
1. Phase 1C (trivial fix — SYSTEM_MAP_FALLBACK key)
206+
2. Phase 1B (foundry_actor_type — manifest struct + API + adapter + actor-sync)
207+
3. Phase 2A-D (manifest expansion — all entity presets with Foundry paths)
208+
4. Phase 3A-B (reference data files)
209+
5. Phase 1A (CORS admin whitelist — biggest infrastructure change)
210+
6. Phase 4A-B (generic adapter enhancement)
211+
7. Phase 5 (documentation)
212+
213+
## Risk Notes
214+
215+
- **Draw Steel Foundry paths may change** — The Draw Steel Foundry system is actively
216+
developed (v0.11.1). Paths like `system.hero.primary.value` could shift. The generic
217+
adapter approach mitigates this since path definitions live in the manifest, not in code.
218+
- **Class/subclass/ancestry/career** are item-derived in Foundry — these can't be set via
219+
`actor.update()` in Foundry, so they're included as read-only Chronicle fields with no
220+
`foundry_path` (sync is name/description only for these).
221+
- **The `section` field** on FieldDef is used in manifests but not in the Go struct. It's
222+
preserved through JSON round-tripping. No changes needed.

0 commit comments

Comments
 (0)