macOS SwiftUI app (macOS 14+, Swift 6, @Observable) for running Shadowdark RPG combat encounters. 3-pane UI with one-click dice rolling for fast combat.
- 3-pane UI: Initiative list (left), combatant detail (center), roll log (right)
- Combat tracking: Initiative, rounds, HP, AC, conditions, allegiance
- One-click rolling: Attack/spell rolls with advantage/disadvantage, auto-damage on hit
- Save/load encounters to disk as
.sdencJSON files - Party persistence: Save party members separately, load them into any new encounter instantly
- Monster/NPC templates: Pre-built stat blocks saved as reusable templates, drag into encounters fast
ShadowdarkTracker/
├── ShadowdarkTrackerApp.swift
├── Models/
│ ├── Allegiance.swift # enum: ally, enemy, neutral
│ ├── Condition.swift # enum: blinded, paralyzed, poisoned, etc.
│ ├── AbilityScores.swift # STR-CHA + modifier table
│ ├── DiceExpression.swift # Parsed "2d10+3" struct
│ ├── Attack.swift # name, bonus, damage, quantity
│ ├── Spell.swift # name, dc, damage, range, description
│ ├── SpecialAbility.swift # name, description
│ ├── Combatant.swift # Unified PC/Monster/NPC model
│ ├── RollLogEntry.swift # Single roll result record
│ ├── Encounter.swift # Combatants + round state + roll log
│ ├── Party.swift # Named group of PCs for reuse
│ └── CombatantTemplate.swift # Reusable monster/NPC stat block
├── Dice/
│ ├── DiceParser.swift # Regex parser for dice notation
│ └── DiceRoller.swift # Roll engine with adv/disadv
├── Services/
│ ├── FileService.swift # Encounter save/load (.sdenc JSON)
│ ├── PartyStore.swift # Party save/load (.sdparty JSON)
│ └── TemplateStore.swift # Monster/NPC template library (.sdtemplate JSON)
├── State/
│ └── EncounterManager.swift # @Observable source of truth
├── Views/
│ ├── ContentView.swift # HSplitView 3-pane root
│ ├── Initiative/
│ │ ├── InitiativeListView.swift
│ │ └── InitiativeRowView.swift
│ ├── Detail/
│ │ ├── CombatantDetailView.swift # Router: PC vs Monster/NPC
│ │ ├── PCDetailView.swift
│ │ ├── MonsterDetailView.swift
│ │ └── AttackActionView.swift # Roll button row per attack
│ ├── RollLog/
│ │ ├── RollLogView.swift
│ │ └── RollLogEntryView.swift
│ ├── Toolbar/
│ │ └── EncounterToolbar.swift # Round controls, save/load, add combatant
│ ├── Shared/
│ │ ├── HPBar.swift
│ │ ├── ConditionBadges.swift
│ │ └── DiceExpressionField.swift
│ └── Library/
│ ├── PartyPickerView.swift # Load saved party into encounter
│ ├── TemplateLibraryView.swift # Browse/search monster & NPC templates
│ └── TemplateEditorView.swift # Create/edit a template
└── Resources/
└── Assets.xcassets
struct Combatant: Codable, Identifiable, Equatable {
let id: UUID
var name: String
var kind: CombatantKind // .pc, .monster, .npc
var allegiance: Allegiance // .ally, .enemy, .neutral
var ac: Int
var maxHP: Int
var currentHP: Int
var initiative: Int?
var conditions: Set<Condition>
var dexModifier: Int
var abilityScores: AbilityScores?
var attacks: [Attack]
var spells: [Spell]
var specialAbilities: [SpecialAbility]
var movement: String?
var level: Int?
var alignment: String?
}struct Encounter: Codable, Identifiable {
let id: UUID
var name: String
var combatants: [Combatant]
var currentRound: Int
var activeCombatantID: UUID?
var rollLog: [RollLogEntry]
var createdAt: Date
var modifiedAt: Date
}| Phase | Files | What |
|---|---|---|
| 1 | Leaf models | Condition, Allegiance, AbilityScores, DiceExpression |
| 2 | Composite models | Attack, Spell, SpecialAbility |
| 3 | Core models | Combatant, RollLogEntry, Encounter, Party, CombatantTemplate |
| 4 | Dice engine | DiceParser, DiceRoller |
| 5 | File services | FileService, PartyStore, TemplateStore |
| 6 | State | EncounterManager |
| 7 | Shared views | HPBar, ConditionBadges, DiceExpressionField |
| 8 | Left pane | InitiativeRowView, InitiativeListView |
| 9 | Center pane | AttackActionView, PCDetailView, MonsterDetailView, CombatantDetailView |
| 10 | Right pane | RollLogEntryView, RollLogView |
| 11 | Library views | PartyPickerView, TemplateLibraryView, TemplateEditorView |
| 12 | Toolbar + root | EncounterToolbar, ContentView, ShadowdarkTrackerApp |