From 0320ae04143c0f009ac19d848b370a42fe48712f Mon Sep 17 00:00:00 2001 From: Fritz Date: Sun, 29 Mar 2026 10:35:23 -0700 Subject: [PATCH 1/2] adding diagrams and render script --- CLAUDE.md | 30 ++- DHModels-diagram.md | 194 ------------------ Scripts/render-diagrams.sh | 49 +++++ .../DHKit.docc/Resources/participants.svg | 1 + Sources/DHKit/DHKit.docc/Resources/stores.svg | 1 + Sources/DHKit/DHKit.docc/Resources/usage.svg | 1 + .../Resources/catalog.svg | 1 + .../Resources/content.svg | 1 + .../Resources/encounter.svg | 1 + diagrams/DHKit/participants.mmd | 61 ++++++ DHKit-diagram.md => diagrams/DHKit/stores.mmd | 119 +---------- diagrams/DHKit/usage.mmd | 23 +++ diagrams/DHModels/catalog.mmd | 90 ++++++++ diagrams/DHModels/content.mmd | 53 +++++ diagrams/DHModels/encounter.mmd | 61 ++++++ 15 files changed, 380 insertions(+), 306 deletions(-) delete mode 100644 DHModels-diagram.md create mode 100755 Scripts/render-diagrams.sh create mode 100644 Sources/DHKit/DHKit.docc/Resources/participants.svg create mode 100644 Sources/DHKit/DHKit.docc/Resources/stores.svg create mode 100644 Sources/DHKit/DHKit.docc/Resources/usage.svg create mode 100644 Sources/DHModels/DaggerheartModels.docc/Resources/catalog.svg create mode 100644 Sources/DHModels/DaggerheartModels.docc/Resources/content.svg create mode 100644 Sources/DHModels/DaggerheartModels.docc/Resources/encounter.svg create mode 100644 diagrams/DHKit/participants.mmd rename DHKit-diagram.md => diagrams/DHKit/stores.mmd (52%) create mode 100644 diagrams/DHKit/usage.mmd create mode 100644 diagrams/DHModels/catalog.mmd create mode 100644 diagrams/DHModels/content.mmd create mode 100644 diagrams/DHModels/encounter.mmd diff --git a/CLAUDE.md b/CLAUDE.md index ee32075..b915e5f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,18 +11,27 @@ Two targets: `DHModels` (Linux-safe value types) and `DHKit` ``` DHModels/ ├── Sources/ -│ ├── DHModels/ # Foundation-only; no Apple-only frameworks +│ ├── DHModels/ +│ │ └── DaggerheartModels.docc/ +│ │ └── Resources/ # SVG diagrams (generated by Scripts/render-diagrams.sh) │ ├── DHKit/ +│ │ ├── DHKit.docc/ +│ │ │ └── Resources/ # SVG diagrams (generated by Scripts/render-diagrams.sh) │ │ └── Resources/ # adversaries.json, environments.json (SRD data) │ └── validate-dhpack/ ├── Tests/ │ ├── DHModelsTests/ # Linux-compatible; runs in Linux CI │ │ └── Fixtures/ # adversaries.json, environments.json, sample-homebrew.dhpack │ └── DHKitTests/ # Apple-platform only +├── diagrams/ +│ ├── DHModels/ # catalog.mmd, encounter.mmd, content.mmd +│ └── DHKit/ # participants.mmd, stores.mmd, usage.mmd ├── schemas/ # dhpack.schema.json (JSON Schema for .dhpack files) ├── Package.swift ├── .swift-format # Formatting rules (matches gwillish/encounter) -└── Scripts/format.sh # Format all tracked Swift files +└── Scripts/ + ├── format.sh # Format all tracked Swift files + └── render-diagrams.sh # Render .mmd sources to SVG ``` --- @@ -159,6 +168,23 @@ declaration to opt out of the default `@MainActor` isolation. --- +## Diagrams + +Source files live in `diagrams/DHModels/*.mmd` and `diagrams/DHKit/*.mmd`. + +**Whenever a diagram source is added or modified, re-run the render script** to +keep the DocC Resources in sync: + +```bash +./Scripts/render-diagrams.sh +``` + +Output goes to `Sources/DHModels/DaggerheartModels.docc/Resources/` and +`Sources/DHKit/DHKit.docc/Resources/`. +Commit the generated SVGs alongside the source `.mmd` files. + +--- + ## Git - **Never commit on behalf of the user.** Wait for an explicit request. diff --git a/DHModels-diagram.md b/DHModels-diagram.md deleted file mode 100644 index a818669..0000000 --- a/DHModels-diagram.md +++ /dev/null @@ -1,194 +0,0 @@ -# DHModels — Type Relationship Diagram - -`DHModels` is the catalog layer. All types are value types -(`struct` or `enum`), `Codable`, `Sendable`, and Linux-safe. - -```mermaid -classDiagram - direction TB - - %% ── Adversary cluster ───────────────────────────────────────────────── - class Adversary { - +String id - +String name - +String source - +Int tier - +AdversaryType role - +String flavorText - +String? motivesAndTactics - +Int difficulty - +Int thresholdMajor - +Int thresholdSevere - +Int hp - +Int stress - +String attackModifier - +String attackName - +AttackRange attackRange - +String damage - +String? experience - +[EncounterFeature] features - +Bool isHomebrew - } - - class AdversaryType { - <> - bruiser - horde - leader - minion - ranged - skulk - social - solo - standard - support - } - - class AttackRange { - <> - melee - veryClose - close - far - veryFar - } - - class FeatureType { - <> - action - reaction - passive - +inferred(from:) FeatureType$ - } - - class EncounterFeature { - +String id - +String name - +String text - +FeatureType kind - } - - Adversary --> AdversaryType : role - Adversary --> AttackRange : attackRange - Adversary "1" *-- "0..*" EncounterFeature : features - EncounterFeature --> FeatureType : kind - - %% ── Environment cluster ─────────────────────────────────────────────── - class DaggerheartEnvironment { - +String id - +String name - +String source - +String flavorText - +[EncounterFeature] features - +Bool isHomebrew - } - - DaggerheartEnvironment "1" *-- "0..*" EncounterFeature : features - - %% ── Conditions ──────────────────────────────────────────────────────── - class Condition { - <> - hidden - restrained - vulnerable - custom(String) - +String displayName - } - - %% ── Encounter definition (prep / save layer) ────────────────────────── - class EncounterDefinition { - +UUID id - +String name - +[String] adversaryIDs - +[String] environmentIDs - +[PlayerConfig] playerConfigs - +String gmNotes - +Date createdAt - +Date modifiedAt - } - - class PlayerConfig { - +UUID id - +String name - +Int maxHP - +Int maxStress - +Int evasion - +Int thresholdMajor - +Int thresholdSevere - +Int armorSlots - } - - EncounterDefinition "1" *-- "0..*" PlayerConfig : playerConfigs - - %% ── Difficulty budget (static utility) ─────────────────────────────── - class DifficultyBudget { - <> - +cost(for: AdversaryType) Int$ - +baseBudget(playerCount:) Int$ - +totalCost(for: [AdversaryType]) Int$ - +rating(adversaryTypes:playerCount:budgetAdjustment:) Rating$ - +suggestedAdjustments(adversaryTypes:) [Adjustment]$ - } - - class DifficultyBudget_Rating { - +Int cost - +Int budget - +Int remaining - } - - class DifficultyBudget_Adjustment { - <> - easierFight - multipleSolos - boostedDamage - lowerTierAdversary - noBigThreats - harderFight - +Int pointValue - } - - DifficultyBudget ..> AdversaryType : uses - DifficultyBudget ..> DifficultyBudget_Rating : returns - DifficultyBudget ..> DifficultyBudget_Adjustment : returns - - %% ── Content pack types ──────────────────────────────────────────────── - class DHPackContent { - +[Adversary] adversaries - +[DaggerheartEnvironment] environments - } - - DHPackContent "1" *-- "0..*" Adversary : adversaries - DHPackContent "1" *-- "0..*" DaggerheartEnvironment : environments - - class ContentSource { - +UUID id - +String? displayName - +URL? url - +Date addedAt - +Date? lastFetchedAt - +String? etag - +Bool isThrottled(at:) - +TimeInterval nextAllowedFetch(after:) - } - - class ContentFingerprint { - +String sha256 - } - - class ContentStoreError { - <> - fileNotFound - decodingFailed - downloadFailed - invalidURL - } -``` - -## Key relationships - -| Relationship | Description | -|---|---| -| `EncounterDefinition` stores IDs | Holds `adversaryIDs` and `environmentIDs` as `[String]` slugs — resolved into live slots at session creation via `Compendium` (DHKit) | -| `PlayerConfig` → `PlayerSlot` | `PlayerConfig` is the serialisable prep-time form; `PlayerSlot` (DHKit) is the live runtime form created from it | -| `DifficultyBudget` | Stateless utility — call its static methods with a list of `AdversaryType` to estimate encounter difficulty | -| `DHPackContent` | Decoded form of a `.dhpack` file; fed into `Compendium.replaceSourceContent(sourceID:adversaries:environments:)` | diff --git a/Scripts/render-diagrams.sh b/Scripts/render-diagrams.sh new file mode 100755 index 0000000..de2b1e2 --- /dev/null +++ b/Scripts/render-diagrams.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Render all Mermaid diagram sources to SVG. +# +# Requirements: Node.js >= 18 +# Uses mmdc from @mermaid-js/mermaid-cli via npx (no prior install needed). +# To use a globally-installed mmdc instead, ensure it is on your PATH. +# +# Usage: +# ./Scripts/render-diagrams.sh +# +# Output: +# Sources/DHModels/DaggerheartModels.docc/Resources/*.svg (from diagrams/DHModels/*.mmd) +# Sources/DHKit/DHKit.docc/Resources/*.svg (from diagrams/DHKit/*.mmd) + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +DIAGRAMS="$REPO_ROOT/diagrams" +DHMODELS_OUT="$REPO_ROOT/Sources/DHModels/DaggerheartModels.docc/Resources" +DHKIT_OUT="$REPO_ROOT/Sources/DHKit/DHKit.docc/Resources" + +mkdir -p "$DHMODELS_OUT" +mkdir -p "$DHKIT_OUT" + +if command -v mmdc &>/dev/null; then + MMDC=mmdc +else + MMDC="npx --yes @mermaid-js/mermaid-cli" +fi + +render() { + local src="$1" dst="$2" + echo " $(basename "$src") → $(basename "$dst")" + $MMDC -i "$src" -o "$dst" +} + +echo "=== DHModels ===" +for f in "$DIAGRAMS/DHModels"/*.mmd; do + name="$(basename "${f%.mmd}")" + render "$f" "$DHMODELS_OUT/${name}.svg" +done + +echo "=== DHKit ===" +for f in "$DIAGRAMS/DHKit"/*.mmd; do + name="$(basename "${f%.mmd}")" + render "$f" "$DHKIT_OUT/${name}.svg" +done + +echo "Done." diff --git a/Sources/DHKit/DHKit.docc/Resources/participants.svg b/Sources/DHKit/DHKit.docc/Resources/participants.svg new file mode 100644 index 0000000..23bec9e --- /dev/null +++ b/Sources/DHKit/DHKit.docc/Resources/participants.svg @@ -0,0 +1 @@ +

«protocol»

EncounterParticipant

+UUID id

«protocol»

CombatParticipant

+Int currentHP

+Int maxHP

+Int currentStress

+Int maxStress

+Set<Condition> conditions

AdversarySlot

+UUID id

+String adversaryID

+String? customName

+Int maxHP

+Int maxStress

+Int currentHP

+Int currentStress

+Bool isDefeated

+Set<Condition> conditions

+init(from: Adversary, customName:)

+applying(currentHP:currentStress:isDefeated:conditions:) : AdversarySlot

PlayerSlot

+UUID id

+String name

+Int maxHP

+Int currentHP

+Int maxStress

+Int currentStress

+Int evasion

+Int thresholdMajor

+Int thresholdSevere

+Int armorSlots

+Int currentArmorSlots

+Set<Condition> conditions

+applying(currentHP:currentStress:currentArmorSlots:conditions:) : PlayerSlot

EnvironmentSlot

+UUID id

+String environmentID

+Bool isActive

+applying(isActive:) : EnvironmentSlot

\ No newline at end of file diff --git a/Sources/DHKit/DHKit.docc/Resources/stores.svg b/Sources/DHKit/DHKit.docc/Resources/stores.svg new file mode 100644 index 0000000..27df159 --- /dev/null +++ b/Sources/DHKit/DHKit.docc/Resources/stores.svg @@ -0,0 +1 @@ +

adversarySlots

playerSlots

environmentSlots

throws

throws

sessions

resolves catalog via make

resolves catalog

creates and owns

1

1

1

1

0..*

0..*

0..*

0..*

«Observable, MainActor»

EncounterSession

+UUID id

+String name

+[AdversarySlot] adversarySlots

+[PlayerSlot] playerSlots

+[EnvironmentSlot] environmentSlots

+Int fearPool

+Int hopePool

+UUID? spotlightedSlotID

+Int spotlightCount

+String gmNotes

