Skip to content

Commit e855bb6

Browse files
committed
feat: bidirectional actor/character sync with system adapters (F-4)
Add ActorSync module for bidirectional sync between Foundry Actors and Chronicle character entities. System-specific field mapping via adapters (dnd5e: 15 fields, pf2e: HP/name back only). Dashboard Characters tab with push button for unlinked actors. Delete from Chronicle unlinks rather than deletes Actor to prevent data loss. https://claude.ai/code/session_01XMwxFR8BCi5XvgaSVMSBZB
1 parent 890586a commit e855bb6

11 files changed

Lines changed: 937 additions & 12 deletions

File tree

.ai/status.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@
88
<!-- ====================================================================== -->
99

1010
## Last Updated
11-
2026-03-12 -- **Sprint F-3: System Detection & Character Field Templates.**
11+
2026-03-12 -- **Sprint F-4: Actor ↔ Entity Sync.**
12+
13+
34. **Sprint F-4: Actor ↔ Entity Sync (DONE).**
14+
- **actor-sync.mjs** — New `ActorSync` module class. Bidirectional sync between Foundry Actors (type: character) and Chronicle character entities. Registers `createActor`/`updateActor`/`deleteActor` hooks. Handles `entity.created/updated/deleted` WS messages filtered by character type. Uses `_syncing` guard. `_onCharacterDeleted()` unlinks (unsets flags) rather than deleting Actor.
15+
- **System adapters**`adapters/dnd5e-adapter.mjs` maps 15 D&D 5e fields (ability scores, HP, AC, speed, level, class, race, alignment, proficiency_bonus). `adapters/pf2e-adapter.mjs` maps PF2e fields (ability mods, HP, AC, perception, ancestry, heritage); only pushes HP/name back to Foundry (PF2e derives most values from items/rules).
16+
- **Dashboard Characters tab** — New tab in sync dashboard showing synced/unlinked actors with Push button for manual push. Empty states for no actors, disabled sync, no system match.
17+
- **module.mjs** — Registered `ActorSync` as sync module.
18+
- **TESTING.md** — Added 30+ character sync test items covering both directions, dashboard, adapters, edge cases.
19+
- **Next:** F-5 (NPC Viewer / Hall) or F-6 (Armory / Inventory).
1220

1321
33. **Sprint F-3: System detection & character field templates (DONE).**
1422
- **Server: Manifest expansion** — dnd5e character preset expanded from 4 to 15 fields (added ability scores, HP, AC, speed, proficiency_bonus). New pf2e character preset with 15 PF2e-specific fields (ancestry, heritage, ability mods, perception, etc). Added `CharacterPreset()` method on `SystemManifest`.

