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:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user