This commit is contained in:
root
2026-09-20 14:16:41 +02:00
parent 0798933b05
commit b6d1909d7f
56 changed files with 3752 additions and 1989 deletions
+59 -19
View File
@@ -1,26 +1,70 @@
// Fleet manifest with launch controls.
import { api } from './api.js';
import { h, toast, confirmDialog } from './dom.js';
import { h, toast, customDialog, pad } from './dom.js';
import { statusOf } from './status.js';
export function fleetView(ctx) {
const { ships, selected } = ctx.state;
async function launch(ship) {
const ok = await confirmDialog({
title: `LAUNCH SHIP #${ship.id}?`,
body: [
'THE SHIP ENTERS THE BELT ON THE NEXT DAILY RUN.',
'ITS PROGRAM IS SEALED AT LAUNCH. IT CANNOT BE CHANGED, RECALLED OR RESTARTED.',
let slots;
try { slots = await api.slots(); } catch (e) { toast(`COULD NOT LOAD YOUR CODE SLOTS: ${e.message}`, 'err'); return; }
const slotLabel = (s) => `${pad(s.slot, 2)} ${s.builtin ? `${s.name} (EXAMPLE)` : s.empty ? '(EMPTY)' : s.name}`;
let remembered = 0;
try { remembered = Number(localStorage.getItem('halcyon.launchSlot')); } catch { /* ignore */ }
const usable = slots.filter((s) => !s.empty);
const first = usable.find((s) => s.slot === remembered) ?? usable.find((s) => !s.builtin) ?? usable[0];
const sel = h('select', { id: 'launch-slot' },
slots.map((s) => h('option', { value: s.slot, disabled: s.empty, selected: s.slot === first.slot }, slotLabel(s))));
const status = h('p', { class: 'launch-status', role: 'status', 'aria-live': 'polite' }, 'CHECKING...');
const dlg = customDialog({
title: `LAUNCH SHIP #${ship.id}`,
content: [
h('p', null, 'CHOOSE THE CODE SLOT THIS SHIP WILL FLY. IT IS ASSEMBLED NOW AND SEALED INTO THE SHIP.'),
h('label', { for: 'launch-slot' }, 'CODE SLOT'),
sel,
status,
h('p', { class: 'warn' }, 'AT LAUNCH THE PROGRAM IS FIXED. IT CANNOT BE CHANGED, RECALLED OR RESTARTED.'),
],
confirmText: 'LAUNCH',
danger: true,
});
if (!ok) return;
// Assemble the chosen slot as soon as it is chosen, so problems show before launch.
let seq = 0;
const check = async () => {
const mine = ++seq;
const n = Number(sel.value);
dlg.confirm.disabled = true;
status.className = 'launch-status';
status.textContent = 'ASSEMBLING...';
let text;
let good = false;
try {
const src = await api.slot(n);
const r = await api.assemble(src.source);
if (r.size === 0) text = 'THIS SLOT HAS NO CODE.';
else if (!r.fits) text = `TOO LARGE: ${r.size} BYTES, THE LIMIT IS ${r.limit}.`;
else { good = true; text = `${r.size} BYTES / ${r.instructions} INSTRUCTIONS: READY TO LAUNCH.`; }
} catch (e) {
text = `DOES NOT ASSEMBLE: ${e.message}`;
}
if (mine !== seq) return; // a newer choice superseded this one
status.className = `launch-status ${good ? 'ok' : 'bad'}`;
status.textContent = text;
dlg.confirm.disabled = !good;
};
sel.addEventListener('change', check);
check();
if (!(await dlg.result)) return;
const n = Number(sel.value);
try {
await api.launch(ship.id);
toast(`SHIP #${ship.id} CLEARED FOR LAUNCH. IT FLIES ON THE NEXT DAILY RUN.`);
const r = await api.launch(ship.id, n);
try { localStorage.setItem('halcyon.launchSlot', String(n)); } catch { /* ignore */ }
toast(`SHIP #${ship.id} CLEARED FOR LAUNCH WITH SLOT ${n} (${r?.program_bytes ?? '?'} BYTES). IT FLIES ON THE NEXT DAILY RUN.`);
await ctx.refresh();
} catch (e) {
toast(`LAUNCH FAILED: ${e.message}`, 'err');
@@ -34,11 +78,7 @@ export function fleetView(ctx) {
const out = [];
if (s.status === 'inventory') {
out.push(btn('CODE', () => ctx.selectShip(s.id, 'code')));
out.push(btn('LAUNCH', () => launch(s), {
cls: 'primary',
disabled: s.program_bytes === 0,
title: s.program_bytes === 0 ? 'Upload a program first' : 'Launch on the next daily run',
}));
out.push(btn('LAUNCH', () => launch(s), { cls: 'primary', title: 'Choose a code slot and launch on the next daily run' }));
}
if (s.status === 'launching' || s.status === 'active') out.push(btn('UPLINK', () => ctx.selectShip(s.id, 'uplink')));
if (s.downlink_day >= 0) out.push(btn('DOWNLINK', () => ctx.selectShip(s.id, 'downlink')));
@@ -72,13 +112,13 @@ export function fleetView(ctx) {
h('aside', { class: 'panel brief' },
h('div', { class: 'panel-title' }, 'MISSION BRIEF'),
h('ol', { class: 'steps' },
step('1', 'WRITE', 'a flight program in the CODE editor. Your ship cannot be flown by hand.'),
step('2', 'SEAL', 'it into a docked ship. You can replace it until launch.'),
step('3', 'LAUNCH', 'the ship. It enters the belt on the next daily run and the program is fixed for good.'),
step('1', 'WRITE', 'a flight program in the CODE editor and save it in one of your 16 code slots. Your ship cannot be flown by hand.'),
step('2', 'TEST', 'it: assemble it in the editor, and improve it in as many slots as you like.'),
step('3', 'LAUNCH', 'the ship and choose which slot it flies. It enters the belt on the next daily run and the program is fixed for good.'),
step('4', 'TALK', 'to it once a day: 1 KB up, 1 KB down, over the wormhole link.'),
),
h('p', { class: 'hint pad' }, 'The belt is simulated once a day. Ore delivered to the station earns credits.'),
h('p', { class: 'pad' }, h('a', { class: 'btn small', href: '/manual.txt', target: '_blank', rel: 'noopener' }, 'OPEN THE HC-33 MANUAL')),
h('p', { class: 'hint pad' }, 'The belt runs once a day. Ore delivered to the station earns credits.'),
h('p', { class: 'pad' }, h('a', { class: 'btn small', href: '/manual.pdf', target: '_blank', rel: 'noopener' }, 'OPEN THE HC-33 MANUAL (PDF)')),
),
);
}