Fix Job Board detail text word-wrap for Phaser 4
- Pass `wordWrap` as `{ width, useAdvancedWrap }` instead of a bare number; Phaser 4 ignores the Phaser 3 shape so description copy was running off the panel.
- Add dev-only CDP probe (`dev/jobboard-wrap-*`) that boots to the Job Board, selects a posting, and dumps text-object geometry + overflow vs the panel's right edge.
- Commit captured probe artifacts (`dev/shots/jb-wrap.{png,json}`) as a regression reference.
- Add neutral ally assets: music, attack/scan/scan-complete speech lines, and pilot video.
This commit is contained in:
parent
41f6dbbfd9
commit
1e9c528108
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Runner for the JOB BOARD wrap probe.
|
||||
*
|
||||
* node dev/server.mjs 8080
|
||||
* CHROME_PATH=/path/to/chrome node dev/jobboard-wrap-run.mjs
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { launch, CDP } from './cdpshot.mjs';
|
||||
|
||||
const BASE = process.argv[2] || 'http://127.0.0.1:8080/dev/jobboard-wrap.html';
|
||||
const OUT = path.resolve('dev/shots');
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
fs.mkdirSync(OUT, { recursive: true });
|
||||
|
||||
const { url: wsUrl, kill } = await launch(BASE, 9401, 1280, 800);
|
||||
const cdp = new CDP(wsUrl);
|
||||
let bad = 0;
|
||||
try {
|
||||
await cdp.open();
|
||||
await cdp.send('Runtime.enable');
|
||||
await cdp.send('Page.enable');
|
||||
let rep = null;
|
||||
const t0 = Date.now();
|
||||
while (Date.now() - t0 < 120000) {
|
||||
try {
|
||||
const r = await cdp.send('Runtime.evaluate', { expression: 'window.__JB ? JSON.stringify(window.__JB) : null', returnByValue: true });
|
||||
if (r.result?.value) { rep = JSON.parse(r.result.value); break; }
|
||||
} catch { /* still booting */ }
|
||||
await sleep(500);
|
||||
}
|
||||
if (!rep) {
|
||||
bad++;
|
||||
console.log('✘ TIMEOUT — the page never reported');
|
||||
} else {
|
||||
const shot = await cdp.send('Page.captureScreenshot', { format: 'png' });
|
||||
fs.writeFileSync(path.join(OUT, 'jb-wrap.png'), Buffer.from(shot.data, 'base64'));
|
||||
fs.writeFileSync(path.join(OUT, 'jb-wrap.json'), JSON.stringify(rep, null, 1));
|
||||
if (rep.stage === 'FAILED' || (rep.errors && rep.errors.length)) bad++;
|
||||
console.log(bad ? '✘' : '✔', JSON.stringify(rep, null, 1).slice(0, 4000));
|
||||
}
|
||||
} finally {
|
||||
cdp.close();
|
||||
kill();
|
||||
await sleep(400);
|
||||
}
|
||||
process.exitCode = bad ? 1 : 0;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Orbit — dev JOB BOARD wrap probe</title>
|
||||
<base href="../" />
|
||||
<style>
|
||||
html, body { margin: 0; height: 100%; background: #04060d; overflow: hidden; }
|
||||
#game { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
|
||||
</style>
|
||||
<script src="lib/phaser.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="game"></div>
|
||||
<script type="module" src="dev/jobboard-wrap.mjs"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Dev-only probe: open the JOB BOARD, select a posting, and report the
|
||||
* detail panel's text objects (which wrap, which don't, where they end
|
||||
* relative to the panel's right edge).
|
||||
*
|
||||
* node dev/server.mjs 8080
|
||||
* CHROME_PATH=... node dev/jobboard-wrap-run.mjs
|
||||
*/
|
||||
import Phaser from '../js/vendor/phaser.js';
|
||||
import { config } from '../js/config/Config.js';
|
||||
import { ConfigLoader } from '../js/config/ConfigLoader.js';
|
||||
import { createGameConfig } from '../js/config/GameConfig.js';
|
||||
import { MenuScene } from '../js/scenes/MenuScene.js';
|
||||
import { GameScene } from '../js/scenes/GameScene.js';
|
||||
import { SurfaceScene } from '../js/scenes/SurfaceScene.js';
|
||||
|
||||
const data = await ConfigLoader.load();
|
||||
config.init(data);
|
||||
|
||||
const errors = [];
|
||||
const origErr = console.error.bind(console);
|
||||
console.error = (...a) => { errors.push(a.map(String).join(' ')); origErr(...a); };
|
||||
window.addEventListener('error', (e) => errors.push(String(e.message)));
|
||||
|
||||
const gameConfig = createGameConfig();
|
||||
gameConfig.scene = [MenuScene, GameScene, SurfaceScene];
|
||||
const game = new Phaser.Game(gameConfig);
|
||||
window.game = game;
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
async function waitFor(fn, tries = 260, everyMs = 100) {
|
||||
for (let i = 0; i < tries; i++) {
|
||||
const v = fn();
|
||||
if (v) return v;
|
||||
await sleep(everyMs);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const menu = await waitFor(() => { const m = game.scene.getScene('MenuScene'); return m?.newGameBtn ? m : null; });
|
||||
if (!menu) throw new Error('menu never booted');
|
||||
menu.seedValue = 'VESTRA';
|
||||
menu.startNewGame();
|
||||
const gs = await waitFor(() => { const g = game.scene.getScene('GameScene'); return g?.ship ? g : null; }, 300);
|
||||
if (!gs) throw new Error('GameScene never booted');
|
||||
await sleep(700);
|
||||
|
||||
const target = gs.commsTargetFor(gs.planet);
|
||||
gs.isHomeSystem = false; // PROBE STAGING — the EXPLORE drawer's Job Board slot
|
||||
gs.startLanding(target);
|
||||
const ss = await waitFor(() => { const c = game.scene.getScene('SurfaceScene'); return c?.jobBoardWindow ? c : null; }, 300);
|
||||
if (!ss) throw new Error('surface never booted');
|
||||
await sleep(600);
|
||||
ss.startSurface();
|
||||
await sleep(700);
|
||||
|
||||
ss.toggleExplore();
|
||||
await sleep(1100);
|
||||
ss.exploreAction('jobBoard');
|
||||
await sleep(2000);
|
||||
const win = ss.jobBoardWindow;
|
||||
if (!win) throw new Error('no job board window');
|
||||
if (win?.video?.video) { win.video.video.muted = true; win.video.video.play().catch(() => {}); }
|
||||
|
||||
const posts = gs.sideQuestPostings(gs.planet?.discoveryName ?? null, false);
|
||||
const pick = posts[0];
|
||||
if (!pick) throw new Error('no postings on the board');
|
||||
win.selectedId = pick.id;
|
||||
win.refresh();
|
||||
await sleep(900);
|
||||
|
||||
const panelRight = win.geo.rightX + win.geo.rightW;
|
||||
const snap = (list) =>
|
||||
list
|
||||
.filter((o) => o && o.isText === true || (o && typeof o.setText === 'function'))
|
||||
.map((t) => ({
|
||||
text: String(t.text ?? '').slice(0, 80),
|
||||
x: Math.round(t.x),
|
||||
y: Math.round(t.y),
|
||||
width: Math.round(t.width),
|
||||
height: Math.round(t.height),
|
||||
right: Math.round(t.x + t.width),
|
||||
overflow: Math.round(t.x + t.width) > Math.round(panelRight) ? `+${Math.round(t.x + t.width - panelRight)}` : null,
|
||||
wordWrapProp: t.wordWrap ?? null,
|
||||
styleWordWrap: t.style?.wordWrap ?? null,
|
||||
}));
|
||||
|
||||
const out = {
|
||||
window: gameConfig.width && { w: win.geo.W, h: win.geo.H },
|
||||
panel: { rightX: win.geo.rightX, rightW: win.geo.rightW, panelRight, detailPanelY: win.geo.detailPanelY, detailH: win.geo.detailH },
|
||||
selected: { id: pick.id, category: pick.category, title: pick.title, description: pick.description },
|
||||
detailContent: snap(win.detailSection.content),
|
||||
errors: errors.slice(-6),
|
||||
};
|
||||
window.__JB = out;
|
||||
document.title = 'JB-DONE';
|
||||
} catch (err) {
|
||||
window.__JB = { stage: 'FAILED', error: String(err?.message ?? err), errors: errors.slice(-6) };
|
||||
document.title = 'JB-DONE';
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
{
|
||||
"window": {
|
||||
"w": 1280,
|
||||
"h": 720
|
||||
},
|
||||
"panel": {
|
||||
"rightX": 425.3333333333333,
|
||||
"rightW": 830.6666666666667,
|
||||
"panelRight": 1256,
|
||||
"detailPanelY": 486,
|
||||
"detailH": 210
|
||||
},
|
||||
"selected": {
|
||||
"id": "sq-S000075-p1-bounty-0",
|
||||
"category": "bounty",
|
||||
"title": "Grey Judgement",
|
||||
"description": "Sera Alkantari holds court in open space: she reads the charge, gives you one channel to answer, and then fires a heavy lance and a seeker whether your defence was good or not. Three launch marshals were executed for calling her hull unsafe. The harbor she served is paying for the last word on this one to be somebody else's."
|
||||
},
|
||||
"detailContent": [
|
||||
{
|
||||
"text": "WANTED — SERA ALKANTARI",
|
||||
"x": 439,
|
||||
"y": 538,
|
||||
"width": 185,
|
||||
"height": 10,
|
||||
"right": 624,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
},
|
||||
{
|
||||
"text": "Grey Judgement",
|
||||
"x": 439,
|
||||
"y": 553,
|
||||
"width": 97,
|
||||
"height": 10,
|
||||
"right": 536,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
},
|
||||
{
|
||||
"text": "CHARGES — Executing three launch marshals on the pad",
|
||||
"x": 439,
|
||||
"y": 568,
|
||||
"width": 364,
|
||||
"height": 10,
|
||||
"right": 803,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
},
|
||||
{
|
||||
"text": "CRAFT — JUDICIAL WARDEN, LANCE AND MISSILE FIRED LIKE A SENTENCE",
|
||||
"x": 439,
|
||||
"y": 583,
|
||||
"width": 484,
|
||||
"height": 10,
|
||||
"right": 923,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
},
|
||||
{
|
||||
"text": "BRING YOUR RECORD UP TO LEVEL 2 AND THE BOARD WILL LIST YOU",
|
||||
"x": 439,
|
||||
"y": 598,
|
||||
"width": 427,
|
||||
"height": 10,
|
||||
"right": 866,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
},
|
||||
{
|
||||
"text": "GREY JUDGEMENT",
|
||||
"x": 439,
|
||||
"y": 498,
|
||||
"width": 149,
|
||||
"height": 15,
|
||||
"right": 588,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
},
|
||||
{
|
||||
"text": "ISSUER — THE HARBORMASTER",
|
||||
"x": 439,
|
||||
"y": 520,
|
||||
"width": 193,
|
||||
"height": 10,
|
||||
"right": 632,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
},
|
||||
{
|
||||
"text": "Sera Alkantari holds court in open space: she reads the charge, gives you one ch",
|
||||
"x": 439,
|
||||
"y": 613,
|
||||
"width": 778,
|
||||
"height": 27,
|
||||
"right": 1217,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
},
|
||||
{
|
||||
"text": "PAYOUT — 95 CR",
|
||||
"x": 439,
|
||||
"y": 650,
|
||||
"width": 112,
|
||||
"height": 12,
|
||||
"right": 551,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
},
|
||||
{
|
||||
"text": "LOCKED — REQUIRES LEVEL 2",
|
||||
"x": 841,
|
||||
"y": 676,
|
||||
"width": 241,
|
||||
"height": 12,
|
||||
"right": 1082,
|
||||
"overflow": null,
|
||||
"wordWrapProp": null,
|
||||
"styleWordWrap": null
|
||||
}
|
||||
],
|
||||
"errors": []
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 284 KiB |
|
|
@ -882,7 +882,7 @@ export class JobBoardWindow extends Phaser.GameObjects.Container {
|
|||
fontSize: '10.5px',
|
||||
color: toCss(C.dim),
|
||||
lineSpacing: 3,
|
||||
wordWrap: wrapW,
|
||||
wordWrap: { width: wrapW, useAdvancedWrap: true }, // Phaser 4: the wrap width is the object's `width` (a bare number is a Phaser 3 key — silently dropped, the copy runs off the panel)
|
||||
})
|
||||
.setScrollFactor(0);
|
||||
this.add(body);
|
||||
|
|
|
|||
Loading…
Reference in New Issue