+[AdversarySlot] activeAdversaries

+Bool isOver

+add(adversary: Adversary, customName:)

+add(environment: DaggerheartEnvironment)

+add(player: PlayerSlot)

+removeAdversary(withID:)

+removePlayer(withID:)

+spotlight(id:)

+yieldSpotlight()

+applyDamage(_:to:)

+applyHealing(_:to:)

+applyStress(_:to:)

+reduceStress(_:from:)

+applyCondition(_:to:)

+removeCondition(_:from:)

+markArmorSlot(for:)

+restoreArmorSlot(for:)

+incrementFear(by:)

+spendFear(by:)

+incrementHope(by:)

+spendHope(by:)

+make(from: EncounterDefinition, using: Compendium) : EncounterSession

AdversarySlot

+UUID id

PlayerSlot

+UUID id

EnvironmentSlot

+UUID id

«Observable, MainActor»

Compendium

+[String: Adversary] adversariesByID

+[String: DaggerheartEnvironment] environmentsByID

+[Adversary] adversaries

+[DaggerheartEnvironment] environments

+[Adversary] homebrewAdversaries

+[DaggerheartEnvironment] homebrewEnvironments

+Bool isLoading

+CompendiumError? loadError

+init(bundle: Bundle?)

+load() : async throws

+adversary(id:) : Adversary?

+environment(id:) : DaggerheartEnvironment?

+adversaries(ofTier:) : [Adversary]

+adversaries(ofRole:) : [Adversary]

+adversaries(matching:) : [Adversary]

+addAdversary(_:)

+removeHomebrewAdversary(id:)

+addEnvironment(_:)

+removeHomebrewEnvironment(id:)

+replaceSRDContent(adversaries:environments:)

+replaceSourceContent(sourceID:adversaries:environments:)

+removeSourceContent(sourceID:)

«enumeration»

CompendiumError

fileNotFound(resourceName:)

decodingFailed(resourceName:underlying:)

«Observable, MainActor»

EncounterStore

+[EncounterDefinition] definitions

+URL directory

+Bool isLoading

+Error? loadError

+localDirectory URL

+init(directory: URL)

+defaultDirectory() : URL$ async

+relocate(to:)

+load() : async

+create(name:) : async throws

+save(_:) : async throws

+delete(id:) : async throws

+duplicate(id:) : async throws

«enumeration»

EncounterStoreError

notFound(UUID)

saveFailed(UUID, String)

deleteFailed(UUID, String)

«Observable, MainActor»

SessionRegistry

+[UUID: EncounterSession] sessions

+init()

+session(for:definition:compendium:) : EncounterSession

+clearSession(for:)

+resetSession(for:definition:compendium:) : EncounterSession

\ No newline at end of file diff --git a/Sources/DHKit/DHKit.docc/Resources/usage.svg b/Sources/DHKit/DHKit.docc/Resources/usage.svg new file mode 100644 index 0000000..9a1b868 --- /dev/null +++ b/Sources/DHKit/DHKit.docc/Resources/usage.svg @@ -0,0 +1 @@ +EncounterSessionSessionRegistryEncounterStoreCompendiumAppEncounterSessionSessionRegistryEncounterStoreCompendiumAppDecodes SRD JSON from bundleReads .encounter.json files from diskResolves IDs via Compendium and builds slotsinit() + load()init(directory:) + load()create(name:)definitions updatedsession(for:definition:compendium:)make(from:using:)add(player:) / spotlight(id:)applyDamage(_:to:) / applyCondition(_:to:)save(definition) when prep changes \ No newline at end of file diff --git a/Sources/DHModels/DaggerheartModels.docc/Resources/catalog.svg b/Sources/DHModels/DaggerheartModels.docc/Resources/catalog.svg new file mode 100644 index 0000000..d1f88bf --- /dev/null +++ b/Sources/DHModels/DaggerheartModels.docc/Resources/catalog.svg @@ -0,0 +1 @@ +

role

attackRange

features

kind

features

1

1

0..*

0..*

Adversary

+String id

+String name

+String source

+Int tier

+AdversaryType role

+String flavorText

+String? motivesAndTactics

+Int difficulty

+Int thresholdMajor

+Int thresholdSevere

+Int hp

+Int stress

+String attackModifier

+String attackName

+AttackRange attackRange

+String damage

+String? experience

+[EncounterFeature] features

+Bool isHomebrew

«enumeration»

AdversaryType

bruiser

horde

leader

minion

ranged

skulk

social

solo

standard

support

«enumeration»

AttackRange

melee

veryClose

close

far

veryFar

«enumeration»

FeatureType

action

reaction

passive

+inferred(from:) : FeatureType

EncounterFeature

+String id

+String name

+String text

+FeatureType kind

DaggerheartEnvironment

+String id

+String name

+String source

+String flavorText

+[EncounterFeature] features

+Bool isHomebrew

«enumeration»

Condition

hidden

restrained

vulnerable

+String displayName

custom(String)

\ No newline at end of file diff --git a/Sources/DHModels/DaggerheartModels.docc/Resources/content.svg b/Sources/DHModels/DaggerheartModels.docc/Resources/content.svg new file mode 100644 index 0000000..168a23c --- /dev/null +++ b/Sources/DHModels/DaggerheartModels.docc/Resources/content.svg @@ -0,0 +1 @@ +

adversaries

environments

fingerprint

1

1

0..*

0..*

DHPackContent

+[Adversary] adversaries

+[DaggerheartEnvironment] environments

«catalog type — see catalog diagram»

Adversary

«catalog type — see catalog diagram»

DaggerheartEnvironment

ContentSource

+UUID id

+String? displayName

+URL? url

+Date addedAt

+Date? lastFetchedAt

+ContentFingerprint? fingerprint

+Int consecutiveFailures

+Bool isLocalImport

+Bool isThrottled(at: Date)

+Date nextAllowedFetch(at: Date)

+ContentSource recordingFailure(at: Date)

+ContentSource recordingSuccess(fingerprint:at:)

ContentFingerprint

+String sha256

+String? etag

«enumeration»

ContentStoreError

fetchThrottled

networkError

decodingFailed

writeFailed

readFailed

invalidContent

\ No newline at end of file diff --git a/Sources/DHModels/DaggerheartModels.docc/Resources/encounter.svg b/Sources/DHModels/DaggerheartModels.docc/Resources/encounter.svg new file mode 100644 index 0000000..24e1c2f --- /dev/null +++ b/Sources/DHModels/DaggerheartModels.docc/Resources/encounter.svg @@ -0,0 +1 @@ +

playerConfigs

returns

returns

1

0..*

EncounterDefinition

+UUID id

+String name

+[String] adversaryIDs

