Skip to content

refactor: redesign slot types, tighten protocol contracts, fix concurrency#9

Merged
gwillish merged 5 commits intomainfrom
refactor/slot-types-and-api-cleanup
Mar 29, 2026
Merged

refactor: redesign slot types, tighten protocol contracts, fix concurrency#9
gwillish merged 5 commits intomainfrom
refactor/slot-types-and-api-cleanup

Conversation

@gwillish
Copy link
Copy Markdown
Owner

Summary

  • Slot types moved to DHModelsAdversarySlot and EnvironmentSlot extracted from EncounterSession.swift into Sources/DHModels/, alongside PlayerSlot. All three now have let-only properties (immutable read-only projections); mutations flow through EncounterSession via copy-with-update.
  • DHKit ungated — removed the #if canImport(Darwin) conditional from Package.swift; DHKit and DHKitTests are now unconditional cross-platform targets. Linux CI updated to build and test them.
  • Protocol contracts tightenedCombatParticipant is now { get } only (pure display contract). All combat mutation methods on EncounterSession take UUID instead of some CombatParticipant. heal renamed to applyHealing.
  • API naming fixesDifficultyBudget.cost(for:) internal param typerole; Compendium.searchAdversaries(query:)adversaries(matching:).
  • Concurrency@concurrent on Compendium.decodeArray; reentrancy guards on all four async EncounterStore methods; EncounterStoreError stores String descriptions and conforms to Sendable.

Test plan

  • swift build — all targets compile clean
  • swift test — 140 tests pass (0 failures)
  • ./Scripts/format.sh — no lint warnings
  • Linux CI will run on this PR (DHModelsTests + DHKitTests)

…rency

Package structure:
- Remove #if canImport(Darwin) gate; DHKit and DHKitTests are now
  unconditional, cross-platform targets
- Update Linux CI to build DHKit and run DHKitTests

Slot type redesign:
- Extract AdversarySlot and EnvironmentSlot from EncounterSession.swift
  into Sources/DHModels/ alongside PlayerSlot
- Make all mutable slot properties `let`; types are now read-only
  value projections
- Remove nonisolated annotation from all three structs (was a no-op)
- Remove AdversarySlot.make(from:customName:) public factory; construction
  from Adversary now happens internally in EncounterSession
- EncounterSession stores private _adversarySlots/_playerSlots/_environmentSlots
  and exposes public read-only computed properties; mutations use
  copy-with-update (no more in-place var mutation footgun)
- Remove modifying(in:id:_:) helper (required { get set }; no longer needed)

Protocol changes + mutation API:
- CombatParticipant is now { get } only — pure read/display contract
- All combat mutation methods take UUID instead of some CombatParticipant
- spotlight(_:) keeps its EncounterParticipant-based signature
- heal renamed to applyHealing for consistency

API naming:
- DifficultyBudget.cost(for:) internal param type → role
- Compendium.searchAdversaries(query:) → adversaries(matching:)

Concurrency:
- Add @Concurrent to Compendium.decodeArray
- Add reentrancy guards (in-flight Sets/flag) to all four async
  EncounterStore methods
- EncounterStoreError stores String descriptions instead of Error
  payloads; conforms to Sendable
- Add applying(...) copy-with-update helpers to AdversarySlot and PlayerSlot;
  refactor all EncounterSession mutation methods to use them (removes ~70 lines
  of repetitive full-struct reconstruction)
- Add logger.warning for unmatched-ID mutations in EncounterSession
- Rename removeAdversary(id:)/removePlayer(id:) → removeAdversary(withID:)/removePlayer(withID:)
- Rename Compendium.adversaries(ofType:) → adversaries(ofRole:)
- Rename DifficultyBudget.Rating.totalCost → .cost to avoid shadowing the static method
- Strengthen EncounterStore.save(_:) doc comment re: dropped concurrent saves
@gwillish gwillish merged commit 7e5f3cb into main Mar 29, 2026
2 checks passed
@gwillish gwillish deleted the refactor/slot-types-and-api-cleanup branch March 29, 2026 05:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant