Back to Napcraft
SOURCE / PINNED RELEASE

Made of little things.

Napcraft

Release
ee47ac6a25df…
Author-recorded commit
410dea87e109…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-52f9e22f5ce

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

backend/handler.ts
// Self-contained soy-ts-quickjs-v1 module. Only the provider supplies authority.
import type { BackendContext } from '../docs/examples/backend-context';
type Edit = {x:number;y:number;z:number;block:number;expected:number};
type Change = {n:number;x:number;y:number;z:number;block:number};
type World = {name:string;blocks:number[];edits:number;locked:boolean};
function delta(world:World, changes:Change[], after:number) {
  const reset = after > world.edits || after < world.edits - changes.length;
  return {from:after,edits:world.edits,locked:world.locked,reset,changes:reset?[]:changes.filter(c=>c.n>after)};
}
const W = 18,
  H = 12;
function index(x: number, y: number, z: number) {
  return x + z * W + y * W * W;
}
export async function handle(ctx: BackendContext, input: any) {
  if (ctx.operation === 'createWorld') {
    const blocks = Array(W * W * H).fill(0);
    const put = (x: number, y: number, z: number, b: number) => {
      blocks[index(x, y, z)] = b;
    };
    for (let x = 0; x < W; x++)
      for (let z = 0; z < W; z++) {
        put(x, 0, z, 2);
        put(x, 1, z, 2);
        put(x, 2, z, x < 2 || z < 2 || x > 15 || z > 15 ? 6 : 1);
        if (x > 12 && z < 6) put(x, 3, z, 1);
      }
    for (const [x, z] of [
      [3, 4],
      [13, 12],
      [5, 14],
    ]) {
      put(x, 3, z, 3);
      put(x, 4, z, 3);
      for (let dx = -1; dx <= 1; dx++)
        for (let dz = -1; dz <= 1; dz++) put(x + dx, 5, z + dz, 1);
      put(x, 6, z, 1);
    }
    // A tiny welcoming pavilion; the rest is an open building canvas.
    for (let x = 8; x <= 11; x++) for (let z = 3; z <= 6; z++) put(x, 3, z, 3);
    for (const [x, z] of [
      [8, 3],
      [11, 3],
      [8, 6],
      [11, 6],
    ]) {
      put(x, 4, z, 4);
      put(x, 5, z, 4);
    }
    for (let x = 8; x <= 11; x++) for (let z = 3; z <= 6; z++) put(x, 6, z, 5);
    const world = {
      name: input.name.trim() || 'Our little island',
      blocks,
      edits: 0,
      locked: false,
    };
    await ctx.state.setAccess({
      visibility: 'public',
      building: 'everyone',
      guestsMayBuild: true,
      members: {},
    });
    await ctx.state.set('worlds', 'island', world);
    await ctx.state.set('editLog', 'recent', {changes:[]});
    return world;
  }
  const world = await ctx.state.get<World>('worlds', 'island');
  if (!world) throw new Error('NOT_FOUND: This island is unavailable.');
  if (ctx.operation === 'readWorld') return world;
  const journal = await ctx.state.get<{changes:Change[]}>('editLog','recent') ?? {changes:[]};
  if (ctx.operation === 'readUpdates') return delta(world,journal.changes,input.after);
  if (ctx.operation === 'setLocked') {
    if (!ctx.account || ctx.principal !== ctx.owner)
      throw new Error('FORBIDDEN: Only the world owner can lock building.');
    world.locked = input.locked;
  } else if (ctx.operation === 'editBlock' || ctx.operation === 'editBlocks') {
    if (world.locked)
      throw new Error('FORBIDDEN: The owner has paused building.');
    const edits: Edit[] = ctx.operation === 'editBlocks' ? input.edits : [input];
    if (!Array.isArray(edits) || edits.length < 1 || edits.length > 8) throw new Error('INVALID: Use 1–8 edits per batch.');
    // Validate sequentially on a private copy. No records are written until ALL pass.
    world.blocks = [...world.blocks];
    for (const { x, y, z, block, expected } of edits) {
    if (
      ![x, y, z, block, expected].every(Number.isInteger) ||
      x < 0 ||
      x >= W ||
      z < 0 ||
      z >= W ||
      y < 2 ||
      y >= H ||
      block < 0 ||
      block > 6
    )
      throw new Error('INVALID: Outside the buildable island.');
    const i = index(x, y, z);
    if (world.blocks[i] !== expected)
      throw new Error(
        'CONFLICT: This block changed. Look again before editing.',
      );
    if (block === expected)
      throw new Error('INVALID: This block is already that material.');
    if (block !== 0 && expected !== 0)
      throw new Error('CONFLICT: Remove the existing block first.');
    if (block !== 0 && world.blocks[index(x, y - 1, z)] === 0)
      throw new Error('INVALID: Build on an existing block.');
    if (block === 0 && y < H - 1 && world.blocks[index(x, y + 1, z)] !== 0)
      throw new Error('INVALID: Remove the top block first.');
    world.blocks[i] = block;
    world.edits++;
    journal.changes.push({n:world.edits,x,y,z,block});
    }
    journal.changes = journal.changes.slice(-128);
    await ctx.state.set('editLog','recent',journal);
  } else throw new Error('INVALID: Unknown operation.');
  await ctx.state.set('worlds', 'island', world);
  return ctx.operation === 'editBlocks' ? delta(world,journal.changes,input.after) : world;
}