.ai/todo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ _Improve Foundry VTT sync fidelity. Add system-aware character sheet sync. Build
286286
- [x] **Sprint F-1: Journal Sync Fidelity** — Multi-page journal sync (split entity `entry_html` by headings into Foundry pages, concatenate pages back on Foundry→Chronicle). Ownership change hook (detect ownership changes in `updateJournalEntry` hook, push to Chronicle). Helpers: `_splitByHeadings`, `_collectTextPages`, `_syncPagesToJournal`.
287287
- [x] **Sprint F-2: Granular Permission Mapping** — Map Chronicle `visibility: 'custom'` + `entity_permissions` to Foundry per-user ownership levels (view→OBSERVER, edit→OWNER). New syncapi endpoints: `GET /entities/:eid/permissions`, `PUT /entities/:eid/permissions`. Reverse-map Foundry ownership changes back to Chronicle. Helpers: `_buildOwnership`, `_pushPermissions`. User-specific grants stored in flags but not mapped to Foundry users (requires user ID mapping table — deferred).
288288
- [x] **Sprint F-3: System Detection & Character Field Templates** — Expanded dnd5e character preset (15 fields: class, level, race, alignment + 6 ability scores + HP/AC/speed/proficiency). Added pf2e character preset (15 fields: class, level, ancestry, heritage + 6 ability mods + HP/AC/perception/speed). `CharacterPreset()` helper on `SystemManifest`. New `GET /api/v1/campaigns/:id/systems` endpoint returns available systems with enabled flag. Foundry module: `syncCharacters` + `detectedSystem` settings, `SYSTEM_MAP` table, `_detectSystem()` on start, `getMatchedSystem()` accessor. Dashboard Status tab shows system match info and character sync availability.
289-
- [ ] **Sprint F-4: Actor ↔ Entity Sync**New `actor-sync.mjs` module. Foundry Actor (type: "character") ↔ Chronicle entity (type: "character"). System-specific adapters (`dnd5e-adapter.mjs`, `pf2e-adapter.mjs`) with `toChronicleFields(actor)` / `fromChronicleFields(entity)`. Hooks: `createActor`, `updateActor`, `deleteActor`. WebSocket: `entity.updated` with character type. Same `_syncing` guard pattern. Dashboard "Characters" tab.
289+
- [x] **Sprint F-4: Actor ↔ Entity Sync**`actor-sync.mjs` with bidirectional Actor entity sync. System adapters: `dnd5e-adapter.mjs` (15 fields), `pf2e-adapter.mjs` (HP/name back only). Dashboard Characters tab with Push button. Registered in module.mjs. TESTING.md updated.
290290
- [ ] **Sprint F-5: NPC Viewer / Hall** — Campaign route `/campaigns/:id/npcs`. Gallery/grid of revealed NPCs (non-private character entities). Portrait, name, description, location, faction. Filters by location/organization/relation. "Reveal" = DM toggles `is_private`. Foundry integration: ownership change on NPC journal → auto-reveal on Chronicle. Long-term: NPC relationship map (filtered relation graph).
291291
- [ ] **Sprint F-6: Armory / Inventory System** — Items as entities with game-mechanic fields (weight, cost, rarity, damage, properties). Character "Inventory" tab/block via entity relations. Relation metadata: equipped, quantity, attunement. System-specific item templates (dnd5e ≠ pf2e). Foundry sync: Actor inventory ↔ Chronicle inventory relations. "Armory" campaign page showing all catalogued items.
292292
- [ ] **Sprint F-7: Shop / Marketplace Enhancement** — Transaction logging (who bought what, when). Currency tracking per character. Stock management (auto-deplete on purchase). Foundry: purchase from shop window → update character inventory on both sides.

