npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bgoodman/ladder

v0.0.2

Published

Deterministic TypeScript runtime for turn-based games

Readme

Ladder

Ladder is a deterministic TypeScript runtime for turn-based board, dice, and card games.

A Ladder game is a package of pure rules: serializable state, typed decisions, automatic transitions, legal choices, projections, and an outcome. Ladder provides seeded randomness, run-until-blocked execution, validation, actor-owned requests, trusted detached forks, checkpoints, and replay.

The model

A game alternates between two settled states:

  • Blocked: the game is waiting for one or more actor decisions.
  • Complete: the game has an outcome and no pending decisions.

Creating a session and submitting a decision always runs automatic rules until one of those states is reached. Applications do not drive internal phases or random effects step by step.

Actors are stable player identities registered when a game is created. A viewer describes whose projection a runtime caller may see. Applications use { kind: "actor", actorId } for a player's frame and submit calls; a transport maps authenticated users to those viewers.

import { createLadder } from "@bgoodman/ladder/core"
import { highRoll } from "./high-roll"

const ladder = createLadder()

const session = ladder.create(highRoll, {
  gameId: "table-1",
  config: { targetScore: 12 },
  actors: [{ id: "alice" }, { id: "bob" }],
  seed: "demo-seed",
})

const alice = { kind: "actor", actorId: "alice" } as const
const frame = session.frame(alice)
const request = frame.visibleRequests[0]

const result = session.submit(
  {
    requestId: request.id,
    revision: request.revision,
    actorId: "alice",
    response: { optionId: "roll" },
  },
  alice,
)

if (!result.accepted) {
  console.error(result.rejection)
} else {
  console.log(result.frame.view)
}

When controllers can answer every actor request, let Ladder own that loop:

import { runGame } from "@bgoodman/ladder/core"

const finalFrame = await runGame(session, {
  controllers: {
    alice: () => ({ optionId: "roll" }),
    bob: () => ({ optionId: "roll" }),
  },
  resultViewer: { kind: "observer" },
})

Controllers may be local strategies, terminal prompts, remote players, or test fixtures. Their responses still pass through normal validation and replay history. Use frame and submit directly when an application needs custom interaction or recovery.

Set policySeed when a controller needs request-scoped randomness separate from the game's rule RNG. Deterministic controllers can ignore it. Accepted decisions remain the canonical replay input; controllers are not called during replay.

The seed, game-definition version, config, actors, and accepted decisions fully determine the result. Replaying them produces the same state, requests, events, and outcome.

Define a game

A game definition supplies six things:

  1. A stable ID and version.
  2. Zod schemas for persisted data and decision payloads.
  3. initialize, advance, and decide transition functions.
  4. Pending decision requests, optionally with finite legal choices.
  5. State and event projections for actors, observers, and admins.
  6. A ranked outcome when play is complete.
import * as z from "zod"
import { defineGame } from "@bgoodman/ladder/core"

const configSchema = z.strictObject({
  targetScore: z.number().int().positive(),
})

type Config = z.infer<typeof configSchema>

const game = defineGame({
  game: { id: "example/high-roll", version: "1.0.0" },
  schemas,
  decisions,
  initialize,
  advance,
  decide,
  projectState,
  projectEvent,
})

Game state stays plain serializable data rather than extending a Ladder class. defineGame provides inference and conformance checking around the complete definition without adding runtime behavior. Game packages author Zod 4 schemas, infer their TypeScript types with z.infer, and let Ladder derive JSON Schema Draft 2020-12 for MCP and other transport surfaces.

Start with Create your first game for a complete example. Use the API reference for the contracts and lifecycle.

Core guarantees

  • Deterministic: rules are synchronous, pure, seeded, and replayable.
  • Transport-neutral: the core has no HTTP, WebSocket, MCP, UI, or database dependency.
  • Private by projection: authoritative state stays inside the session; callers receive viewer-specific state, events, and requests.
  • Typed and runtime-validated: Zod schemas validate data and infer TypeScript types; Ladder derives JSON Schema for external tool surfaces.
  • Stale-safe: request IDs and state revisions prevent old or duplicate responses from changing a game.

Documentation

Scope

Ladder owns game execution, not game content. Boards, cards, scoring rules, trade rules, AI heuristics, and presentation belong to game packages. Persistence, transports, authentication, experiments, and generated MCP tools are extensions around the core. Trusted local rollout evaluation belongs in the optional @bgoodman/ladder/ai extension; it is not an actor or MCP capability.