feat: Great Cycle system — 7-run macro cycles with traces and narrative

Core engine for the Great Cycle mechanic (GDD Section IV):
- GreatCycleState tracking: cycle number, run within cycle, theme
- RunTrace recording: death position, school, biome, discoveries per run
- Cycle advancement: auto-advance to next cycle after 7 runs
- Great Renewal: resets traces, advances theme, strengthens Mycelium
- 6 narrative themes (Awakening→Doubt→Realization→Attempt→Acceptance→Synthesis)
- Cycle world modifiers: terrain/resources/creatures scale with cycle
- Narrative data JSON with Russian/English lore fragments per theme
- MetaState + persistence + RunState extended with cycle fields
- 38 new tests (549 total), all passing

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Денис Шкабатур
2026-02-12 18:44:45 +03:00
parent 0cd995c817
commit 5f78aa1444
7 changed files with 929 additions and 6 deletions

View File

@@ -117,6 +117,8 @@ export interface RunState {
runId: number;
/** Selected school id */
schoolId: string;
/** Biome being explored this run */
biomeId: string;
/** Current phase */
phase: RunPhase;
/** Time spent in current phase (ms) */
@@ -133,6 +135,10 @@ export interface RunState {
discoveries: RunDiscoveries;
/** Whether the player is alive */
alive: boolean;
/** World seed for this run */
worldSeed: number;
/** Player death position in tile coords (set on death) */
deathPosition: { tileX: number; tileY: number } | null;
}
export interface RunDiscoveries {
@@ -168,6 +174,8 @@ export interface MetaState {
runHistory: RunSummary[];
/** Mycelium knowledge graph (persistent between runs) */
mycelium: MyceliumGraphData;
/** Great cycle state (7-run macro cycles) */
greatCycle: GreatCycleState;
}
/** Serializable Mycelium graph data (stored in MetaState) */
@@ -214,6 +222,10 @@ export interface RunSummary {
discoveries: number;
sporesEarned: number;
crisisResolved: boolean;
/** Biome explored (added Phase 11) */
biomeId?: string;
/** Great cycle number when this run occurred */
cycleNumber?: number;
}
// ─── Body Composition (for death animation) ──────────────────────
@@ -232,6 +244,107 @@ export const BODY_COMPOSITION: { symbol: string; fraction: number }[] = [
{ symbol: 'Fe', fraction: 0.001 },
];
// ─── Great Cycle (every 7 runs) ──────────────────────────────────
/** Narrative themes for each great cycle (GDD Section IV) */
export type CycleTheme =
| 'awakening' // Cycle 1 (runs 1-7): learning the world
| 'doubt' // Cycle 2 (runs 8-14): finding traces of past adepts
| 'realization' // Cycle 3 (runs 15-21): understanding the nature of cycles
| 'attempt' // Cycle 4 (runs 22-28): first attempts to transcend
| 'acceptance' // Cycle 5 (runs 29-35): cycle is not a prison
| 'synthesis'; // Cycle 6+ (runs 36+): unifying all knowledge
/** Human-readable names for CycleTheme */
export const CYCLE_THEME_NAMES: Record<CycleTheme, string> = {
awakening: 'Awakening',
doubt: 'Doubt',
realization: 'Realization',
attempt: 'Attempt',
acceptance: 'Acceptance',
synthesis: 'Synthesis',
};
export const CYCLE_THEME_NAMES_RU: Record<CycleTheme, string> = {
awakening: 'Пробуждение',
doubt: 'Сомнение',
realization: 'Осознание',
attempt: 'Попытка',
acceptance: 'Принятие',
synthesis: 'Синтез',
};
/** Ordered themes by cycle number (0-indexed, cycles beyond 6 repeat 'synthesis') */
export const CYCLE_THEMES: CycleTheme[] = [
'awakening',
'doubt',
'realization',
'attempt',
'acceptance',
'synthesis',
];
/** A trace left by a completed run within a great cycle */
export interface RunTrace {
/** Run ID this trace belongs to */
runId: number;
/** Position within the great cycle (1-7) */
runInCycle: number;
/** School used this run */
schoolId: string;
/** Biome explored */
biomeId: string;
/** Death location (tile coords), null if run completed via boss victory */
deathPosition: { tileX: number; tileY: number } | null;
/** Phase reached when run ended */
phaseReached: RunPhase;
/** Was the crisis resolved? */
crisisResolved: boolean;
/** Number of unique discoveries */
discoveryCount: number;
/** Key element discoveries (up to 5 symbols for trace markers) */
keyElements: string[];
/** Duration of the run in ms */
duration: number;
/** World seed used for this run */
worldSeed: number;
}
/** Persistent great cycle state (stored in MetaState) */
export interface GreatCycleState {
/** Current great cycle number (1-based) */
cycleNumber: number;
/** Current run within the cycle (1-7, resets on renewal) */
runInCycle: number;
/** Current cycle's theme */
theme: CycleTheme;
/** Traces from runs in the CURRENT cycle (cleared on renewal) */
currentCycleTraces: RunTrace[];
/** Traces from the PREVIOUS cycle (for cross-cycle references) */
previousCycleTraces: RunTrace[];
/** Total great renewals completed */
renewalsCompleted: number;
/** Mycelium maturation level (increases each renewal) */
myceliumMaturation: number;
}
/** Number of runs per great cycle */
export const RUNS_PER_CYCLE = 7;
/** World generation modifiers based on cycle number */
export interface CycleWorldModifiers {
/** Elevation noise scale multiplier (world shape variation) */
elevationScaleMultiplier: number;
/** Detail noise scale multiplier */
detailScaleMultiplier: number;
/** Resource density multiplier */
resourceDensityMultiplier: number;
/** Creature spawn rate multiplier */
creatureSpawnMultiplier: number;
/** Escalation rate multiplier (difficulty) */
escalationRateMultiplier: number;
}
// ─── Constants ───────────────────────────────────────────────────
/** Phase durations in ms (approximate, can be adjusted) */