SOURCE / PINNED RELEASE
Made of little things.
Shiftfield
- Release
- 5a170c505f6b…
- Author-recorded commit
- a2e23e0171aa…
- License
- LICENSE
- Author’s source reference
- nostr://npub16nzc4ql748r2kv9202rgzrt0fmtk4n3tzsjl86yf6ty9agqe9u0qjcc7hc/wss%3A%2F%2Fgit.napplet.soy%2F/n-e4fb6341ccd
Archive hash verified: 66de7623efd91da1…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
/* NAP-CVM leaderboard client (depth leaderboard plan, ruling 2026-10-28).
*
* The napplet sandbox has no network of its own; `cvm` is the host-injected
* bridge to napplet.soy's shared board service. The board is registered in
* napplet.json (backend.boards, depth-v1) and mirrored to
* .napplet-space/soy-backend.json by `soyli backend init|sync`; the published
* artifact imports that generated context so it knows its own author-qualified
* naddr. cvm is declared OPTIONAL in vite.config (O tag): the guard below is
* what keeps a host without CVM working — callers get available:false.
*
* Hard rules (docs/napplet-backend.md): score = 1-based deepest cleared board
* (Progression.runDepth is the 0-based engine depth, so +1 matches the Menu's
* "deepest depth N"); personal best per transport actor only — repeats and
* worse scores are rejected client-side here and server-side by rules;
* trust is client-reported (casual board, no anti-cheat claims); read at most
* every 2 s while visible; names are untrusted public data — render with
* textContent only. Nothing in this module ever throws into game code: every
* failure becomes { ok:false, reason }.
*/
import { cvm } from '@napplet/sdk';
import backend from '../.napplet-space/soy-backend.json';
import { runtimeHasDomain } from './domain-availability.js';
const BOARD = 'depth-v1';
const REF = { napplet: backend.napplet, board: BOARD };
const FALLBACK_NAME = 'unnamed-runner';
export type BoardRow = {
actor: string;
score: number;
name: string;
updated: number;
revision: number;
hasData: boolean;
};
export type BoardResult = {
rows: BoardRow[];
own: BoardRow | null;
trust: string;
nextOffset?: number;
};
export type SubmitOutcome =
| { ok: true; score: number; better: boolean }
| { ok: false; reason: string };
/** True only when the host injected the optional `cvm` domain for this load. */
export function boardAvailable(): boolean {
return runtimeHasDomain('cvm') && typeof backend.napplet === 'string';
}
async function call(
tool: string,
args: Record<string, unknown> = {},
): Promise<Record<string, unknown>> {
const result = backend.provider
? await cvm.callTool(backend.provider, tool, args)
: await cvm.registry.call('soy.boards.v1', tool, args);
if (result.isError) {
const text = (result.content ?? [])
.filter((part) => part.type === 'text')
.map((part) => part.text)
.join('\n');
throw new Error(text || 'Backend request failed');
}
return (result.structuredContent ?? {}) as Record<string, unknown>;
}
/** Fire-and-forget run-end post. `score` is the 1-based deepest cleared
* board; scores below the registered minimum (a zero-clear run) are skipped
* silently. The server keeps only strictly-better personal bests. */
export async function submitRunEnd(
score: number,
rawName: string,
): Promise<SubmitOutcome> {
if (!boardAvailable()) return { ok: false, reason: 'unavailable' };
if (!Number.isInteger(score) || score < 1) return { ok: false, reason: 'no-clear' };
const name = rawName.trim().slice(0, 24).trim() || FALLBACK_NAME;
try {
const r = await call('soy_board_submit', { ...REF, score, name });
if (r.error) return { ok: false, reason: String(r.error) };
return { ok: true, score, better: r.better !== false };
} catch {
return { ok: false, reason: 'offline' };
}
}
/** Top-`limit` read for the leaderboard screen (≤100; 2 s caller throttle). */
export async function readBoard(
limit = 20,
offset?: number,
): Promise<BoardResult | { ok: false; reason: string }> {
if (!boardAvailable()) return { ok: false, reason: 'unavailable' };
try {
const r = await call('soy_board_read', {
...REF,
limit,
...(typeof offset === 'number' ? { offset } : {}),
});
return {
rows: (r.rows ?? []) as BoardRow[],
own: (r.own ?? null) as BoardRow | null,
trust: String(r.trust ?? 'client-reported'),
...(typeof r.nextOffset === 'number' ? { nextOffset: r.nextOffset } : {}),
};
} catch {
return { ok: false, reason: 'offline' };
}
}
