1 Commits

Author SHA1 Message Date
Lukas
b39e4923e1 Remove demo combatants and allow empty encounters
All checks were successful
CI / check (push) Successful in 45s
CI / build-image (push) Successful in 28s
Empty encounters are now valid (INV-1 updated). New sessions start
with zero combatants instead of pre-populated Aria/Brak/Cael.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:24:26 +01:00
3 changed files with 14 additions and 26 deletions

View File

@@ -22,7 +22,6 @@ import type {
} from "@initiative/domain"; } from "@initiative/domain";
import { import {
combatantId, combatantId,
createEncounter,
isDomainError, isDomainError,
creatureId as makeCreatureId, creatureId as makeCreatureId,
resolveCreatureName, resolveCreatureName,
@@ -33,24 +32,16 @@ import {
saveEncounter, saveEncounter,
} from "../persistence/encounter-storage.js"; } from "../persistence/encounter-storage.js";
function createDemoEncounter(): Encounter { const EMPTY_ENCOUNTER: Encounter = {
const result = createEncounter([ combatants: [],
{ id: combatantId("1"), name: "Aria" }, activeIndex: 0,
{ id: combatantId("2"), name: "Brak" }, roundNumber: 1,
{ id: combatantId("3"), name: "Cael" }, };
]);
if (isDomainError(result)) {
throw new Error(`Failed to create demo encounter: ${result.message}`);
}
return result;
}
function initializeEncounter(): Encounter { function initializeEncounter(): Encounter {
const stored = loadEncounter(); const stored = loadEncounter();
if (stored !== null) return stored; if (stored !== null) return stored;
return createDemoEncounter(); return EMPTY_ENCOUNTER;
} }
function deriveNextId(encounter: Encounter): number { function deriveNextId(encounter: Encounter): number {

View File

@@ -169,9 +169,9 @@ describe("advanceTurn", () => {
}); });
describe("invariants", () => { describe("invariants", () => {
it("INV-1: createEncounter rejects empty combatant list", () => { it("INV-1: createEncounter accepts empty combatant list", () => {
const result = createEncounter([]); const result = createEncounter([]);
expect(isDomainError(result)).toBe(true); expect(isDomainError(result)).toBe(false);
}); });
it("INV-2: activeIndex always in bounds across all scenarios", () => { it("INV-2: activeIndex always in bounds across all scenarios", () => {

View File

@@ -38,8 +38,8 @@ function domainError(code: string, message: string): DomainError {
/** /**
* Creates a valid Encounter, enforcing INV-1, INV-2, INV-3. * Creates a valid Encounter, enforcing INV-1, INV-2, INV-3.
* - INV-1: At least one combatant required. * - INV-1: An encounter MAY have zero combatants.
* - INV-2: activeIndex defaults to 0 (always in bounds). * - INV-2: activeIndex defaults to 0 (always in bounds when combatants exist).
* - INV-3: roundNumber defaults to 1 (positive integer). * - INV-3: roundNumber defaults to 1 (positive integer).
*/ */
export function createEncounter( export function createEncounter(
@@ -47,13 +47,10 @@ export function createEncounter(
activeIndex = 0, activeIndex = 0,
roundNumber = 1, roundNumber = 1,
): Encounter | DomainError { ): Encounter | DomainError {
if (combatants.length === 0) { if (
return domainError( combatants.length > 0 &&
"invalid-encounter", (activeIndex < 0 || activeIndex >= combatants.length)
"An encounter must have at least one combatant", ) {
);
}
if (activeIndex < 0 || activeIndex >= combatants.length) {
return domainError( return domainError(
"invalid-encounter", "invalid-encounter",
`activeIndex ${activeIndex} out of bounds for ${combatants.length} combatants`, `activeIndex ${activeIndex} out of bounds for ${combatants.length} combatants`,