Phase 6: combat ↔ map fusion

- Map combat HUD on the editor (Round, current combatant, Prev/Next turn) that
  drives the active encounter via encountersRepo.mutate(nextTurn/previousTurn).
- The current combatant's linked token glows (MapCanvas activeTokenId).
- Token edit modal gains a "Combat HP" control (−5/−1/+1/+5) for combatant-linked
  tokens, writing damage/heal back to the tracker (applyDamage/applyHealing +
  logEvent + updateCombatant) — HP now syncs both ways between map and combat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 15:41:06 +02:00
parent 06615020f6
commit f923375ebb
3 changed files with 79 additions and 5 deletions
+32
View File
@@ -53,6 +53,38 @@ test('maps: import a Universal VTT (.dd2vtt) map', async ({ page }) => {
await expect(page.getByRole('button', { name: 'Export .uvtt' })).toBeVisible();
});
test('maps: combat HUD drives turns + token HP syncs to the tracker', async ({ page }) => {
await page.getByRole('button', { name: '+ New campaign' }).first().click();
await page.locator('input[data-autofocus]').fill('Fight');
await page.getByRole('button', { name: 'Create' }).click();
await page.getByRole('link', { name: 'Characters' }).click();
await createCharacter(page, 'Hero');
// Encounter with the hero + a monster, then start combat.
await page.getByRole('link', { name: 'Combat' }).click();
await page.getByRole('button', { name: '+ New encounter' }).first().click();
await page.locator('input[data-autofocus]').fill('Brawl');
await page.getByRole('button', { name: 'Create' }).click();
await page.getByLabel('Name').fill('Goblin');
await page.getByRole('button', { name: 'Add', exact: true }).click();
await page.locator('select').filter({ has: page.locator('option', { hasText: 'Choose…' }) }).selectOption({ label: 'Hero (Lv 1)' });
await page.getByRole('button', { name: 'Start combat' }).click();
// Map: the combat HUD appears and Next turn works.
await page.getByRole('link', { name: 'Dashboard' }).click();
await page.getByRole('link', { name: 'Maps' }).click();
await page.locator('input[type="file"]').setInputFiles('public/pwa-512x512.png');
await expect(page.getByText(/Combat · Round 1/)).toBeVisible();
await page.getByRole('button', { name: /Next turn/ }).click();
await expect(page.getByText(/Combat · Round/)).toBeVisible();
// Place the Goblin combatant; its token edit shows the synced Combat HP control.
await page.getByRole('button', { name: /Goblin/ }).click();
await page.getByTestId('map-token').first().click();
await expect(page.getByText(/Combat HP/)).toBeVisible();
});
test('maps: walls tool + dynamic vision controls', async ({ page }) => {
await page.getByRole('button', { name: '+ New campaign' }).first().click();
await page.locator('input[data-autofocus]').fill('Vision');