import { Container, Graphics, Text } from 'pixi.js'; import { hazardColor, makeViewTransform } from './app.js'; import { playerClouds } from '../sim/clouds.js'; import { playerSystem } from '../sim/player.js'; import { otherEnd } from '../sim/galaxy.js'; export class GalaxyView { // onSelectSystem(systemId) -> UI intel panel constructor(renderer, world, { onSelectSystem, onJump }) { this.renderer = renderer; this.world = world; this.onSelectSystem = onSelectSystem; this.onJump = onJump; this.container = new Container(); this.labelContainer = new Container(); renderer.world.addChild(this.container); renderer.labels.addChild(this.labelContainer); this.staticG = new Graphics(); this.cloudG = new Graphics(); this.dynamicG = new Graphics(); this.container.addChild(this.staticG, this.cloudG, this.dynamicG); this.hitNodes = new Container(); this.container.addChild(this.hitNodes); this.buildStatic(); } buildStatic() { const { galaxy } = this.world; const v = makeViewTransform(this.renderer.app); this.v = v; const g = this.staticG; g.clear(); for (const lane of galaxy.lanes) { const a = galaxy.systems[lane.a], b = galaxy.systems[lane.b]; g.moveTo(v.x(a.x), v.y(a.y)).lineTo(v.x(b.x), v.y(b.y)) .stroke({ width: 2, color: hazardColor(lane.hazard), alpha: 0.55 }); } for (const s of galaxy.systems) { g.circle(v.x(s.x), v.y(s.y), 7).fill({ color: 0xcfeeff }); } this.labelContainer.removeChildren(); this.hitNodes.removeChildren(); for (const s of galaxy.systems) { const label = new Text({ text: s.name, style: { fill: '#7fa8c9', fontSize: 11, fontFamily: 'system-ui' } }); label.position.set(v.x(s.x) + 10, v.y(s.y) - 6); this.labelContainer.addChild(label); const hit = new Graphics().circle(0, 0, 16).fill({ color: 0xffffff, alpha: 0.001 }); hit.position.set(v.x(s.x), v.y(s.y)); hit.eventMode = 'static'; hit.cursor = 'pointer'; hit.on('pointertap', () => this.handleSystemTap(s.id)); this.hitNodes.addChild(hit); } } handleSystemTap(systemId) { this.onSelectSystem(systemId); } lanePoint(lane, fromSys, f) { const { galaxy } = this.world; const a = galaxy.systems[fromSys]; const b = galaxy.systems[otherEnd(lane, fromSys)]; return { x: this.v.x(a.x + (b.x - a.x) * f), y: this.v.y(a.y + (b.y - a.y) * f) }; } update() { const sim = this.world; const v = this.v; const cur = playerSystem(sim); // Mid-lane the player observes nothing; don't exclude any visible band. const obsSys = sim.player.loc.kind === 'lane' ? null : cur; // Probability clouds: glowing strokes along lane segments, alpha ~ weight. const cg = this.cloudG; cg.clear(); for (const cloud of playerClouds(sim, obsSys)) { for (const seg of cloud.segs) { const lane = sim.galaxy.lanes[seg.laneId]; const p0 = this.lanePoint(lane, seg.fromSys, seg.f0); const p1 = this.lanePoint(lane, seg.fromSys, seg.f1); const alpha = Math.min(0.85, 0.18 + seg.w * 0.9); cg.moveTo(p0.x, p0.y).lineTo(p1.x, p1.y) .stroke({ width: 7, color: 0xffa94d, alpha }); } } // Player marker: bright cyan triangle at current position. const dg = this.dynamicG; dg.clear(); const pos = this.playerXY(); if (pos) { dg.poly([pos.x, pos.y - 8, pos.x + 6, pos.y + 6, pos.x - 6, pos.y + 6]).fill({ color: 0x4dffd2 }); } // Halo on the player's current system. if (cur != null) { const s = sim.galaxy.systems[cur]; dg.circle(v.x(s.x), v.y(s.y), 12).stroke({ width: 2, color: 0x4dffd2, alpha: 0.8 }); } } playerXY() { const sim = this.world; const loc = sim.player.loc; const v = this.v; if (loc.kind === 'lane') { const lane = sim.galaxy.lanes[loc.laneId]; const f = Math.min(1, (sim.t - loc.departT) / (loc.arriveT - loc.departT)); return this.lanePoint(lane, loc.fromSys, f); } const sysId = playerSystem(sim); if (sysId == null) return null; const s = sim.galaxy.systems[sysId]; return { x: v.x(s.x), y: v.y(s.y) }; } setVisible(on) { this.container.visible = on; this.labelContainer.visible = on; } }