foundry-module/.ai.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ module.mjs (entry point)
1515
├─ JournalSync (journal-sync.mjs) ← Entity ↔ JournalEntry
1616
├─ MapSync (map-sync.mjs) ← Map drawings/tokens ↔ Scene
1717
├─ CalendarSync (calendar-sync.mjs)← Calendar events/date ↔ Calendaria/SimpleCalendar
18-
└─ ShopWidget (shop-widget.mjs) ← Shop entity inventory in Foundry UI
18+
├─ ShopWidget (shop-widget.mjs) ← Shop entity inventory in Foundry UI
19+
└─ ActorSync (actor-sync.mjs) ← Character entity ↔ Actor
20+
├─ dnd5e-adapter.mjs ← D&D 5e field mapping
21+
└─ pf2e-adapter.mjs ← Pathfinder 2e field mapping
1922
```
2023

2124
### Data Flow
@@ -46,10 +49,13 @@ All sync modules use a `_syncing` boolean flag to prevent infinite loops:
4649
| `scripts/map-sync.mjs` | Map drawings/tokens/fog sync. Percentage↔pixel coordinate conversion. Scene-to-map linking via context menu |
4750
| `scripts/calendar-sync.mjs` | Adapter pattern for Calendaria and SimpleCalendar. 0-indexed↔1-indexed conversion |
4851
| `scripts/shop-widget.mjs` | Shop window (Application v1). Context menu, drag-to-character-sheet, real-time updates |
52+
| `scripts/actor-sync.mjs` | Actor ↔ character entity bidirectional sync. System adapter loading, hook registration |
53+
| `scripts/adapters/dnd5e-adapter.mjs` | D&D 5e field mapping: 15 fields (ability scores, HP, AC, speed, level, class, race, alignment, proficiency_bonus) |
54+
| `scripts/adapters/pf2e-adapter.mjs` | PF2e field mapping: ability mods, HP, AC, perception, speed, level, class, ancestry, heritage. Only HP/name sync back |
4955
| `templates/shop-window.hbs` | Handlebars template for shop inventory display |
5056
| `styles/chronicle-sync.css` | Status indicator + shop window styles |
5157
| `lang/en.json` | English localization for all UI strings |
52-
| `TESTING.md` | 116-item E2E testing checklist |
58+
| `TESTING.md` | E2E testing checklist (journals, maps, calendar, shops, characters) |
5359

5460
## Server-Side Interconnects
5561

@@ -92,7 +98,7 @@ All sync modules use a `_syncing` boolean flag to prevent infinite loops:
9298
- **Shop icon field**: Always returns null (`shop-widget.mjs:146`)
9399
- **Single scene**: Only the active Foundry scene syncs (no multi-scene)
94100
- **GM only**: Full sync runs only for GM users; players get passive updates via Foundry
95-
- **No actor/character sync**: Character sheet sync requires matching game system (F-3 detection done, F-4 sync pending)
101+
- **Character sync: limited fields back from PF2e**: Only HP and name sync from Chronicle to PF2e actors (most values are derived from items/rules)
96102

97103
## Planned Features (Phase F)
98104

@@ -120,13 +126,13 @@ See `.ai/todo.md` Phase F for full sprint breakdown.
120126
- Server: `CharacterPreset()` helper on `SystemManifest`
121127
- Server: New `GET /systems` API endpoint with `enabled` flag per campaign
122128

123-
### F-4: Actor ↔ Entity Sync (new `actor-sync.mjs`)
124-
- Foundry Actors (type: "character") ↔ Chronicle entities (type: "character")
125-
- System-specific adapters: `dnd5e-adapter.mjs`, `pf2e-adapter.mjs`
126-
- Each adapter: `toChronicleFields(actor)` / `fromChronicleFields(entity)`
127-
- Hooks: `createActor`, `updateActor`, `deleteActor`
128-
- WebSocket: `entity.updated` with character type triggers actor update
129-
- Dashboard "Characters" tab
129+
### F-4: Actor ↔ Entity Sync (DONE)
130+
- `actor-sync.mjs`: Bidirectional sync between Foundry Actors and Chronicle character entities
131+
- System adapters: `adapters/dnd5e-adapter.mjs` (15 fields), `adapters/pf2e-adapter.mjs` (HP/name back only)
132+
- Hooks: `createActor`, `updateActor`, `deleteActor` with `_syncing` guard
133+
- WS: `entity.created/updated/deleted` filtered by character type (via type_slug, type_name, or type_id)
134+
- Dashboard "Characters" tab: synced/unlinked actors, Push button, empty states
135+
- Delete from Chronicle unlinks Actor (preserves data); delete from Foundry deletes Chronicle entity
130136

131137
### F-5: NPC Viewer / Hall (website feature)
132138
- Campaign route `/campaigns/:id/npcs` — gallery of revealed NPCs

foundry-module/TESTING.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,52 @@ Requires a running Chronicle instance and Foundry VTT with the chronicle-sync mo
148148
- [ ] Private entities hidden from non-owner API keys
149149
- [ ] Rate limiting enforced (60 req/min default)
150150

151+
## Character Sync (Actor ↔ Entity)
152+
153+
### Prerequisites
154+
- [ ] Game system matches a Chronicle system (dnd5e or pf2e)
155+
- [ ] "Sync Characters" enabled in module settings
156+
- [ ] Character entity type exists in Chronicle campaign
157+
158+
### Chronicle -> Foundry
159+
- [ ] Create character entity in Chronicle -> Actor (type: character) created in Foundry
160+
- [ ] Update character entity fields -> Actor system data updates (ability scores, HP)
161+
- [ ] Update character entity name -> Actor name updates
162+
- [ ] Delete character entity -> Actor unlinked (flags removed) but NOT deleted
163+
164+
### Foundry -> Chronicle
165+
- [ ] Create character Actor in Foundry -> Entity created in Chronicle with mapped fields
166+
- [ ] Update Actor ability scores -> Chronicle entity fields_data updates
167+
- [ ] Update Actor HP -> Chronicle entity hp_current/hp_max update
168+
- [ ] Update Actor name -> Chronicle entity name updates
169+
- [ ] Delete Actor -> Chronicle entity deleted
170+
171+
### Dashboard - Characters Tab
172+
- [ ] Characters tab visible in sync dashboard
173+
- [ ] System badge shows matched system name
174+
- [ ] Synced actors show green check with "Synced" label
175+
- [ ] Unlinked actors show "Not linked" with Push button
176+
- [ ] Push button creates Chronicle entity and links actor
177+
- [ ] Empty state shown when no character actors exist
178+
- [ ] Disabled state shown when syncCharacters is off
179+
- [ ] No-system state shown when game system doesn't match
180+
181+
### System Adapters
182+
- [ ] D&D 5e: All 6 ability scores sync (str, dex, con, int, wis, cha)
183+
- [ ] D&D 5e: HP current/max syncs bidirectionally
184+
- [ ] D&D 5e: AC, speed, level, class, race, alignment, proficiency_bonus push to Chronicle
185+
- [ ] PF2e: Ability mods sync to Chronicle (str_mod through cha_mod)
186+
- [ ] PF2e: HP syncs bidirectionally
187+
- [ ] PF2e: Only HP and name sync back from Chronicle (derived values protected)
188+
- [ ] PF2e: ancestry, heritage, class, level, perception, speed push to Chronicle
189+
190+
### Edge Cases
191+
- [ ] Actor sync disabled when no system adapter available
192+
- [ ] Sync guard prevents infinite loops (change in A doesn't re-trigger back to A)
193+
- [ ] Only character-type actors processed (NPCs, vehicles ignored)
194+
- [ ] Only current user's changes pushed (other users' changes ignored)
195+
- [ ] Pre-existing actors can be manually pushed via dashboard Push button
196+
151197
## Error Recovery
152198

153199
- [ ] Invalid API key shows clear error message

foundry-module/lang/en.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"Entities": "Entities",
5959
"Shops": "Shops",
6060
"Maps": "Maps",
61+
"Characters": "Characters",
6162
"Calendar": "Calendar",
6263
"Status": "Status"
6364
},
@@ -112,6 +113,17 @@
112113
"NoModuleHint": "Install Calendaria or Simple Calendar to enable calendar sync.",
113114
"UnableToRead": "Unable to read"
114115
},
116+
"Characters": {
117+
"Synced": "Synced",
118+
"NotLinked": "Not linked",
119+
"Push": "Push",
120+
"NoActors": "No character actors found.",
121+
"NoActorsHint": "Create character actors in Foundry or character entities in Chronicle.",
122+
"Disabled": "Character sync is disabled.",
123+
"DisabledHint": "Enable \"Sync Characters\" in Module Settings to sync actors with Chronicle.",
124+
"NoSystem": "Character sync requires a matching game system.",
125+
"NoSystemHint": "Your Foundry system must match a Chronicle game system with character field templates."
126+
},
115127
"StatusTab": {
116128
"Connection": "Connection",
117129
"EntitiesSynced": "Entities Synced",

0 commit comments

Comments
 (0)