Compact the system dossier to identity + faction status
- SystemReport now returns a tight {title, subtitle, faction, population, status} shape instead of per-settlement/gate line lists; the HUD shows only the name, "<Type> system · star <class>", and "Faction: Neutral · Pop ~X" (or "Unclaimed").
- Drop the settlements/gates/summary/seed lines from the GameScene HUD render and update the decode/collapse docs to the new two-line layout.
- Fix planet kindLabel lookup to key on rec.class instead of rec.name so the comms/surface panels don't repeat the proper name.
- Update asteroid, galaxy, and system-hud tests plus PROJECT_NOTES to match the compact report shape.
This commit is contained in:
parent
92520a0503
commit
f8d62dfe09
|
|
@ -66,7 +66,7 @@
|
|||
],
|
||||
|
||||
"stationVideos": [
|
||||
{ "land": "ss-land-01.mp4", "surface": null, "takeoff": "ss-takeoff-01.mp4", "shop": null },
|
||||
{ "land": "ss-land-01.mp4", "surface": "ss-surface-01.mp4", "takeoff": "ss-takeoff-01.mp4", "shop": null },
|
||||
{ "land": "ss-land-02.mp4", "surface": null, "takeoff": "ss-takeoff-02.mp4", "shop": null },
|
||||
{ "land": "ss-land-03.mp4", "surface": null, "takeoff": "ss-takeoff-03.mp4", "shop": null }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -298,7 +298,8 @@ check(
|
|||
check('home system clusters use multiple frames', new Set(all.map((r) => r.frame)).size >= 3);
|
||||
check('home system clusters mix spin directions', all.some((r) => r.spin > 0) && all.some((r) => r.spin < 0));
|
||||
const report = formatSystemReport(c);
|
||||
check(`report subtitle names the clusters (${c.asteroids.length})`, report.subtitle.includes(`${c.asteroids.length} asteroid cluster`));
|
||||
check('report subtitle is the compact identity line (no cluster count)',
|
||||
/^[A-Za-z ]+ system · star [A-Z]$/.test(report.subtitle));
|
||||
}
|
||||
|
||||
// --- Determinism -------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -373,16 +373,19 @@ let big;
|
|||
pathToFileURL(join(__dirname, '../js/galaxy/SystemReport.js')).href
|
||||
);
|
||||
const anySys = big2.currentSystem();
|
||||
const report = formatSystemReport(big2.ensureContent(anySys.id));
|
||||
check('report has title/subtitle/settlements/gates/summary', !!report.title && !!report.subtitle && Array.isArray(report.settlements) && Array.isArray(report.gates) && typeof report.summary === 'string');
|
||||
check('report lines name their anchor world or open space', report.settlements.every((s) => /on .+|in open space/.test(s.text)));
|
||||
check('report gate lines name their destination star', report.gates.every((gt) => /jump to .+/.test(gt.text)));
|
||||
check('report population sums match', report.population === report.settlements.reduce((s, x) => s + x.population, 0));
|
||||
const anyContent = big2.ensureContent(anySys.id);
|
||||
const report = formatSystemReport(anyContent);
|
||||
check('report has title/subtitle/status', !!report.title && !!report.subtitle && typeof report.status === 'string');
|
||||
check('report subtitle is the compact identity line ("<Type> system · star <class>")',
|
||||
/^[A-Za-z ]+ system · star [A-Z]$/.test(report.subtitle));
|
||||
check('report status shows faction control + population (or unclaimed)',
|
||||
/Faction: Neutral · (Pop ~[0-9.]+[kM]?|Unclaimed)$/.test(report.status));
|
||||
check('report population sums match', report.population === (anyContent.settlements ?? []).reduce((s, x) => s + (x.population ?? 0), 0));
|
||||
check('formatPop() scales (1.2k / 9.0M)', formatPop(1234) === '1.2k' && formatPop(9000000) === '9.0M' && formatPop(12) === '12');
|
||||
|
||||
// The BARREN systems (objectCount → 0) are genuinely unclaimed — that
|
||||
// is the "unclaimed · N jump gates" report branch in the wild now (not
|
||||
// just a defensive fallback).
|
||||
// is the "Faction: Neutral · Unclaimed" report branch in the wild now
|
||||
// (not just a defensive fallback).
|
||||
|
||||
// No-duplicate guarantee: within a system, planet names, station names
|
||||
// and jump-gate names never repeat (drawn without replacement from the
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* - the state caret (▾) sits right of the name, starts hidden, and
|
||||
* appears once the name has landed (expanded state = pointing down);
|
||||
* - the details are OPEN by default; the dossier folds itself 10 s
|
||||
* after arrival — deconstructing in REVERSE build order (seed line
|
||||
* after arrival — deconstructing in REVERSE build order (status line
|
||||
* first … subtitle last) — then the caret swings down→right;
|
||||
* - clicking the name (toggleHud) re-opens (caret down, lines type in
|
||||
* forward) and re-closes (lines erase in reverse, caret right);
|
||||
|
|
@ -79,13 +79,7 @@ const galaxy = Galaxy.create('decode-hud-test');
|
|||
const rec = galaxy.currentSystem();
|
||||
const content = galaxy.ensureContent(rec.id);
|
||||
const report = formatSystemReport(content);
|
||||
const detailValues = [
|
||||
report.subtitle,
|
||||
...report.settlements.map((s) => s.text),
|
||||
...(report.gates ?? []).map((gt) => gt.text),
|
||||
report.summary,
|
||||
`seed ${galaxy.seed}`,
|
||||
];
|
||||
const detailValues = [report.subtitle, report.status];
|
||||
console.log(`dossier under test:\n ${report.title}\n${detailValues.map((s) => ' ' + s).join('\n')}\n`);
|
||||
|
||||
// --- Stub scene: what createSystemHud()/updateHud()/toggleHud() touch -----
|
||||
|
|
@ -211,17 +205,7 @@ check('arrival timeline: name first, then the detail lines',
|
|||
ys.push(y); // subtitle
|
||||
y += 20;
|
||||
y += 2;
|
||||
for (let i = 0; i < report.settlements.length; i++) {
|
||||
ys.push(y);
|
||||
y += 20;
|
||||
}
|
||||
for (let i = 0; i < (report.gates ?? []).length; i++) {
|
||||
ys.push(y);
|
||||
y += 20;
|
||||
}
|
||||
ys.push(y); // summary
|
||||
y += 20;
|
||||
ys.push(y); // seed
|
||||
ys.push(y); // status (faction + population)
|
||||
y += 20;
|
||||
check('y layout unchanged by the decode', [scene.hudTitle, ...scene.hudDetail.map((d) => d.text)].every((t, i) => t.y === ys[i]));
|
||||
check('hudEndY tracks the dossier bottom', scene.hudEndY === y + 6);
|
||||
|
|
@ -296,7 +280,7 @@ for (let t = T0 + 4000; t <= T0 + 13000; t += STEP) {
|
|||
}
|
||||
check('the auto-fold fired at 10 s after arrival',
|
||||
collapseSeen !== null && collapseSeen.t >= T0 + 10000 && collapseSeen.t <= T0 + 10000 + STEP);
|
||||
check('deconstruction runs in REVERSE build order (seed → … → subtitle)',
|
||||
check('deconstruction runs in REVERSE build order (status → subtitle)',
|
||||
collapseSeen !== null &&
|
||||
collapseSeen.order[0] === scene.hudDetail[scene.hudDetail.length - 1].text &&
|
||||
collapseSeen.order[1] === scene.hudDetail[scene.hudDetail.length - 2].text &&
|
||||
|
|
@ -320,7 +304,7 @@ check('toggle while folded starts a FORWARD rebuild',
|
|||
scene.hudPhase === 'constructing' && scene.hudTimeline.mode === 'expand');
|
||||
{
|
||||
const tl = scene.hudTimeline;
|
||||
check('rebuild order is build order (subtitle → … → seed)',
|
||||
check('rebuild order is build order (subtitle → status)',
|
||||
tl.lines.length === detailValues.length &&
|
||||
tl.lines[0].text === scene.hudDetail[0].text &&
|
||||
tl.lines[tl.lines.length - 1].text === scene.hudDetail[scene.hudDetail.length - 1].text &&
|
||||
|
|
|
|||
|
|
@ -177,9 +177,13 @@ branch is now the normal case, not a defensive fallback. Model & seams:
|
|||
where **factions and pirates** will plug in later (claim, flag,
|
||||
relations). Deliberately absent for now — no factions yet.
|
||||
- **Pure report formatter** — `js/galaxy/SystemReport.js`
|
||||
(`formatSystemReport(content)` → title/subtitle/settlements/gates/
|
||||
summary). The GameScene HUD renders it; future star map / terminal UI
|
||||
reuse it.
|
||||
(`formatSystemReport(content)` → title/subtitle/faction/population/
|
||||
status). Compact by design: the HUD shows the system name, identity
|
||||
("Main Sequence system · star G"), and standing ("Faction: Neutral ·
|
||||
Pop ~1.2M" — faction is Neutral for now; unclaimed systems read
|
||||
"Unclaimed"). Per-settlement / gate detail stays in the content +
|
||||
star chart. The GameScene HUD renders it; a future star map / terminal
|
||||
UI reuses it.
|
||||
- Landing/exploration (a future feature) will treat settlements as points
|
||||
of interest: the data already says what's there and where (anchor =
|
||||
planet ordinal or open space).
|
||||
|
|
|
|||
|
|
@ -8,62 +8,42 @@ import { config } from '../config/Config.js';
|
|||
* or a terminal UI later). Scenes stay thin: they render what this
|
||||
* returns.
|
||||
*
|
||||
* The dossier is deliberately compact — the per-settlement and gate
|
||||
* detail lives elsewhere (star chart, map, the content itself). What it
|
||||
* carries: identity (type + star class), who controls the system, and
|
||||
* how many are settled here.
|
||||
*
|
||||
* const report = formatSystemReport(content);
|
||||
* // { title, subtitle, settlements: [{ text, color, population }],
|
||||
* // gates: [{ text, color }], summary, population }
|
||||
* // { title, subtitle, faction, population, status, statusColor }
|
||||
* // title — the system name
|
||||
* // subtitle — "Main Sequence system · star G"
|
||||
* // status — "Faction: Neutral · Pop ~1.2M" (or "… · Unclaimed")
|
||||
*/
|
||||
export function formatSystemReport(content, typeDefs = null, kindDefs = null) {
|
||||
export function formatSystemReport(content, typeDefs = null) {
|
||||
const types = typeDefs ?? config.get('systems.types', {});
|
||||
const kinds = kindDefs ?? config.get('settlements.kinds', {});
|
||||
const typeDef = types[content.type] ?? {};
|
||||
|
||||
const planetName = (ordinal) => {
|
||||
const p = content.planets.find((pl) => pl.ordinal === ordinal);
|
||||
return p ? p.name : `planet ${ordinal}`;
|
||||
};
|
||||
const population = (content.settlements ?? []).reduce(
|
||||
(sum, s) => sum + (s.population ?? 0),
|
||||
0,
|
||||
);
|
||||
|
||||
const settlements = [];
|
||||
for (const s of content.settlements ?? []) {
|
||||
const def = kinds[s.kind] ?? {};
|
||||
const where =
|
||||
s.anchor?.type === 'planet' ? `on ${planetName(s.anchor.ordinal)}` : 'in open space';
|
||||
settlements.push({
|
||||
text: `${s.name} · ${def.label ?? s.kind} ${where}`,
|
||||
color: def.theme?.color ?? '#8fa0c9',
|
||||
population: s.population ?? 0,
|
||||
});
|
||||
}
|
||||
// Faction control — every system is Neutral for now. When factions
|
||||
// land (the `owner` seam on settlements), this is where the faction's
|
||||
// name + theme color will plug in (Neutral = light gray).
|
||||
const faction = 'Neutral';
|
||||
const statusColor = '#c4cad8';
|
||||
|
||||
const population = settlements.reduce((sum, s) => sum + s.population, 0);
|
||||
|
||||
// The jump gates — the system's exits (content.jumps; each line names
|
||||
// the star the gate jumps to, in the gate's cyan).
|
||||
const gates = (content.jumps ?? []).map((j) => ({
|
||||
text: `${j.name} · jump to ${j.toName}`,
|
||||
color: config.get('gates.theme.color', '#5fd4ff'),
|
||||
}));
|
||||
|
||||
const n = settlements.length;
|
||||
const g = gates.length;
|
||||
const summary =
|
||||
n === 0
|
||||
? g === 0
|
||||
? 'charted · unclaimed'
|
||||
: `unclaimed · ${g} jump gate${g === 1 ? '' : 's'}`
|
||||
: `${n} settlement${n === 1 ? '' : 's'} · pop ~${formatPop(population)}` +
|
||||
(g ? ` · ${g} jump gate${g === 1 ? '' : 's'}` : '');
|
||||
|
||||
const planetN = content.planets.length;
|
||||
const asteroidN = Array.isArray(content.asteroids) ? content.asteroids.length : 0;
|
||||
return {
|
||||
title: content.name,
|
||||
subtitle:
|
||||
`${typeDef.label ?? content.type} system · star ${content.star.class} · ${planetN} planet${planetN === 1 ? '' : 's'}` +
|
||||
(asteroidN ? ` · ${asteroidN} asteroid cluster${asteroidN === 1 ? '' : 's'}` : ''),
|
||||
settlements,
|
||||
gates,
|
||||
summary,
|
||||
subtitle: `${typeDef.label ?? content.type} system · star ${content.star.class}`,
|
||||
faction,
|
||||
population,
|
||||
status:
|
||||
population > 0
|
||||
? `Faction: ${faction} · Pop ~${formatPop(population)}`
|
||||
: `Faction: ${faction} · Unclaimed`,
|
||||
statusColor,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1181,9 +1181,11 @@ export class GameScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
/**
|
||||
* Top-left HUD: the current system's dossier — name, identity, and what
|
||||
*'s ALREADY THERE: colonies, mining stations, cloud bases, stations adrift
|
||||
* in open space (or "charted · unclaimed" when nobody's settled here).
|
||||
* Top-left HUD: the current system's dossier — the system name, its
|
||||
* identity ("<Type> system · star <class>"), and its standing: faction
|
||||
* control (Neutral for now) + total population ("Unclaimed" when none).
|
||||
* Deliberately compact — the per-settlement and gate detail lives
|
||||
* elsewhere (star chart, map, the content itself).
|
||||
* Formatted by the pure SystemReport helper; this method only renders.
|
||||
*
|
||||
* The dossier types itself in with the menu's decode scramble
|
||||
|
|
@ -1197,7 +1199,7 @@ export class GameScene extends Phaser.Scene {
|
|||
* beside it), details type in
|
||||
* │ after 10 s (HUD_AUTO_COLLAPSE_MS) — OR a click on the name
|
||||
* ▼
|
||||
* details DECONSTRUCT in reverse build order (seed line
|
||||
* details DECONSTRUCT in reverse build order (status line
|
||||
* first … subtitle last), then the caret swings down→right
|
||||
* │
|
||||
* ▼ a click on the name (or the caret)
|
||||
|
|
@ -1261,7 +1263,7 @@ export class GameScene extends Phaser.Scene {
|
|||
this.hudTitleRect = { x: X - 4, y: y - 4, w: titleW + 24, h: titleH + 8 };
|
||||
y += 26;
|
||||
|
||||
// --- The details (open by default): what's already there ------------
|
||||
// --- The details (open by default) ----------------------------------
|
||||
const detail = [];
|
||||
const line = (value, style) => {
|
||||
const t = this.add
|
||||
|
|
@ -1272,16 +1274,9 @@ export class GameScene extends Phaser.Scene {
|
|||
detail.push({ text: t, value });
|
||||
y += 20;
|
||||
};
|
||||
line(report.subtitle, { fontFamily: fam, fontSize: '12px', color: '#8fa0c9', letterSpacing: 1 });
|
||||
line(report.subtitle, { fontFamily: fam, fontSize: '12px', color: '#ffffff', letterSpacing: 1 });
|
||||
y += 2;
|
||||
for (const s of report.settlements) {
|
||||
line(s.text, { fontFamily: fam, fontSize: '13px', color: toCss(s.color, '#8fa0c9') });
|
||||
}
|
||||
for (const gt of report.gates ?? []) {
|
||||
line(gt.text, { fontFamily: fam, fontSize: '13px', color: toCss(gt.color, '#5fd4ff') });
|
||||
}
|
||||
line(report.summary, { fontFamily: fam, fontSize: '12px', color: '#54608a' });
|
||||
line(`seed ${this.galaxy.seed}`, { fontFamily: fam, fontSize: '11px', color: '#3d476b' });
|
||||
line(report.status, { fontFamily: fam, fontSize: '13px', color: toCss(report.statusColor, '#8fa0c9') });
|
||||
this.hudDetail = detail;
|
||||
this.hudEndY = y + 6; // bottom of the dossier block
|
||||
|
||||
|
|
@ -1312,7 +1307,7 @@ export class GameScene extends Phaser.Scene {
|
|||
* erases its lines one by one with the shared decode scramble;
|
||||
* - on arrival the name lands first and the state caret fades in
|
||||
* beside it;
|
||||
* - on a collapse the LAST line built erases first (seed → … →
|
||||
* - on a collapse the LAST line built erases first (status →
|
||||
* subtitle), then the caret swings down→right;
|
||||
* - the one-shot 10 s auto-fold fires once (cancelled by any manual
|
||||
* toggle).
|
||||
|
|
@ -1387,7 +1382,7 @@ export class GameScene extends Phaser.Scene {
|
|||
|
||||
/**
|
||||
* Re-open the details: the caret swings back down first, then the lines
|
||||
* type back in in build order (subtitle → … → seed) — the same decode as
|
||||
* type back in in build order (subtitle → status) — the same decode as
|
||||
* arrival, just without the name (it never left).
|
||||
*/
|
||||
startExpand(t) {
|
||||
|
|
@ -1406,7 +1401,7 @@ export class GameScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fold the details: the lines DECONSTRUCT in reverse build order (seed
|
||||
* Fold the details: the lines DECONSTRUCT in reverse build order (status
|
||||
* line erases first … subtitle last) — the same scramble played
|
||||
* backwards — and the caret swings down→right once the last one is gone
|
||||
* (updateHud, when the timeline completes).
|
||||
|
|
@ -2176,7 +2171,11 @@ export class GameScene extends Phaser.Scene {
|
|||
// (anchor.type 'planet', anchor.ordinal = the planet's ordinal).
|
||||
const rec = (this.systemContent.planets ?? []).find((p) => p.name === obj.discoveryName);
|
||||
if (rec) {
|
||||
kindLabel = config.get(`planets.typeLabels.${rec.name}`, rec.name);
|
||||
// typeLabels is keyed by the planet's CLASS (rec.class —
|
||||
// 'rocky'/'gas'/…), not its proper name (rec.name) — a name
|
||||
// lookup would miss and fall back to the name, so the comms
|
||||
// panel and the surface HUD would show the name twice.
|
||||
kindLabel = config.get(`planets.typeLabels.${rec.class}`, rec.class);
|
||||
const anchored = (this.systemContent.settlements ?? []).filter(
|
||||
(s) => s.anchor?.type === 'planet' && s.anchor?.ordinal === rec.ordinal,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue