feat: CradleScene + UIScene + GameScene cycle integration

Scene integration for the Great Cycle system:
- CradleScene: shows "Великий Цикл N: Тема | Ран X/7", narrative quote
- UIScene: cycle info bar and run phase display below health
- GameScene: world trace spawning (ruins/markers from past runs),
  trace glow rendering, death position recording, cycle info to registry,
  biomeId + worldSeed passed to RunState
- Scene flow: Fractal → RenewalScene (on 7th run) → Cradle

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Денис Шкабатур
2026-02-12 18:51:19 +03:00
parent 91d4e4d730
commit d9213b6be0
3 changed files with 115 additions and 3 deletions

View File

@@ -15,7 +15,10 @@ import { isSchoolUnlocked } from '../run/meta';
import { GAME_WIDTH, GAME_HEIGHT } from '../config';
import { getAvailableBonuses, purchaseBonus, canAffordBonus, resetShopSession } from '../mycelium/shop';
import { getGraphStats } from '../mycelium/graph';
import { getCycleSummary } from '../run/cycle';
import { CYCLE_THEME_NAMES_RU } from '../run/types';
import type { BonusEffect } from '../mycelium/types';
import narrativeData from '../data/cycle-narrative.json';
const schools = schoolsData as unknown as SchoolData[];
const biomes = biomeDataArray as BiomeData[];
@@ -107,6 +110,9 @@ export class CradleScene extends Phaser.Scene {
// Meta info (top-right)
const graphStats = getGraphStats(this.meta.mycelium);
const cycleSummary = getCycleSummary(this.meta.greatCycle);
const themeName = CYCLE_THEME_NAMES_RU[cycleSummary.theme];
const metaInfo = [
`Раны: ${this.meta.totalRuns}`,
`Кодекс: ${this.meta.codex.length}`,
@@ -118,13 +124,40 @@ export class CradleScene extends Phaser.Scene {
fontFamily: 'monospace',
}).setOrigin(1, 0).setDepth(10);
// Spore count (top-right, below meta info, updates dynamically)
// Spore count (top-right, below meta info)
this.sporeCountText = this.add.text(GAME_WIDTH - 12, 28, `🍄 Споры: ${this.meta.spores}`, {
fontSize: '13px',
color: '#44ff88',
fontFamily: 'monospace',
});
this.sporeCountText.setOrigin(1, 0).setDepth(10);
// Cycle info (top-right, below spores)
const cycleInfo = `Великий Цикл ${cycleSummary.cycleNumber}: ${themeName} | Ран ${cycleSummary.runInCycle}/${cycleSummary.totalRuns}`;
this.add.text(GAME_WIDTH - 12, 46, cycleInfo, {
fontSize: '11px',
color: '#336644',
fontFamily: 'monospace',
}).setOrigin(1, 0).setDepth(10);
// Cycle narrative quote (top-left, fades in slowly)
const themeNarrative = (narrativeData.themes as Record<string, { cradleQuoteRu: string }>)[cycleSummary.theme];
if (themeNarrative) {
const quoteText = this.add.text(12, 12, themeNarrative.cradleQuoteRu, {
fontSize: '12px',
color: '#223322',
fontFamily: 'monospace',
fontStyle: 'italic',
});
quoteText.setAlpha(0);
quoteText.setDepth(10);
this.tweens.add({
targets: quoteText,
alpha: 0.5,
duration: 3000,
delay: 1000,
});
}
}
private showSchoolSelection(): void {

View File

@@ -72,6 +72,10 @@ import { createMycosisState, updateMycosis, getMycosisVisuals } from '../myceliu
import type { FungalNodeInfo, MycosisState, MemoryFlash } from '../mycelium/types';
import { FUNGAL_NODE_CONFIG, MYCOSIS_CONFIG } from '../mycelium/types';
// World traces (Great Cycle)
import { spawnWorldTraces, updateTraceGlow, type WorldTraceInfo } from '../world/traces';
import { CYCLE_THEME_NAMES_RU } from '../run/types';
export class GameScene extends Phaser.Scene {
private gameWorld!: GameWorld;
private bridge!: PhaserBridge;
@@ -129,6 +133,10 @@ export class GameScene extends Phaser.Scene {
private fungalNodeGlowGraphics!: Phaser.GameObjects.Graphics;
private hasDepositedThisRun = false;
// World traces from past runs
private worldTraceData: WorldTraceInfo[] = [];
private worldTraceGraphics!: Phaser.GameObjects.Graphics;
constructor() {
super({ key: 'GameScene' });
}
@@ -144,14 +152,14 @@ export class GameScene extends Phaser.Scene {
init(data: { meta: MetaState; schoolId: string; runId: number; purchasedEffects?: import('../mycelium/types').BonusEffect[]; biomeId?: string }): void {
this.meta = data.meta;
this.runState = createRunState(data.runId, data.schoolId);
this.biomeId = data.biomeId ?? 'catalytic-wastes';
this.runState = createRunState(data.runId, data.schoolId, this.biomeId);
this.crisisState = null;
this.playerDead = false;
this.mycosisState = createMycosisState();
this.hasDepositedThisRun = false;
this.memoryFlashTimer = 0;
this.purchasedEffects = data.purchasedEffects ?? [];
this.biomeId = data.biomeId ?? 'catalytic-wastes';
this.schoolBonuses = getSchoolBonuses(data.schoolId);
}
@@ -164,6 +172,7 @@ export class GameScene extends Phaser.Scene {
// 2. Generate world — use selected biome
const biome = (biomeDataArray as BiomeData[]).find(b => b.id === this.biomeId) ?? biomeDataArray[0] as BiomeData;
this.worldSeed = Date.now() % 1000000;
this.runState.worldSeed = this.worldSeed;
const worldData = generateWorld(biome, this.worldSeed);
// 3. Create tilemap
@@ -184,6 +193,13 @@ export class GameScene extends Phaser.Scene {
this.gameWorld.world, worldData.grid, biome, this.worldSeed,
);
// 5c. Spawn world traces from past runs (Great Cycle)
this.worldTraceData = spawnWorldTraces(
this.gameWorld.world, this.meta.greatCycle, this.biomeId, biome,
);
this.worldTraceGraphics = this.add.graphics();
this.worldTraceGraphics.setDepth(4); // above tiles, below entities
// 6. Initialize creature systems — filter by biome
const allSpecies = speciesDataArray as SpeciesData[];
const biomeSpecies = allSpecies.filter(s => s.biome === biome.id);
@@ -581,6 +597,12 @@ export class GameScene extends Phaser.Scene {
}
this.registry.set('invCounts', counts);
// 14. Push cycle info to registry for UIScene
this.registry.set('cycleNumber', this.meta.greatCycle.cycleNumber);
this.registry.set('runInCycle', this.meta.greatCycle.runInCycle);
this.registry.set('cycleThemeRu', CYCLE_THEME_NAMES_RU[this.meta.greatCycle.theme]);
this.registry.set('runPhaseRu', RUN_PHASE_NAMES_RU[this.runState.phase]);
// 15. Creature observation for UIScene
const nearbyCreatures = getObservableCreatures(this.gameWorld.world);
if (nearbyCreatures.length > 0) {
@@ -698,6 +720,25 @@ export class GameScene extends Phaser.Scene {
);
}
// Render world trace glows (past run markers)
this.worldTraceGraphics.clear();
updateTraceGlow(this.worldTraceData, delta);
for (const trace of this.worldTraceData) {
const eid = trace.eid;
const phase = (this.worldTraceGraphics as unknown as { _phase?: number })._phase ?? 0;
const glowPhase = phase + Position.x[eid] * 0.01; // offset per position
const pulse = 0.3 + 0.7 * Math.sin(glowPhase + this.runState.elapsed * 0.001);
const color = trace.traceType === 'death_site' ? 0xaa3333 : 0x4488aa;
const alpha = 0.1 + 0.1 * pulse;
const radius = 8 * (0.7 + 0.3 * pulse);
this.worldTraceGraphics.fillStyle(color, alpha);
this.worldTraceGraphics.fillCircle(Position.x[eid], Position.y[eid], radius);
// Inner core
this.worldTraceGraphics.fillStyle(color, alpha * 2);
this.worldTraceGraphics.fillCircle(Position.x[eid], Position.y[eid], radius * 0.4);
}
// Fade memory flash text
if (this.memoryFlashTimer > 0) {
this.memoryFlashTimer -= delta;
@@ -797,6 +838,14 @@ export class GameScene extends Phaser.Scene {
this.playerDead = true;
this.runState.alive = false;
// Record death position for great cycle traces
const px = Position.x[this.playerEid] ?? 0;
const py = Position.y[this.playerEid] ?? 0;
this.runState.deathPosition = {
tileX: Math.floor(px / this.tileSize),
tileY: Math.floor(py / this.tileSize),
};
// Auto-deposit all discoveries into Mycelium on death
if (!this.hasDepositedThisRun) {
depositKnowledge(this.meta.mycelium, this.runState);

View File

@@ -51,6 +51,12 @@ export class UIScene extends Phaser.Scene {
// Controls hint
private controlsText!: Phaser.GameObjects.Text;
// Cycle info (top-left, below health)
private cycleText!: Phaser.GameObjects.Text;
// Run phase (top-left, below cycle)
private phaseText!: Phaser.GameObjects.Text;
constructor() {
super({ key: 'UIScene' });
}
@@ -135,6 +141,20 @@ export class UIScene extends Phaser.Scene {
});
this.invText.setOrigin(1, 1);
// === Cycle info (top-left, below health bar) ===
this.cycleText = this.add.text(12, 32, '', {
fontSize: '10px',
color: '#446644',
fontFamily: 'monospace',
});
// === Run phase (below cycle) ===
this.phaseText = this.add.text(12, 46, '', {
fontSize: '10px',
color: '#557755',
fontFamily: 'monospace',
});
// === Controls hint (top-right) ===
this.controlsText = this.add.text(w - 12, 12, 'WASD move | E collect | F throw | 1-4 slots | scroll zoom', {
fontSize: '10px',
@@ -192,5 +212,15 @@ export class UIScene extends Phaser.Scene {
// === Update inventory ===
this.invText.setText(`${Math.round(invWeight)}/${invMaxWeight} AMU | ${invSlots} items`);
// === Update cycle info ===
const cycleNumber = (this.registry.get('cycleNumber') as number) ?? 1;
const runInCycle = (this.registry.get('runInCycle') as number) ?? 1;
const cycleTheme = (this.registry.get('cycleThemeRu') as string) ?? '';
this.cycleText.setText(`Цикл ${cycleNumber}: ${cycleTheme} | Ран ${runInCycle}/7`);
// === Update run phase ===
const runPhaseRu = (this.registry.get('runPhaseRu') as string) ?? '';
this.phaseText.setText(runPhaseRu);
}
}