Back to Shiftfield
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.

src/board-service.ts
/* 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' };
  }
}