V2 Phase 2: spell + resource enforcement (sheet → combat → live)
The Slice-0 vertical that proves the enforcement architecture end-to-end. - kernel: castSpell (slot consumption + concentration set/replace, upcasting, warlock pact slots), spendResource/regainResource, rollDeathSave — pure functions returning Partial<Character> patches. 13 unit tests. - applyRest now ends concentration on any rest (+ test). - character sheet SpellcastingSection: per-spell Cast button, concentration banner with Drop, concentration markers; ResourcesSection −/+ routed through the kernel. - player MyCharacterPanel: castable spell list + concentration banner, so a seated player casts and it broadcasts via the existing playerPatch flow. - live sync: partialCharacterDiffSchema carries concentration; GM applies it; playerCharacterSchema mirrors slots/concentration/resources (PC-only) and the player-facing AC now respects equippedArmor. Wire round-trip test added. Verified live: casting decrements the right slot, switching concentration logs "Concentration on X ends", banner + Drop render. Build + 258 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -98,4 +98,12 @@ describe('applyRest', () => {
|
||||
expect(patch.defenses!.exhaustion).toBe(1);
|
||||
expect(patch.defenses!.deathSaves).toEqual({ successes: 0, failures: 0 });
|
||||
});
|
||||
|
||||
it('any rest ends ongoing concentration', () => {
|
||||
const c = { ...makeCharacter(), concentration: { spellId: 's', spellName: 'Bless' } };
|
||||
const short = dnd5e.restOptions.find((o) => o.id === 'short')!;
|
||||
expect(applyRest(c, short).concentration).toBeNull();
|
||||
// no concentration → no concentration key in the patch
|
||||
expect('concentration' in applyRest(makeCharacter(), short)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user