feat: neon galaxy view with bloom, hazard tints, clouds

This commit is contained in:
2026-06-12 14:47:51 +00:00
parent ca32407c43
commit 61244453e3
3 changed files with 178 additions and 1 deletions
+128
View File
@@ -0,0 +1,128 @@
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; onJump(laneId) -> goTo lane
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) {
const sim = this.world;
const cur = playerSystem(sim);
// Clicking a neighbor = jump intent; clicking anything = intel selection.
const lane = sim.galaxy.lanes.find(
(l) => (l.a === cur && l.b === systemId) || (l.b === cur && l.a === systemId)
);
if (lane) this.onJump(lane.id);
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);
// Probability clouds: glowing strokes along lane segments, alpha ~ weight.
const cg = this.cloudG;
cg.clear();
for (const cloud of playerClouds(sim, cur)) {
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;
}
}