+[String] environmentIDs

+[PlayerConfig] playerConfigs

+String gmNotes

+Date createdAt

+Date modifiedAt

PlayerConfig

+UUID id

+String name

+Int maxHP

+Int maxStress

+Int evasion

+Int thresholdMajor

+Int thresholdSevere

+Int armorSlots

«utility»

DifficultyBudget

+cost(for: AdversaryType) : Int

+baseBudget(playerCount:) : Int

+totalCost(for: [AdversaryType]) : Int

+rating(adversaryTypes:playerCount:budgetAdjustment:) : Rating

+suggestedAdjustments(adversaryTypes:) : Set<Adjustment>

DifficultyBudget_Rating

+Int cost

+Int budget

+Int remaining

«enumeration»

DifficultyBudget_Adjustment

easierFight

multipleSolos

boostedDamage

lowerTierAdversary

noBigThreats

harderFight

+Int pointValue

+String displayName

\ No newline at end of file diff --git a/diagrams/DHKit/participants.mmd b/diagrams/DHKit/participants.mmd new file mode 100644 index 0000000..9367a96 --- /dev/null +++ b/diagrams/DHKit/participants.mmd @@ -0,0 +1,61 @@ +classDiagram + direction TB + + %% ── Protocols ───────────────────────────────────────────────────────── + class EncounterParticipant { + <> + +UUID id + } + + class CombatParticipant { + <> + +Int currentHP + +Int maxHP + +Int currentStress + +Int maxStress + +Set~Condition~ conditions + } + + EncounterParticipant <|-- CombatParticipant + + %% ── Slot value types (nonisolated) ──────────────────────────────────── + class AdversarySlot { + +UUID id + +String adversaryID + +String? customName + +Int maxHP + +Int maxStress + +Int currentHP + +Int currentStress + +Bool isDefeated + +Set~Condition~ conditions + +init(from: Adversary, customName:) + +applying(currentHP:currentStress:isDefeated:conditions:) AdversarySlot + } + + class PlayerSlot { + +UUID id + +String name + +Int maxHP + +Int currentHP + +Int maxStress + +Int currentStress + +Int evasion + +Int thresholdMajor + +Int thresholdSevere + +Int armorSlots + +Int currentArmorSlots + +Set~Condition~ conditions + +applying(currentHP:currentStress:currentArmorSlots:conditions:) PlayerSlot + } + + class EnvironmentSlot { + +UUID id + +String environmentID + +Bool isActive + +applying(isActive:) EnvironmentSlot + } + + CombatParticipant <|.. AdversarySlot + CombatParticipant <|.. PlayerSlot + EncounterParticipant <|.. EnvironmentSlot diff --git a/DHKit-diagram.md b/diagrams/DHKit/stores.mmd similarity index 52% rename from DHKit-diagram.md rename to diagrams/DHKit/stores.mmd index 3049135..5f31806 100644 --- a/DHKit-diagram.md +++ b/diagrams/DHKit/stores.mmd @@ -1,72 +1,6 @@ -# DHKit — Type Relationship Diagram - -`DHKit` is the observable store layer. All top-level store classes -are `@Observable @MainActor`. Slot types are `nonisolated` value types so they -can be passed freely across isolation boundaries. - -```mermaid classDiagram direction TB - %% ── Protocols ───────────────────────────────────────────────────────── - class EncounterParticipant { - <> - +UUID id - } - - class CombatParticipant { - <> - +Int currentHP - +Int maxHP - +Int currentStress - +Int maxStress - +Set~Condition~ conditions - } - - EncounterParticipant <|-- CombatParticipant - - %% ── Slot value types ───────────────────────────────────────────────── - class AdversarySlot { - +UUID id - +String adversaryID - +String? customName - +Int maxHP - +Int maxStress - +Int currentHP - +Int currentStress - +Bool isDefeated - +Set~Condition~ conditions - +init(from: Adversary, customName:) - +applying(currentHP:currentStress:isDefeated:conditions:) AdversarySlot - } - - class PlayerSlot { - +UUID id - +String name - +Int maxHP - +Int currentHP - +Int maxStress - +Int currentStress - +Int evasion - +Int thresholdMajor - +Int thresholdSevere - +Int armorSlots - +Int currentArmorSlots - +Set~Condition~ conditions - +applying(currentHP:currentStress:currentArmorSlots:conditions:) PlayerSlot - } - - class EnvironmentSlot { - +UUID id - +String environmentID - +Bool isActive - +applying(isActive:) EnvironmentSlot - } - - CombatParticipant <|.. AdversarySlot - CombatParticipant <|.. PlayerSlot - EncounterParticipant <|.. EnvironmentSlot - %% ── EncounterSession ───────────────────────────────────────────────── class EncounterSession { <> @@ -104,6 +38,11 @@ classDiagram +make(from: EncounterDefinition, using: Compendium) EncounterSession$ } + %% slot types shown as aggregated members only — see participants diagram + class AdversarySlot { +UUID id } + class PlayerSlot { +UUID id } + class EnvironmentSlot { +UUID id } + EncounterSession "1" *-- "0..*" AdversarySlot : adversarySlots EncounterSession "1" *-- "0..*" PlayerSlot : playerSlots EncounterSession "1" *-- "0..*" EnvironmentSlot : environmentSlots @@ -182,47 +121,7 @@ classDiagram SessionRegistry "1" o-- "0..*" EncounterSession : sessions - %% ── Cross-type dependencies ────────────────────────────────────────── - EncounterSession ..> Compendium : make(from:using:) - SessionRegistry ..> Compendium : session(for:definition:compendium:) - SessionRegistry ..> EncounterSession : creates / owns -``` - -## Typical usage flow - -```mermaid -sequenceDiagram - participant App - participant Compendium - participant EncounterStore - participant SessionRegistry - participant EncounterSession - - App->>Compendium: init() + load() - Note over Compendium: Decodes SRD JSON from bundle - - App->>EncounterStore: init(directory:) + load() - Note over EncounterStore: Reads .encounter.json files from disk - - App->>EncounterStore: create(name:) - EncounterStore-->>App: definitions updated - - App->>SessionRegistry: session(for:definition:compendium:) - SessionRegistry->>EncounterSession: make(from:using:) - Note over EncounterSession: Resolves adversary/environment IDs
via Compendium; builds slots - - App->>EncounterSession: add(player:) / spotlight(id:) - App->>EncounterSession: applyDamage(_:to:) / applyCondition(_:to:) - App->>EncounterStore: save(definition) when prep changes -``` - -## Key design points - -| Concern | Approach | -|---|---| -| Default isolation | `@MainActor` on all `@Observable` classes; slots are `nonisolated` structs | -| Mutation pattern | Slots are immutable; `EncounterSession` replaces them wholesale via `applying(...)` | -| Catalog vs. runtime | `Compendium` holds static catalog data; `EncounterSession` holds live session state | -| Persistence | `EncounterStore` persists `EncounterDefinition` (prep); sessions are in-memory only | -| Session lifecycle | `SessionRegistry` holds sessions keyed by definition ID; `clearSession` / `resetSession` to restart | -| Homebrew priority | Compendium merges: homebrew → source packs → SRD (last writer wins on ID conflict) | + %% ── Cross-store dependencies ───────────────────────────────────────── + EncounterSession ..> Compendium : resolves catalog via make + SessionRegistry ..> Compendium : resolves catalog + SessionRegistry ..> EncounterSession : creates and owns diff --git a/diagrams/DHKit/usage.mmd b/diagrams/DHKit/usage.mmd new file mode 100644 index 0000000..2441d5a --- /dev/null +++ b/diagrams/DHKit/usage.mmd @@ -0,0 +1,23 @@ +sequenceDiagram + participant App + participant Compendium + participant EncounterStore + participant SessionRegistry + participant EncounterSession + + App->>Compendium: init() + load() + Note over Compendium: Decodes SRD JSON from bundle + + App->>EncounterStore: init(directory:) + load() + Note over EncounterStore: Reads .encounter.json files from disk + + App->>EncounterStore: create(name:) + EncounterStore-->>App: definitions updated + + App->>SessionRegistry: session(for:definition:compendium:) + SessionRegistry->>EncounterSession: make(from:using:) + Note over EncounterSession: Resolves IDs via Compendium and builds slots + + App->>EncounterSession: add(player:) / spotlight(id:) + App->>EncounterSession: applyDamage(_:to:) / applyCondition(_:to:) + App->>EncounterStore: save(definition) when prep changes diff --git a/diagrams/DHModels/catalog.mmd b/diagrams/DHModels/catalog.mmd new file mode 100644 index 0000000..1fd942f --- /dev/null +++ b/diagrams/DHModels/catalog.mmd @@ -0,0 +1,90 @@ +classDiagram + direction TB + + %% ── Adversary cluster ───────────────────────────────────────────────── + class Adversary { + +String id + +String name + +String source + +Int tier + +AdversaryType role + +String flavorText + +String? motivesAndTactics + +Int difficulty + +Int thresholdMajor + +Int thresholdSevere + +Int hp + +Int stress + +String attackModifier + +String attackName + +AttackRange attackRange + +String damage + +String? experience + +[EncounterFeature] features + +Bool isHomebrew + } + + class AdversaryType { + <> + bruiser + horde + leader + minion + ranged + skulk + social + solo + standard + support + } + + class AttackRange { + <> + melee + veryClose + close + far + veryFar + } + + class FeatureType { + <> + action + reaction + passive + +inferred(from:) FeatureType$ + } + + class EncounterFeature { + +String id + +String name + +String text + +FeatureType kind + } + + Adversary --> AdversaryType : role + Adversary --> AttackRange : attackRange + Adversary "1" *-- "0..*" EncounterFeature : features + EncounterFeature --> FeatureType : kind + + %% ── Environment cluster ─────────────────────────────────────────────── + class DaggerheartEnvironment { + +String id + +String name + +String source + +String flavorText + +[EncounterFeature] features + +Bool isHomebrew + } + + DaggerheartEnvironment "1" *-- "0..*" EncounterFeature : features + + %% ── Conditions ──────────────────────────────────────────────────────── + class Condition { + <> + hidden + restrained + vulnerable + custom(String) + +String displayName + } diff --git a/diagrams/DHModels/content.mmd b/diagrams/DHModels/content.mmd new file mode 100644 index 0000000..161c13a --- /dev/null +++ b/diagrams/DHModels/content.mmd @@ -0,0 +1,53 @@ +classDiagram + direction LR + + %% ── Content pack ────────────────────────────────────────────────────── + class DHPackContent { + +[Adversary] adversaries + +[DaggerheartEnvironment] environments + } + + class Adversary { + <> + } + + class DaggerheartEnvironment { + <> + } + + DHPackContent "1" *-- "0..*" Adversary : adversaries + DHPackContent "1" *-- "0..*" DaggerheartEnvironment : environments + + %% ── Content source tracking ─────────────────────────────────────────── + class ContentSource { + +UUID id + +String? displayName + +URL? url + +Date addedAt + +Date? lastFetchedAt + +ContentFingerprint? fingerprint + +Int consecutiveFailures + +Bool isLocalImport + +Bool isThrottled(at: Date) + +Date nextAllowedFetch(at: Date) + +ContentSource recordingFailure(at: Date) + +ContentSource recordingSuccess(fingerprint:at:) + } + + class ContentFingerprint { + +String sha256 + +String? etag + } + + ContentSource --> ContentFingerprint : fingerprint + + %% ── Errors ──────────────────────────────────────────────────────────── + class ContentStoreError { + <> + fetchThrottled + networkError + decodingFailed + writeFailed + readFailed + invalidContent + } diff --git a/diagrams/DHModels/encounter.mmd b/diagrams/DHModels/encounter.mmd new file mode 100644 index 0000000..c4635c8 --- /dev/null +++ b/diagrams/DHModels/encounter.mmd @@ -0,0 +1,61 @@ +classDiagram + direction TB + + %% ── Encounter definition (prep / save layer) ────────────────────────── + class EncounterDefinition { + +UUID id + +String name + +[String] adversaryIDs + +[String] environmentIDs + +[PlayerConfig] playerConfigs + +String gmNotes + +Date createdAt + +Date modifiedAt + } + + class PlayerConfig { + +UUID id + +String name + +Int maxHP + +Int maxStress + +Int evasion + +Int thresholdMajor + +Int thresholdSevere + +Int armorSlots + } + + EncounterDefinition "1" *-- "0..*" PlayerConfig : playerConfigs + + %% adversaryIDs and environmentIDs are [String] slugs resolved at + %% session creation via Compendium (DHKit) + + %% ── Difficulty budget (static utility) ─────────────────────────────── + class DifficultyBudget { + <> + +cost(for: AdversaryType) Int$ + +baseBudget(playerCount:) Int$ + +totalCost(for: [AdversaryType]) Int$ + +rating(adversaryTypes:playerCount:budgetAdjustment:) Rating$ + +suggestedAdjustments(adversaryTypes:) Set~Adjustment~$ + } + + class DifficultyBudget_Rating { + +Int cost + +Int budget + +Int remaining + } + + class DifficultyBudget_Adjustment { + <> + easierFight + multipleSolos + boostedDamage + lowerTierAdversary + noBigThreats + harderFight + +Int pointValue + +String displayName + } + + DifficultyBudget ..> DifficultyBudget_Rating : returns + DifficultyBudget ..> DifficultyBudget_Adjustment : returns From 8e80832aff22fffa62e3485735e898c109518655 Mon Sep 17 00:00:00 2001 From: Fritz Date: Sun, 29 Mar 2026 10:40:34 -0700 Subject: [PATCH 2/2] breaking out stores --- .../DHKit.docc/Resources/encounterstore.svg | 1 + .../DHKit.docc/Resources/participants.svg | 2 +- Sources/DHKit/DHKit.docc/Resources/stores.svg | 2 +- .../Resources/catalog.svg | 2 +- .../Resources/content.svg | 2 +- .../Resources/encounter.svg | 2 +- diagrams/DHKit/encounterstore.mmd | 29 +++++++++++++++++++ diagrams/DHKit/stores.mmd | 27 ----------------- 8 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 Sources/DHKit/DHKit.docc/Resources/encounterstore.svg create mode 100644 diagrams/DHKit/encounterstore.mmd diff --git a/Sources/DHKit/DHKit.docc/Resources/encounterstore.svg b/Sources/DHKit/DHKit.docc/Resources/encounterstore.svg new file mode 100644 index 0000000..d9cf9d4 --- /dev/null +++ b/Sources/DHKit/DHKit.docc/Resources/encounterstore.svg @@ -0,0 +1 @@ +

throws

«Observable, MainActor»

EncounterStore

+[EncounterDefinition] definitions

+URL directory

+Bool isLoading

+Error? loadError

+localDirectory URL

+init(directory: URL)

+defaultDirectory() : URL$ async

+relocate(to:)

+load() : async

+create(name:) : async throws

+save(_:) : async throws

+delete(id:) : async throws

+duplicate(id:) : async throws

«enumeration»

EncounterStoreError

notFound(UUID)

saveFailed(UUID, String)

deleteFailed(UUID, String)

\ No newline at end of file diff --git a/Sources/DHKit/DHKit.docc/Resources/participants.svg b/Sources/DHKit/DHKit.docc/Resources/participants.svg index 23bec9e..2988747 100644 --- a/Sources/DHKit/DHKit.docc/Resources/participants.svg +++ b/Sources/DHKit/DHKit.docc/Resources/participants.svg @@ -1 +1 @@ -

«protocol»

EncounterParticipant

+UUID id

«protocol»

CombatParticipant

+Int currentHP

+Int maxHP

+Int currentStress

+Int maxStress

+Set<Condition> conditions

AdversarySlot

+UUID id

+String adversaryID

+String? customName

+Int maxHP

+Int maxStress

+Int currentHP

+Int currentStress

+Bool isDefeated

+Set<Condition> conditions

+init(from: Adversary, customName:)

+applying(currentHP:currentStress:isDefeated:conditions:) : AdversarySlot

PlayerSlot

+UUID id

+String name

+Int maxHP

+Int currentHP

+Int maxStress

+Int currentStress

+Int evasion

+Int thresholdMajor

+Int thresholdSevere

+Int armorSlots

+Int currentArmorSlots

+Set<Condition> conditions

+applying(currentHP:currentStress:currentArmorSlots:conditions:) : PlayerSlot

EnvironmentSlot

+UUID id

+String environmentID

+Bool isActive

+applying(isActive:) : EnvironmentSlot

\ No newline at end of file +

«protocol»

EncounterParticipant

+UUID id

«protocol»

CombatParticipant

+Int currentHP

+Int maxHP

+Int currentStress

+Int maxStress

+Set<Condition> conditions

AdversarySlot

+UUID id

+String adversaryID

+String? customName

+Int maxHP

+Int maxStress

+Int currentHP

+Int currentStress

+Bool isDefeated

+Set<Condition> conditions

+init(from: Adversary, customName:)

+applying(currentHP:currentStress:isDefeated:conditions:) : AdversarySlot

PlayerSlot

+UUID id

+String name

+Int maxHP

+Int currentHP

+Int maxStress

+Int currentStress

+Int evasion

+Int thresholdMajor

+Int thresholdSevere

+Int armorSlots

+Int currentArmorSlots

+Set<Condition> conditions

+applying(currentHP:currentStress:currentArmorSlots:conditions:) : PlayerSlot

EnvironmentSlot

+UUID id

+String environmentID

+Bool isActive

+applying(isActive:) : EnvironmentSlot

\ No newline at end of file diff --git a/Sources/DHKit/DHKit.docc/Resources/stores.svg b/Sources/DHKit/DHKit.docc/Resources/stores.svg index 27df159..cf83021 100644 --- a/Sources/DHKit/DHKit.docc/Resources/stores.svg +++ b/Sources/DHKit/DHKit.docc/Resources/stores.svg @@ -1 +1 @@ -

adversarySlots

playerSlots

environmentSlots

throws

throws

sessions

resolves catalog via make

resolves catalog

creates and owns

1

1

1

1

0..*

0..*

0..*

0..*

«Observable, MainActor»

EncounterSession

+UUID id

+String name

+[AdversarySlot] adversarySlots

+[PlayerSlot] playerSlots

+[EnvironmentSlot] environmentSlots

+Int fearPool

+Int hopePool

+UUID? spotlightedSlotID

+Int spotlightCount

+String gmNotes

+[AdversarySlot] activeAdversaries

+Bool isOver

+add(adversary: Adversary, customName:)

+add(environment: DaggerheartEnvironment)

+add(player: PlayerSlot)

+removeAdversary(withID:)

+removePlayer(withID:)

+spotlight(id:)

+yieldSpotlight()

+applyDamage(_:to:)

+applyHealing(_:to:)

+applyStress(_:to:)

+reduceStress(_:from:)

+applyCondition(_:to:)

+removeCondition(_:from:)

+markArmorSlot(for:)

+restoreArmorSlot(for:)

+incrementFear(by:)

+spendFear(by:)

+incrementHope(by:)

+spendHope(by:)

+make(from: EncounterDefinition, using: Compendium) : EncounterSession

AdversarySlot

+UUID id

PlayerSlot

+UUID id

EnvironmentSlot

+UUID id

«Observable, MainActor»

Compendium

+[String: Adversary] adversariesByID

+[String: DaggerheartEnvironment] environmentsByID

+[Adversary] adversaries

+[DaggerheartEnvironment] environments

+[Adversary] homebrewAdversaries

+[DaggerheartEnvironment] homebrewEnvironments

+Bool isLoading

+CompendiumError? loadError

+init(bundle: Bundle?)

+load() : async throws

+adversary(id:) : Adversary?

+environment(id:) : DaggerheartEnvironment?

+adversaries(ofTier:) : [Adversary]

+adversaries(ofRole:) : [Adversary]

+adversaries(matching:) : [Adversary]

+addAdversary(_:)

+removeHomebrewAdversary(id:)

+addEnvironment(_:)

+removeHomebrewEnvironment(id:)

+replaceSRDContent(adversaries:environments:)

+replaceSourceContent(sourceID:adversaries:environments:)

+removeSourceContent(sourceID:)

«enumeration»

CompendiumError

fileNotFound(resourceName:)

decodingFailed(resourceName:underlying:)

«Observable, MainActor»

EncounterStore

+[EncounterDefinition] definitions

+URL directory

+Bool isLoading

+Error? loadError

+localDirectory URL

+init(directory: URL)

+defaultDirectory() : URL$ async

+relocate(to:)

+load() : async

+create(name:) : async throws

+save(_:) : async throws

+delete(id:) : async throws

+duplicate(id:) : async throws

«enumeration»

EncounterStoreError

notFound(UUID)

saveFailed(UUID, String)

deleteFailed(UUID, String)

«Observable, MainActor»

SessionRegistry

+[UUID: EncounterSession] sessions

+init()

+session(for:definition:compendium:) : EncounterSession

+clearSession(for:)

+resetSession(for:definition:compendium:) : EncounterSession

\ No newline at end of file +

adversarySlots

playerSlots

environmentSlots

throws

sessions

resolves catalog via make

resolves catalog

creates and owns

1

1

1

1

0..*

0..*

0..*

0..*

«Observable, MainActor»

EncounterSession

+UUID id

+String name

+[AdversarySlot] adversarySlots

+[PlayerSlot] playerSlots

+[EnvironmentSlot] environmentSlots

+Int fearPool

+Int hopePool

+UUID? spotlightedSlotID

+Int spotlightCount

+String gmNotes

+[AdversarySlot] activeAdversaries

+Bool isOver

+add(adversary: Adversary, customName:)

+add(environment: DaggerheartEnvironment)

+add(player: PlayerSlot)

+removeAdversary(withID:)

+removePlayer(withID:)

+spotlight(id:)

+yieldSpotlight()

+applyDamage(_:to:)

+applyHealing(_:to:)

+applyStress(_:to:)

+reduceStress(_:from:)

+applyCondition(_:to:)

+removeCondition(_:from:)

+markArmorSlot(for:)

+restoreArmorSlot(for:)

+incrementFear(by:)

+spendFear(by:)

+incrementHope(by:)

+spendHope(by:)

+make(from: EncounterDefinition, using: Compendium) : EncounterSession

AdversarySlot

+UUID id

PlayerSlot

+UUID id

EnvironmentSlot

+UUID id

«Observable, MainActor»

Compendium

+[String: Adversary] adversariesByID

+[String: DaggerheartEnvironment] environmentsByID

+[Adversary] adversaries

+[DaggerheartEnvironment] environments

+[Adversary] homebrewAdversaries

+[DaggerheartEnvironment] homebrewEnvironments

+Bool isLoading

+CompendiumError? loadError

+init(bundle: Bundle?)

+load() : async throws

+adversary(id:) : Adversary?

+environment(id:) : DaggerheartEnvironment?

+adversaries(ofTier:) : [Adversary]

+adversaries(ofRole:) : [Adversary]

+adversaries(matching:) : [Adversary]

+addAdversary(_:)

+removeHomebrewAdversary(id:)

+addEnvironment(_:)

+removeHomebrewEnvironment(id:)

+replaceSRDContent(adversaries:environments:)

+replaceSourceContent(sourceID:adversaries:environments:)

+removeSourceContent(sourceID:)

«enumeration»

CompendiumError

fileNotFound(resourceName:)

decodingFailed(resourceName:underlying:)

«Observable, MainActor»

SessionRegistry

+[UUID: EncounterSession] sessions

+init()

+session(for:definition:compendium:) : EncounterSession

+clearSession(for:)

+resetSession(for:definition:compendium:) : EncounterSession

\ No newline at end of file diff --git a/Sources/DHModels/DaggerheartModels.docc/Resources/catalog.svg b/Sources/DHModels/DaggerheartModels.docc/Resources/catalog.svg index d1f88bf..e1cc068 100644 --- a/Sources/DHModels/DaggerheartModels.docc/Resources/catalog.svg +++ b/Sources/DHModels/DaggerheartModels.docc/Resources/catalog.svg @@ -1 +1 @@ -

role

attackRange

features

kind

features

1

1

0..*

0..*

Adversary

+String id

+String name

+String source

+Int tier

+AdversaryType role

+String flavorText

+String? motivesAndTactics

+Int difficulty

+Int thresholdMajor

+Int thresholdSevere

+Int hp

+Int stress

+String attackModifier

+String attackName

+AttackRange attackRange

+String damage

+String? experience

+[EncounterFeature] features

+Bool isHomebrew

«enumeration»

AdversaryType

bruiser

horde

leader

minion

ranged

skulk

social

solo

standard

support

«enumeration»

AttackRange

melee

veryClose

close

far

veryFar

«enumeration»

FeatureType

action

reaction

passive

+inferred(from:) : FeatureType

EncounterFeature

+String id

+String name

+String text

+FeatureType kind

DaggerheartEnvironment

+String id

+String name

+String source

+String flavorText

+[EncounterFeature] features

+Bool isHomebrew

«enumeration»

Condition

hidden

restrained

vulnerable

+String displayName

custom(String)

\ No newline at end of file +

role

attackRange

features

kind

features

1

1

0..*

0..*

Adversary

+String id

+String name

+String source

+Int tier

+AdversaryType role

+String flavorText

+String? motivesAndTactics

+Int difficulty

+Int thresholdMajor

+Int thresholdSevere

+Int hp

+Int stress

+String attackModifier

+String attackName

+AttackRange attackRange

+String damage

+String? experience

+[EncounterFeature] features

+Bool isHomebrew

«enumeration»

AdversaryType

bruiser

horde

leader

minion

ranged

skulk

social

solo

standard

support

«enumeration»

AttackRange

melee

veryClose

close

far

veryFar

«enumeration»

FeatureType

action

reaction

passive

+inferred(from:) : FeatureType

EncounterFeature

+String id

+String name

+String text

+FeatureType kind

DaggerheartEnvironment

+String id

+String name

+String source

+String flavorText

+[EncounterFeature] features

+Bool isHomebrew

«enumeration»

Condition

hidden

restrained

vulnerable

+String displayName

custom(String)

\ No newline at end of file diff --git a/Sources/DHModels/DaggerheartModels.docc/Resources/content.svg b/Sources/DHModels/DaggerheartModels.docc/Resources/content.svg index 168a23c..0161fdf 100644 --- a/Sources/DHModels/DaggerheartModels.docc/Resources/content.svg +++ b/Sources/DHModels/DaggerheartModels.docc/Resources/content.svg @@ -1 +1 @@ -

adversaries

environments

fingerprint

1

1

0..*

0..*

DHPackContent

+[Adversary] adversaries

+[DaggerheartEnvironment] environments

«catalog type — see catalog diagram»

Adversary

«catalog type — see catalog diagram»

DaggerheartEnvironment

ContentSource

+UUID id

+String? displayName

+URL? url

+Date addedAt

+Date? lastFetchedAt

+ContentFingerprint? fingerprint

+Int consecutiveFailures

+Bool isLocalImport

+Bool isThrottled(at: Date)

+Date nextAllowedFetch(at: Date)

+ContentSource recordingFailure(at: Date)

+ContentSource recordingSuccess(fingerprint:at:)

ContentFingerprint

+String sha256

+String? etag

«enumeration»

ContentStoreError

fetchThrottled

networkError

decodingFailed

writeFailed

readFailed

invalidContent

\ No newline at end of file +

adversaries

environments

fingerprint

1

1

0..*

0..*

DHPackContent

+[Adversary] adversaries

+[DaggerheartEnvironment] environments

«catalog type — see catalog diagram»

Adversary

«catalog type — see catalog diagram»

DaggerheartEnvironment

ContentSource

+UUID id

+String? displayName

+URL? url

+Date addedAt

+Date? lastFetchedAt

+ContentFingerprint? fingerprint

+Int consecutiveFailures

+Bool isLocalImport

+Bool isThrottled(at: Date)

+Date nextAllowedFetch(at: Date)

+ContentSource recordingFailure(at: Date)

+ContentSource recordingSuccess(fingerprint:at:)

ContentFingerprint

+String sha256

+String? etag

«enumeration»

ContentStoreError

fetchThrottled

networkError

decodingFailed

writeFailed

readFailed

invalidContent

\ No newline at end of file diff --git a/Sources/DHModels/DaggerheartModels.docc/Resources/encounter.svg b/Sources/DHModels/DaggerheartModels.docc/Resources/encounter.svg index 24e1c2f..a864a75 100644 --- a/Sources/DHModels/DaggerheartModels.docc/Resources/encounter.svg +++ b/Sources/DHModels/DaggerheartModels.docc/Resources/encounter.svg @@ -1 +1 @@ -

playerConfigs

returns

returns

1

0..*

EncounterDefinition

+UUID id

+String name

+[String] adversaryIDs

+[String] environmentIDs

+[PlayerConfig] playerConfigs

+String gmNotes

+Date createdAt

+Date modifiedAt

PlayerConfig

+UUID id

+String name

+Int maxHP

+Int maxStress

+Int evasion

+Int thresholdMajor

+Int thresholdSevere

+Int armorSlots

«utility»

DifficultyBudget

+cost(for: AdversaryType) : Int

+baseBudget(playerCount:) : Int

+totalCost(for: [AdversaryType]) : Int

+rating(adversaryTypes:playerCount:budgetAdjustment:) : Rating

+suggestedAdjustments(adversaryTypes:) : Set<Adjustment>

DifficultyBudget_Rating

+Int cost

+Int budget

+Int remaining

«enumeration»

DifficultyBudget_Adjustment

easierFight

multipleSolos

boostedDamage

lowerTierAdversary

noBigThreats

harderFight

+Int pointValue

+String displayName

\ No newline at end of file +

playerConfigs

returns

returns

1

0..*

EncounterDefinition

+UUID id

+String name

+[String] adversaryIDs

+[String] environmentIDs

+[PlayerConfig] playerConfigs

+String gmNotes

+Date createdAt

+Date modifiedAt

PlayerConfig

+UUID id

+String name

+Int maxHP

+Int maxStress

+Int evasion

+Int thresholdMajor

+Int thresholdSevere

+Int armorSlots

«utility»

DifficultyBudget

+cost(for: AdversaryType) : Int

+baseBudget(playerCount:) : Int

+totalCost(for: [AdversaryType]) : Int

+rating(adversaryTypes:playerCount:budgetAdjustment:) : Rating

+suggestedAdjustments(adversaryTypes:) : Set<Adjustment>

DifficultyBudget_Rating

+Int cost

+Int budget

+Int remaining

«enumeration»

DifficultyBudget_Adjustment

easierFight

multipleSolos

boostedDamage

lowerTierAdversary

noBigThreats

harderFight

+Int pointValue

+String displayName

\ No newline at end of file diff --git a/diagrams/DHKit/encounterstore.mmd b/diagrams/DHKit/encounterstore.mmd new file mode 100644 index 0000000..ce45de1 --- /dev/null +++ b/diagrams/DHKit/encounterstore.mmd @@ -0,0 +1,29 @@ +classDiagram + direction TB + + %% ── EncounterStore ─────────────────────────────────────────────────── + class EncounterStore { + <> + +[EncounterDefinition] definitions + +URL directory + +Bool isLoading + +Error? loadError + +init(directory: URL) + +defaultDirectory() URL$ async + +localDirectory URL$ + +relocate(to:) + +load() async + +create(name:) async throws + +save(_:) async throws + +delete(id:) async throws + +duplicate(id:) async throws + } + + class EncounterStoreError { + <> + notFound(UUID) + saveFailed(UUID, String) + deleteFailed(UUID, String) + } + + EncounterStore ..> EncounterStoreError : throws diff --git a/diagrams/DHKit/stores.mmd b/diagrams/DHKit/stores.mmd index 5f31806..27f4f70 100644 --- a/diagrams/DHKit/stores.mmd +++ b/diagrams/DHKit/stores.mmd @@ -82,33 +82,6 @@ classDiagram Compendium ..> CompendiumError : throws - %% ── EncounterStore ─────────────────────────────────────────────────── - class EncounterStore { - <> - +[EncounterDefinition] definitions - +URL directory - +Bool isLoading - +Error? loadError - +init(directory: URL) - +defaultDirectory() URL$ async - +localDirectory URL$ - +relocate(to:) - +load() async - +create(name:) async throws - +save(_:) async throws - +delete(id:) async throws - +duplicate(id:) async throws - } - - class EncounterStoreError { - <> - notFound(UUID) - saveFailed(UUID, String) - deleteFailed(UUID, String) - } - - EncounterStore ..> EncounterStoreError : throws - %% ── SessionRegistry ────────────────────────────────────────────────── class SessionRegistry { <>