diff --git a/assets/music/neutral-01.mp3 b/assets/music/neutral-01.mp3 new file mode 100644 index 0000000..551370e Binary files /dev/null and b/assets/music/neutral-01.mp3 differ diff --git a/assets/speech/neutral/attack-01.mp3 b/assets/speech/neutral/attack-01.mp3 new file mode 100644 index 0000000..1d07ff8 Binary files /dev/null and b/assets/speech/neutral/attack-01.mp3 differ diff --git a/assets/speech/neutral/attack-02.mp3 b/assets/speech/neutral/attack-02.mp3 new file mode 100644 index 0000000..3e42608 Binary files /dev/null and b/assets/speech/neutral/attack-02.mp3 differ diff --git a/assets/speech/neutral/attack-03.mp3 b/assets/speech/neutral/attack-03.mp3 new file mode 100644 index 0000000..3267166 Binary files /dev/null and b/assets/speech/neutral/attack-03.mp3 differ diff --git a/assets/speech/neutral/scan-01.mp3 b/assets/speech/neutral/scan-01.mp3 new file mode 100644 index 0000000..82f3379 Binary files /dev/null and b/assets/speech/neutral/scan-01.mp3 differ diff --git a/assets/speech/neutral/scan-02.mp3 b/assets/speech/neutral/scan-02.mp3 new file mode 100644 index 0000000..3280c04 Binary files /dev/null and b/assets/speech/neutral/scan-02.mp3 differ diff --git a/assets/speech/neutral/scan-03.mp3 b/assets/speech/neutral/scan-03.mp3 new file mode 100644 index 0000000..9c8e114 Binary files /dev/null and b/assets/speech/neutral/scan-03.mp3 differ diff --git a/assets/speech/neutral/scan-complete-01.mp3 b/assets/speech/neutral/scan-complete-01.mp3 new file mode 100644 index 0000000..2045ef7 Binary files /dev/null and b/assets/speech/neutral/scan-complete-01.mp3 differ diff --git a/assets/speech/neutral/scan-complete-02.mp3 b/assets/speech/neutral/scan-complete-02.mp3 new file mode 100644 index 0000000..54421be Binary files /dev/null and b/assets/speech/neutral/scan-complete-02.mp3 differ diff --git a/assets/speech/neutral/scan-complete-03.mp3 b/assets/speech/neutral/scan-complete-03.mp3 new file mode 100644 index 0000000..1945da3 Binary files /dev/null and b/assets/speech/neutral/scan-complete-03.mp3 differ diff --git a/assets/videos/allies/neutral-pilot.mp4 b/assets/videos/allies/neutral-pilot.mp4 new file mode 100644 index 0000000..c7a1d3b Binary files /dev/null and b/assets/videos/allies/neutral-pilot.mp4 differ diff --git a/dev/jobboard-wrap-run.mjs b/dev/jobboard-wrap-run.mjs new file mode 100644 index 0000000..8b51f55 --- /dev/null +++ b/dev/jobboard-wrap-run.mjs @@ -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; diff --git a/dev/jobboard-wrap.html b/dev/jobboard-wrap.html new file mode 100644 index 0000000..deab47f --- /dev/null +++ b/dev/jobboard-wrap.html @@ -0,0 +1,17 @@ + + + + + Orbit — dev JOB BOARD wrap probe + + + + + +
+ + + diff --git a/dev/jobboard-wrap.mjs b/dev/jobboard-wrap.mjs new file mode 100644 index 0000000..c2f6e3a --- /dev/null +++ b/dev/jobboard-wrap.mjs @@ -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'; +} diff --git a/dev/shots/jb-wrap.json b/dev/shots/jb-wrap.json new file mode 100644 index 0000000..dfcd288 --- /dev/null +++ b/dev/shots/jb-wrap.json @@ -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": [] +} \ No newline at end of file diff --git a/dev/shots/jb-wrap.png b/dev/shots/jb-wrap.png new file mode 100644 index 0000000..6537401 Binary files /dev/null and b/dev/shots/jb-wrap.png differ diff --git a/js/ui/JobBoardWindow.js b/js/ui/JobBoardWindow.js index 37032e4..53f14fd 100644 --- a/js/ui/JobBoardWindow.js +++ b/js/ui/JobBoardWindow.js @@ -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);