Back to Napplet Machines V8
SOURCE / PINNED RELEASE

Made of little things.

Napplet Machines V8

Release
f041ff5dc40c…
Author-recorded commit
71c68951e1dd…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-b5572362d4a

Archive hash verified: 996282af7a380b0d…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/network-codec.ts
import type { World, Body, Input } from './physics.js';

// Application format MC7/1. Roster/name/car arrive reliably in the race start.
// Binary frames never carry an identity; the authenticated event.from owns input.
export const stateFields = [
  'x',
  'y',
  'z',
  'vx',
  'vy',
  'vz',
  'angle',
  'spin',
  'steering',
  'handbrake',
  'progress',
  'lastDistance',
  'safeDistance',
  'lap',
  'finished',
  'falls',
  'respawn',
  'boost',
  'pitch',
  'roll',
  'ack',
  'nextGate',
  'stuck',
] as const;
type Command = { seq: number; input: Input };
export type StatePacket =
  | { type: 'world'; race: string; world: World }
  | { type: 'input'; race: string; commands: Command[] };
const header = 20;
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
export function encodeState(packet: StatePacket): Uint8Array | undefined {
  if (!uuid.test(packet.race)) return;
  const world = packet.type === 'world' ? packet.world : undefined;
  const commands = packet.type === 'input' ? packet.commands : [];
  const length = world
    ? header + 14 + world.bodies.length * (1 + stateFields.length * 4)
    : header + 1 + commands.length * 9;
  if (length > 16384 || commands.length > 120) return;
  const bytes = new Uint8Array(length),
    view = new DataView(bytes.buffer);
  bytes.set([77, 67, 1, world ? 1 : 2]);
  bytes.set(
    packet.race
      .replaceAll('-', '')
      .match(/../g)!
      .map((h) => parseInt(h, 16)),
    4,
  );
  let offset = header;
  const u32 = (n: number) => {
    view.setUint32(offset, n, true);
    offset += 4;
  };
  const f32 = (n: number) => {
    view.setFloat32(offset, n, true);
    offset += 4;
  };
  if (world) {
    u32(world.tick);
    f32(world.time);
    f32(world.countdown);
    bytes[offset++] = world.ended ? 1 : 0;
    bytes[offset++] = world.bodies.length;
    for (const b of world.bodies) {
      bytes[offset++] = (b.grounded ? 1 : 0) | (b.boosting ? 2 : 0) | (b.ai ? 4 : 0);
      for (const field of stateFields) field === 'ack' ? u32(b[field]) : f32(b[field]);
    }
  } else {
    bytes[offset++] = commands.length;
    for (const c of commands) {
      u32(c.seq);
      view.setInt16(offset, Math.round(c.input.throttle * 32767), true);
      offset += 2;
      view.setInt16(offset, Math.round(c.input.steer * 32767), true);
      offset += 2;
      bytes[offset++] = (c.input.brake ? 1 : 0) | (c.input.boost ? 2 : 0) | (c.input.reset ? 4 : 0);
    }
  }
  return bytes;
}
export function decodeState(
  bytes: Uint8Array,
  race: string,
  roster: Body[],
): StatePacket | undefined {
  if (
    bytes.length < header + 1 ||
    bytes.length > 16384 ||
    bytes[0] !== 77 ||
    bytes[1] !== 67 ||
    bytes[2] !== 1
  )
    return;
  const hex = Array.from(bytes.slice(4, 20), (n) => n.toString(16).padStart(2, '0')).join('');
  if (hex !== race.replaceAll('-', '')) return;
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
  let offset = header;
  const u32 = () => {
    const n = view.getUint32(offset, true);
    offset += 4;
    return n;
  };
  const f32 = () => {
    const n = view.getFloat32(offset, true);
    offset += 4;
    return n;
  };
  if (bytes[3] === 1) {
    if (bytes.length !== header + 14 + roster.length * (1 + stateFields.length * 4)) return;
    const tick = u32(),
      time = f32(),
      countdown = f32(),
      ended = bytes[offset++],
      count = bytes[offset++];
    if (ended > 1 || count !== roster.length || count < 1 || count > 6) return;
    const bodies: Body[] = [];
    for (const original of roster) {
      const flags = bytes[offset++];
      if (flags > 7) return;
      const b = {
        ...original,
        grounded: !!(flags & 1),
        boosting: !!(flags & 2),
        ai: !!(flags & 4),
        name: flags & 4 && !original.ai ? original.name.slice(0, 12) + ' · AI' : original.name,
      };
      for (const field of stateFields) b[field] = field === 'ack' ? u32() : f32();
      bodies.push(b);
    }
    return { type: 'world', race, world: { tick, time, countdown, ended: !!ended, bodies } };
  }
  if (bytes[3] === 2) {
    const count = bytes[offset++];
    if (count < 1 || count > 120 || bytes.length !== header + 1 + count * 9) return;
    const commands: Command[] = [];
    for (let i = 0; i < count; i++) {
      const seq = u32(),
        throttle = view.getInt16(offset, true) / 32767,
        steer = view.getInt16(offset + 2, true) / 32767;
      offset += 4;
      const flags = bytes[offset++];
      if (flags > 7) return;
      commands.push({
        seq,
        input: {
          throttle,
          steer,
          brake: !!(flags & 1),
          boost: !!(flags & 2),
          reset: !!(flags & 4),
        },
      });
    }
    return { type: 'input', race, commands };
  }
}
export const stateBase64 = (bytes: Uint8Array) => btoa(String.fromCharCode(...bytes));
export function stateBytes(text: string): Uint8Array | undefined {
  if (
    text.length > 21848 ||
    !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(text)
  )
    return;
  const raw = atob(text);
  if (btoa(raw) !== text) return;
  return Uint8Array.from(raw, (c) => c.charCodeAt(0));
}