Projections
A projection is the computed state at a specific message. Projections are never stored — they’re computed on demand by applying events to a snapshot.
Snapshots
Snapshots are the starting points for projection. There are two types:
Initial Snapshot
Created by the initial extraction on the first message. Contains the complete baseline state: time, location, characters, outfits, relationships, scene, and forecasts.
Chapter Snapshots
Created at chapter boundaries. When a chapter ends, BlazeTracker saves a snapshot of the projected state at that point. This means projecting state for message 50 doesn’t require replaying all events from message 1 — it can start from the most recent chapter snapshot.
How Projection Works
Snapshot → filter events (canonical, active, up to target message) → apply in order → compute climate → ProjectionStep by step:
- Find the best snapshot — The most recent snapshot before the target message (either the initial snapshot or a chapter snapshot)
- Filter events — Keep only events that are:
- From canonical swipes (currently selected swipe for each message)
- Not deleted (soft-deleted events are excluded)
- From messages after the snapshot and up to the target message
- Sort events — By message ID, then by timestamp within the same message
- Apply events — Each event mutates the projection state (add a mood, change position, advance time, etc.)
- Compute climate — After all events are applied, climate is computed deterministically from the forecast data + current time + current location. Climate is a derived value, not an event.
- Compute narrative events — Narrative description events are combined with state at each message (witnesses, location, tension) to produce display-ready narrative events
What’s in a Projection
interface Projection {
time: moment.Moment | null;
location: LocationState | null;
forecasts: Record<string, LocationForecast>;
climate: ClimateForecast | null;
scene: SceneState | null;
characters: Record<string, CharacterState>;
relationships: Record<string, RelationshipState>;
currentChapter: number;
charactersPresent: string[];
narrativeEvents: NarrativeEvent[];
}Key distinction from snapshots:
- Projections use
moment.Momentfor time (easy to manipulate, format, compare) - Snapshots use ISO strings for time (JSON-serializable for storage)
Climate is Computed, Not Stored
Climate (temperature, humidity, conditions, wind, daylight phase) is not stored as an event. It’s computed deterministically from:
- The forecast for the current area (a 28-day hourly forecast generated when entering an area)
- The current time (to look up the right hour)
- The current location type (indoor locations adjust temperature based on building type)
This means climate automatically updates when time or location changes, without needing a separate extraction step. See Procedural Weather for how forecasts are generated.
Swipe Context
Projection needs to know which swipe is canonical for each message. This is provided by a SwipeContext:
interface SwipeContext {
getCanonicalSwipeId(messageId: number): number;
}In practice, this reads from SillyTavern’s chat array, where each message has a swipe_id property indicating the currently selected swipe.
Performance: Chapter Snapshots
Without chapter snapshots, projecting state at message 100 would require replaying all events from message 1. Chapter snapshots act as checkpoints:
Initial Snapshot (msg 1)
→ Events msgs 1-30
→ Chapter 0 ends → Chapter Snapshot (msg 30)
→ Events msgs 31-65
→ Chapter 1 ends → Chapter Snapshot (msg 65)
→ Events msgs 66-100 ← only these need replayingThis keeps projection fast even in long conversations.