SOURCE / PINNED RELEASE
Made of little things.
Supersonic RC Revive
- Release
- 1ba42f1ca1d6…
- Author-recorded commit
- baecad10b1cd…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f
Archive hash verified: 90d22b206672eba4…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import './napplet-settings.js';
import { STORAGE_DOMAIN, storage } from '@napplet/sdk';
import { runtimeHasDomain } from './domain-availability.js';
import {
canDownloadPack,
canLoadPackLink,
canSideLoadPack,
currentPack,
currentLevelOverlay,
currentPackBuffer,
fetchOriginalPack,
fetchPackLink,
fetchRegisteredLevel,
installedPack,
installLevelOverlay,
installPack,
isGlb,
loadBuildPack,
PACK_EMBEDDED,
type PackMeta,
pickPackFile,
type RegisteredLevel,
registeredLevels,
} from './game/assets.js';
import { App, type Progress } from './game/app.js';
import { intentToToken, keyToToken, KonamiCode } from './game/konami.js';
import { MODE_ORDER, type ModeName } from './game/lingo/gameplay.js';
import { focusTargets, padPress, stepFocus } from './game/ui/screens.js';
import { onPlayIntent } from './napplet-intent.js';
import { registerDrivingKeyActions } from './napplet-keys.js';
import './styles.css';
// App-owned colours: the game keeps its own palette in every host theme.
export const FOLLOW_HOST_THEME = false;
const PROGRESS_KEY = 'ssrc.unlocked.v1';
function requireElement<T extends HTMLElement>(selector: string): T {
const element = document.querySelector<T>(selector);
if (!element) throw new Error(`Missing required element: ${selector}`);
return element;
}
/** Unlocks persist through the host's storage domain; without it they last for the session (as in the original). */
const progress: Progress | null = runtimeHasDomain(STORAGE_DOMAIN)
? {
async load() {
try {
const raw = await storage.getItem(PROGRESS_KEY);
const list = raw ? (JSON.parse(raw) as unknown) : [];
return Array.isArray(list) ? MODE_ORDER.filter((m) => list.includes(m)) : [];
} catch {
return [];
}
},
save(unlocked: ModeName[]) {
void storage.setItem(PROGRESS_KEY, JSON.stringify(unlocked)).catch(() => {});
},
}
: null;
const stage = requireElement<HTMLDivElement>('#stage');
const status = requireElement<HTMLOutputElement>('#status');
const badge = requireElement<HTMLSpanElement>('#assetBadge');
const panel = requireElement<HTMLElement>('#packPanel');
const panelTitle = requireElement<HTMLHeadingElement>('#packTitle');
const panelMessage = requireElement<HTMLParagraphElement>('#packMessage');
const linkForm = requireElement<HTMLDivElement>('#packLinkForm');
const linkInput = requireElement<HTMLInputElement>('#packLink');
const linkButton = requireElement<HTMLButtonElement>('#packLinkLoad');
const retryButton = requireElement<HTMLButtonElement>('#packRetry');
const fileButton = requireElement<HTMLButtonElement>('#packFile');
const cancelButton = requireElement<HTMLButtonElement>('#packCancel');
const levelsBox = requireElement<HTMLDivElement>('#packLevels');
const levelList = requireElement<HTMLDivElement>('#packLevelList');
const errorText = (error: unknown) => (error instanceof Error ? error.message : String(error));
// The pack panel: a full-frame card that gets an asset pack from the player,
// at startup (when the build's own pack is unavailable) or from "Asset pack…"
// on the title screen. One set of controls; `panelSession` says what the open
// panel does with them.
interface PanelSession {
title: string;
message: string;
error: boolean;
/** Called with the chosen pack's bytes. Throw to show the error in the panel. */
take(buffer: ArrayBuffer): Promise<void> | void;
retry?: () => void;
cancel?: () => void;
/** Offer the registered extra levels and the pack's own track. */
levels?: (choice: RegisteredLevel | null) => void;
}
let panelSession: PanelSession | null = null;
const currentPackTitle = () => installedPack()?.title ?? 'Default';
/** The registered level being played: its id, null for the pack's own track, '' for any other level file. */
let playingLevel: string | null = null;
function renderLevels(session: PanelSession): void {
const levels = session.levels ? registeredLevels() : [];
levelsBox.hidden = levels.length === 0;
levelList.replaceChildren();
if (levelsBox.hidden) return;
const choices: Array<RegisteredLevel | null> = [null, ...levels];
for (const level of choices) {
const button = document.createElement('button');
button.type = 'button';
button.className = 'pack-button';
button.textContent = level ? level.title : `${currentPackTitle()} track`;
button.setAttribute('aria-pressed', String((level?.id ?? null) === playingLevel));
button.addEventListener('click', () => session.levels?.(level));
levelList.append(button);
}
}
function showPanel(session: PanelSession): void {
panelSession = session;
panel.hidden = false;
status.hidden = true;
panelTitle.textContent = session.title;
panelMessage.dataset.state = session.error ? 'error' : '';
panelMessage.textContent = session.message;
linkForm.hidden = !canLoadPackLink();
fileButton.hidden = !canSideLoadPack();
// Next to the link field the file picker is the secondary choice.
fileButton.textContent = linkForm.hidden ? 'Load asset pack…' : 'Choose file…';
fileButton.classList.toggle('primary', linkForm.hidden);
retryButton.hidden = !session.retry;
cancelButton.hidden = !session.cancel;
renderLevels(session);
setPanelBusy(false);
(!linkForm.hidden ? linkInput : !fileButton.hidden ? fileButton : retryButton).focus();
}
function hidePanel(): void {
panelSession = null;
if (panel.contains(document.activeElement)) (document.activeElement as HTMLElement).blur();
panel.hidden = true;
}
function setPanelBusy(busy: boolean): void {
for (const control of [linkInput, linkButton, retryButton, fileButton, cancelButton, ...levelList.querySelectorAll('button')]) control.disabled = busy;
}
/** Get a pack's bytes from `load` and hand them to the open session; errors stay in the panel. */
async function panelLoad(load: () => Promise<ArrayBuffer | null>, busyText: string): Promise<void> {
const session = panelSession;
if (!session) return;
setPanelBusy(true);
panelMessage.dataset.state = '';
panelMessage.textContent = busyText;
try {
const buffer = await load();
if (buffer) await session.take(buffer);
else if (panelSession === session) showPanel(session);
} catch (error) {
if (panelSession === session) showPanel({ ...session, message: errorText(error), error: true });
}
}
/** Download a linked pack into the open panel's session. */
function loadLinkInPanel(text: string): void {
linkInput.value = text;
void panelLoad(() => fetchPackLink(text), 'Downloading the asset pack…');
}
// Not a <form>: the sandbox has no allow-forms, so a submit would be blocked.
const loadLink = () => loadLinkInPanel(linkInput.value);
linkButton.addEventListener('click', loadLink);
linkInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') loadLink();
});
fileButton.addEventListener('click', () => void panelLoad(pickPackFile, 'Reading the asset pack…'));
retryButton.addEventListener('click', () => panelSession?.retry?.());
cancelButton.addEventListener('click', () => panelSession?.cancel?.());
panel.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && panelSession?.cancel && !cancelButton.disabled) {
e.stopPropagation();
panelSession.cancel();
}
});
/**
* Get an asset pack: the build's own (embedded, or downloaded from Blossom),
* else whatever the player links or side-loads. Resolves once one is installed.
*/
function acquirePack(): Promise<PackMeta> {
return new Promise((resolve) => {
const offer = (message: string) => {
const link = canLoadPackLink();
const sideLoad = canSideLoadPack();
const alternatives = [link && 'paste a Blossom link or hash of an asset pack', sideLoad && 'load an asset pack file (.ssrcpack)'].filter(Boolean);
showPanel({
title: 'Game data',
message:
!canDownloadPack() && !link && !sideLoad
? 'This host cannot download or open the game data. Open SuperSonic RC in a napplet host with resource or file access.'
: message + (alternatives.length ? ` You can ${alternatives.join(' or ')} instead.` : ''),
error: true,
take: (buffer) => {
const meta = installPack(buffer);
hidePanel();
resolve(meta);
},
retry: canDownloadPack() ? () => void tryBuild() : undefined,
});
// A play intent that arrived during startup brings its own pack.
if (pendingPlay !== null) {
const link = pendingPlay;
pendingPlay = null;
loadLinkInPanel(link);
}
};
const tryBuild = async () => {
panel.hidden = PACK_EMBEDDED;
panelTitle.textContent = 'Game data';
panelMessage.dataset.state = '';
panelMessage.textContent = 'Downloading the game data…';
linkForm.hidden = retryButton.hidden = fileButton.hidden = cancelButton.hidden = true;
try {
resolve(await loadBuildPack());
} catch (error) {
offer(errorText(error));
}
};
void tryBuild();
});
}
const app = new App(stage, progress);
// Controllers in the pack panel: move between its controls, A presses one
// (the link field only loads a link already pasted; a controller cannot type),
// B cancels like Escape. While the panel is up the screens behind it get nothing.
app.menuOverride = (intent) => {
if (panel.hidden) return false;
const session = panelSession;
if (!session) return true;
const targets = focusTargets(panel, 'button, input');
const focused = targets.find((t) => t === document.activeElement);
if (intent === 'back') session.cancel?.();
else if (intent !== 'select' && intent !== 'start') stepFocus(targets, intent);
else if (focused === linkInput) {
if (linkInput.value.trim()) loadLink();
else stepFocus(targets, 'down');
} else if (focused) padPress(focused);
else stepFocus(targets, 'down');
return true;
};
const closeKeyActions = registerDrivingKeyActions((key) => app.triggerKeyAction(key));
window.addEventListener('pagehide', closeKeyActions, { once: true });
app.onStatus = (text) => {
status.textContent = text;
status.hidden = text === '';
status.dataset.state = '';
};
/** A short status line that clears itself (pack switch results). */
let noticeTimer: ReturnType<typeof setTimeout> | undefined;
function notice(text: string, error = false): void {
clearTimeout(noticeTimer);
app.onStatus(text);
status.dataset.state = error ? 'error' : '';
noticeTimer = setTimeout(() => app.onStatus(''), error ? 8000 : 4000);
}
/**
* Replace the running game's pack with `buffer` (an asset pack, or a level
* .glb played with the current pack) and return to the title screen. Throws
* synchronously, changing nothing, if it is neither; one that fails to build
* is dropped and the previous pack or level reinstalled.
*/
function switchPack(buffer: ArrayBuffer, levelId = ''): Promise<void> {
const previousPack = currentPackBuffer();
const previousLevel = currentLevelOverlay();
const previousId = playingLevel;
const level = isGlb(buffer);
const meta = level ? installLevelOverlay(buffer)! : installPack(buffer);
playingLevel = level ? levelId : null;
const restore = () => {
if (!level && previousPack) installPack(previousPack);
installLevelOverlay(previousLevel);
playingLevel = previousId;
};
return rebuildWithPack(meta, level ? 'level' : 'asset pack', restore);
}
/** Back to the installed pack's own track (drop the level overlay). */
function switchToPackLevel(): Promise<void> {
const previousLevel = currentLevelOverlay();
const previousId = playingLevel;
installLevelOverlay(null);
playingLevel = null;
return rebuildWithPack({ title: `${currentPackTitle()} track`, original: installedPack()?.original === true }, 'level', () => {
installLevelOverlay(previousLevel);
playingLevel = previousId;
});
}
async function rebuildWithPack(meta: PackMeta, what: string, restore: () => void): Promise<void> {
try {
await app.reloadAssets();
} catch (error) {
restore();
app.showTitle();
notice(`Could not switch to that ${what}: ${errorText(error)}`, true);
return;
}
badge.hidden = !meta.original;
notice(`Loaded ${what}: ${meta.title}`);
}
/** The registered level whose download the open panel is taking (so the switch records it). */
let pickedLevel: RegisteredLevel | null = null;
// Switching packs: "Asset pack…" on the title screen opens the pack panel to
// load another .ssrcpack (a pasted Blossom link or hash through the host's
// resource domain, or a file through its file picker) in place of the current
// one. A file that is not a pack stays in the panel as an error; a pack that
// fails to build is dropped and the previous one reinstalled. The panel also
// lists the registered extra levels and the pack's own track.
if (canLoadPackLink() || canSideLoadPack()) {
app.packSwitch = () =>
new Promise<void>((done) => {
const current = currentPack();
showPanel({
title: 'Asset pack',
message: `${current ? `Playing with “${current.title}”. ` : ''}${registeredLevels().length ? 'Pick a level, or load' : 'Load'} another asset pack or level: ${
canLoadPackLink() ? `paste its Blossom link or hash${canSideLoadPack() ? ', or choose a .ssrcpack or .glb file' : ''}` : 'choose a .ssrcpack or .glb file'
}.`,
error: false,
take: async (buffer) => {
// Not a pack: installPack throws here, and the panel shows the error.
const switching = switchPack(buffer, pickedLevel?.id ?? '');
pickedLevel = null;
hidePanel();
done();
linkInput.value = '';
await switching;
},
levels: (level) => {
if (!level) {
if (playingLevel === null) return;
hidePanel();
done();
void switchToPackLevel();
return;
}
if (level.id === playingLevel) return;
pickedLevel = level;
void panelLoad(() => fetchRegisteredLevel(level), `Downloading “${level.title}”…`).finally(() => {
pickedLevel = null;
});
},
cancel: () => {
hidePanel();
done();
},
});
});
}
// NAP-INTENT play (see napplet-intent.ts): another napplet asks to play with
// the asset pack it links. An open pack panel takes the pack as if pasted;
// during startup the link waits for the game (or the startup panel, if the
// build's own pack fails); during a race it waits for the race to end.
let started = false;
let pendingPlay: string | null = null;
let playQueue = Promise.resolve();
function receivePlay(link: string): void {
if (panelSession) return loadLinkInPanel(link);
if (!started) {
pendingPlay = link;
return;
}
playQueue = playQueue.then(() => playLinkedPack(link));
}
async function playLinkedPack(link: string): Promise<void> {
// The pack panel may have opened while an earlier request was handled.
if (panelSession) return loadLinkInPanel(link);
app.onStatus('Downloading the linked asset pack…');
try {
const buffer = await fetchPackLink(link);
if (app.racing) {
notice('Finish the race to play with the linked asset pack.');
await app.whenIdle();
}
await switchPack(buffer);
} catch (error) {
notice(`Could not play that link: ${errorText(error)}`, true);
}
}
const closePlayIntents = onPlayIntent(receivePlay, () => notice('A request to play had no usable asset pack or level link.', true));
window.addEventListener('pagehide', closePlayIntents, { once: true });
// The secret (↑ ↑ ↓ ↓ ← → ← → B A), enterable on any menu screen with the
// keyboard or a controller: swap in the original 2004 assets, downloaded by
// hash from the Blossom servers that carry them (fetchOriginalPack). Keyboard
// entry reads raw key codes — KeyA is "left" to the game — and the controller
// side consumes only the completing A, so it does not also press the focused
// menu button.
const konami = new KonamiCode();
let loadingOriginal = false;
/** Feed one code token; true when it completed the code. */
function feedKonami(token: ReturnType<typeof keyToToken>): boolean {
return token !== undefined && konami.feed(token);
}
/** What completing the code does: load the original-assets pack, like a linked pack. */
async function loadOriginalPack(): Promise<void> {
if (loadingOriginal) return;
if (installedPack()?.original) {
notice('Already playing the original assets.');
return;
}
if (!canLoadPackLink()) {
notice('This host cannot download the original assets.', true);
return;
}
loadingOriginal = true;
notice('Loading the original assets…');
try {
const buffer = await fetchOriginalPack();
if (app.racing) {
notice('Finish the race to play with the original assets.');
await app.whenIdle();
}
await switchPack(buffer);
} catch (error) {
notice(`Could not load the original assets: ${errorText(error)}`, true);
} finally {
loadingOriginal = false;
}
}
// Menus only: not during startup, a race or an open pack panel.
app.menuIntentFilter = (intent) => {
if (!started || app.racing) return false;
if (!feedKonami(intentToToken(intent))) return false;
void loadOriginalPack();
return true;
};
window.addEventListener('keydown', (e) => {
if (e.repeat || !started || app.racing || !panel.hidden) return;
// Typing in a text field (the pack link) is not code entry.
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
if (feedKonami(keyToToken(e.code))) void loadOriginalPack();
});
acquirePack()
.then((pack) => {
panel.hidden = true;
badge.hidden = !pack.original;
return app.start();
})
.then(() => {
started = true;
if (pendingPlay !== null) {
const link = pendingPlay;
pendingPlay = null;
receivePlay(link);
}
})
.catch((error: unknown) => {
status.hidden = false;
status.textContent = errorText(error);
status.dataset.state = 'error';
});
