D&D 5e death-save state machine, undo/redo fixes, dev bulk delete #4

Merged
robert merged 11 commits from single-source into main 2026-07-06 22:14:05 -04:00
3 changed files with 19 additions and 5 deletions
Showing only changes of commit 532af1ecc4 - Show all commits
+4
View File
@@ -7,6 +7,7 @@ Backlog of bugs + long-term items. Milestones live in REWORK_PLAN.md.
### dm list - keep active particpant in view (scroll) ### dm list - keep active particpant in view (scroll)
not sure good way to do this
### combat.js doesnt seem to actually exercise the add characters. there are no characters in the campaign. only in the encounter.. ### combat.js doesnt seem to actually exercise the add characters. there are no characters in the campaign. only in the encounter..
@@ -99,6 +100,9 @@ Backlog of bugs + long-term items. Milestones live in REWORK_PLAN.md.
- Migration: keep old log entries readable; new format for new writes. - Migration: keep old log entries readable; new format for new writes.
### quality of life fix: 2. UI says "Campaign Characters", field is players --- naming mismatch (separate concern, flag for later)
## FEAT - parallel campaigns ## FEAT - parallel campaigns
## FEAT - multi user ## FEAT - multi user
+4 -3
View File
@@ -41,9 +41,9 @@ const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
function buildRoster() { function buildRoster() {
return [ return [
{ name: 'Fighter', defaultMaxHp: 200, defaultInitMod: 2 }, { id: 'char-fighter', name: 'Fighter', defaultMaxHp: 200, defaultInitMod: 2 },
{ name: 'Cleric', defaultMaxHp: 180, defaultInitMod: 1 }, { id: 'char-cleric', name: 'Cleric', defaultMaxHp: 180, defaultInitMod: 1 },
{ name: 'Rogue', defaultMaxHp: 160, defaultInitMod: 3 }, { id: 'char-rogue', name: 'Rogue', defaultMaxHp: 160, defaultInitMod: 3 },
]; ];
} }
function buildMonsters() { function buildMonsters() {
@@ -239,6 +239,7 @@ module.exports = async function replay(args) {
await callStep('setup_campaign', { campaignId }, async () => await callStep('setup_campaign', { campaignId }, async () =>
storage.setDoc(getPath.campaign(campaignId), { storage.setDoc(getPath.campaign(campaignId), {
id: campaignId, name: `Replay Campaign ${runStamp}`, createdAt: Date.now(), id: campaignId, name: `Replay Campaign ${runStamp}`, createdAt: Date.now(),
players: buildRoster(),
}) })
); );
await callStep('setup_encounter', { encounterId }, async () => await callStep('setup_encounter', { encounterId }, async () =>
+11 -2
View File
@@ -56,7 +56,7 @@ let display = {
hideNpcHp: false, hideNpcHp: false,
}; };
const CAMPAIGN_ID = 'camp-1'; const CAMPAIGN_ID = 'camp-1';
const roster = []; const CAMPAIGN_PATH = `campaigns/${CAMPAIGN_ID}`;const roster = [];
let storage, ctx; let storage, ctx;
const DISPLAY_PATH = 'activeDisplay/status'; const DISPLAY_PATH = 'activeDisplay/status';
@@ -74,7 +74,16 @@ const any = (name) => enc.participants.find(p => p.name === name);
const anyById = (id) => enc.participants.find(p => p.id === id); const anyById = (id) => enc.participants.find(p => p.id === id);
function addCharacterViaUI(name, maxHp, initMod) { function addCharacterViaUI(name, maxHp, initMod) {
roster.push({ id: `char-${name}`, name, defaultMaxHp: maxHp, defaultInitMod: initMod }); const character = { id: `char-${name}`, name, defaultMaxHp: maxHp, defaultInitMod: initMod };
roster.push(character);
// persist to campaign doc (mirrors app: players array)
if (storage) {
const existing = (storage.docs.get(CAMPAIGN_PATH) || {}).players || [];
storage.setDoc(CAMPAIGN_PATH, {
id: CAMPAIGN_ID, name: 'Scenario Campaign', createdAt: Date.now(),
players: [...existing, character],
});
}
} }
async function addMonsterParticipant(name, maxHp, initMod, asNpc = false) { async function addMonsterParticipant(name, maxHp, initMod, asNpc = false) {