Skip to content

Commit 6c4c89b

Browse files
committed
Spread events across the week with seed-based day scheduling
Events without day restrictions always fired on Monday because checkForEvent returns the first match. Now each standalone event is assigned a specific scheduledDay during seed-based selection, picking randomly from its allowed day range. - Arc events keep definition-based day checks (ordering matters) - "Any day" events restricted to weekdays (blockStart can't fire on weekends, and weekday-survival is the narrative arc) - scheduledDay persisted in save data
1 parent dfebc01 commit 6c4c89b

4 files changed

Lines changed: 58 additions & 4 deletions

File tree

src/state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export interface EventInstance {
7676
status: "pending" | "active" | "resolved";
7777
/** For major events: which choice the player made. */
7878
choiceId?: string;
79+
/** Day index (0-6) this event is scheduled to fire on. Assigned during selection. */
80+
scheduledDay?: number;
7981
}
8082

8183
/**

src/systems/eventSelection.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
type EventTier,
1010
eventPool,
1111
} from "../data/events";
12-
import type { EventInstance } from "../state";
12+
import { DAYS, type EventInstance } from "../state";
1313
import { seededRandom, seededShuffle } from "../utils/random";
1414
import type { PatternsData } from "./persistence";
1515

@@ -166,5 +166,52 @@ export function selectEventsForSeed(
166166
}
167167
}
168168

169+
// Assign each standalone event a specific day within its allowed range.
170+
// Without this, events fire on the first matching day (always Monday for
171+
// "any day" events). Arc events keep their definition-based day checks
172+
// since arc ordering depends on carefully designed day ranges.
173+
assignScheduledDays(selected, seed);
174+
169175
return selected;
170176
}
177+
178+
/** Salt offset for day scheduling (distinct from selection salts). */
179+
const SALT_SCHEDULE_DAY = 5200;
180+
181+
/**
182+
* Assigns a scheduledDay to each non-arc standalone event, picking from its
183+
* allowed day range using seeded random. Arc events are skipped (their day
184+
* ranges are designed for narrative ordering).
185+
*/
186+
function assignScheduledDays(events: EventInstance[], seed: number): void {
187+
for (let i = 0; i < events.length; i++) {
188+
const instance = events[i];
189+
if (!instance) continue;
190+
191+
const definition = eventPool.find((e) => e.id === instance.id);
192+
if (!definition || definition.arcId) continue;
193+
194+
// Build allowed day indices from definition
195+
let allowedDays: number[];
196+
if (definition.timing.day) {
197+
const days = Array.isArray(definition.timing.day)
198+
? definition.timing.day
199+
: [definition.timing.day];
200+
allowedDays = days.map((d) => DAYS.indexOf(d)).filter((idx) => idx >= 0);
201+
} else {
202+
// Any day: restrict to weekdays (0-4) since blockStart events can't
203+
// fire on weekends (no time block transitions) and weekend scheduling
204+
// doesn't fit the weekday-survival narrative arc
205+
allowedDays = [0, 1, 2, 3, 4];
206+
}
207+
208+
if (allowedDays.length === 0) continue;
209+
210+
// Pick one day from the range
211+
const r = seededRandom(seed, SALT_SCHEDULE_DAY + i);
212+
const dayIndex = allowedDays[Math.floor(r * allowedDays.length)];
213+
if (dayIndex !== undefined) {
214+
instance.scheduledDay = dayIndex;
215+
}
216+
}
217+
}

src/systems/events.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ export function checkForEvent(
4141
// Check phase
4242
if (definition.timing.phase !== phase) continue;
4343

44-
// Check day
45-
if (definition.timing.day) {
44+
// Check day: use scheduled day if assigned, otherwise fall back to definition
45+
if (instance.scheduledDay !== undefined) {
46+
if (state.dayIndex !== instance.scheduledDay) continue;
47+
} else if (definition.timing.day) {
4648
const days = Array.isArray(definition.timing.day)
4749
? definition.timing.day
4850
: [definition.timing.day];

src/systems/persistence.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ interface SavedTask {
3737
succeededToday: boolean;
3838
}
3939

40-
/** Persisted event instance state (status + player choice). */
40+
/** Persisted event instance state (status + player choice + scheduling). */
4141
interface SavedEventInstance {
4242
id: EventId;
4343
status: EventInstance["status"];
4444
choiceId?: string;
45+
scheduledDay?: number;
4546
}
4647

4748
/** Minimal game state for persistence - no translatable content. */
@@ -168,6 +169,7 @@ function toSavedState(state: GameState): SavedState {
168169
id: e.id,
169170
status: e.status,
170171
choiceId: e.choiceId,
172+
scheduledDay: e.scheduledDay,
171173
})),
172174
eventFlags: state.eventFlags,
173175
};
@@ -365,6 +367,7 @@ function fromSavedState(saved: SavedState): GameState {
365367
id: e.id,
366368
status: e.status,
367369
choiceId: e.choiceId,
370+
scheduledDay: e.scheduledDay,
368371
}));
369372

370373
// Derive activeEventId from the events array

0 commit comments

Comments
 (0)