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

@game-hub/game-stpetersburg

v0.1.1

Published

Saint Petersburg (1st edition) as a Game Hub game package — a standalone repository built against the published @game-hub/kernel and @game-hub/ui-kit, extracted from the platform monorepo on the out-of-repo pattern the Labyrinth pilot proved. Four subpath

Downloads

375

Readme

@game-hub/game-stpetersburg

Saint Petersburg (1st edition — Michael Tummelhofer, 2004; the 2009 Rio Grande printing) as a Game Hub game package: a 2–4 player card-buying engine builder played over ~7–10 rounds of four phases — hire workers for rubles, raise buildings for points, court aristocrats for both, and use trading cards to upgrade what you already own. Your money and your hand are secret; most points wins.

It is a standalone repository that depends on @game-hub/kernel and @game-hub/ui-kit from the public npm registry, like any third-party consumer would. It was extracted from the platform monorepo (container's packages/games/stpetersburg) onto the out-of-repo pattern the Labyrinth pilot proved (Track D): four subpath exports (./engine, ./module, ./client, ./bot) behind game-agnostic hosts, shipped as a compiled dist/ package a host installs like any dependency.

Why this game is interesting to the platform

Saint Petersburg is the hub's first game with real hidden information. The three games before it barely used the redaction seam; this one lives on it:

  • A player's rubles and hand are secret (rulebook pg. 2, "may never tell"). viewFor redacts both: an opponent sees your hand count and nothing else, and rubles: null. Your own seat sees everything.
  • The deck order is a real secret. The four draw stacks are projected as counts, never contents — a view can never leak what will be dealt next.
  • The first bot on the platform that decides from a genuinely incomplete view. ./bot reads only its own seat's knowledge (the redacted StPetersburgView), so cheating is structurally impossible — there is no hidden field for it to peek at.

Redaction lives in the engine (src/engine/view.ts), under the 100% gate — what a player may see is as much a rule as what they may do.

Status

Feature-complete (SP0–SP9). The full game plays: the four-phase spine and round loop, the hidden hand, trading-card displacement, the six special cards (Pub, Observatory, Warehouse, and the rest), final scoring, an illustrated board (the "Malachite & Gilt" art), and an AI opponent that decides from the redacted view. 161 engine + bot tests; engine coverage 100%, bot coverage ≥ 90%. The slice history, the rules digest and every ruling live in ROADMAP.md.

The ./module is routeless — no server-only actions and no per-turn dice. Every special card is a rule, so it lives in the engine: the Pub and Observatory interludes are engine-level turn locks (pendingPubBuy / pendingDraw), and the Observatory draw is a pure engine action (the stack top is deterministic, shuffled once at setup). So this package needs no fastify dependency.

Layout

src/
  engine/     the pure rules core — no I/O, no Date, no Math.random. 100% coverage gate.
    core/       constants, domain types, errors (rulebook-sourced values)
    actions/    one file per move: buy, addToHand, playFromHand, pass, pubBuy, observatory + applyAction
    internal/   shared helpers (deck, phase, scoring, displacement, specials, the kernel record()/seating)
    view.ts     viewFor — the redaction (opponent rubles + hands, and the draw-stack contents)
    tests/      one file per concern
  module/     the backend seam — the routeless GameModule: createGame wiring, parseAction, the error map
  client/     the UI seam — the GameClient + the board
    Board.tsx     the game screen; CardRow / PlayerPanel / Results / Status the panels
    art/          the "Malachite & Gilt" card art (CardFace, Salon, icons)
  bot/        the AI (SP9) — decide / policy / self-play / bench. 90% coverage gate.

Running it

Requires Node 22 (.nvmrc) and pnpm (the version is pinned in packageManager).

pnpm install         # resolves @game-hub/* from the public registry
pnpm test            # vitest + the coverage gates (engine 100%; bot 90%)
pnpm test:watch
pnpm typecheck       # strict TS across all four subpaths
pnpm lint            # ESLint 9 flat config — real hazards, not a second typecheck
pnpm format:check    # Prettier (hand-wrap Markdown; *.md is Prettier-ignored)
pnpm build           # tsc → dist/ (JS + .d.ts + inline-source maps), what publishConfig points at
pnpm pack:smoke      # pack, install outside this repo, play a game under plain node, typecheck a consumer

⚠️ Relative imports in the shipped sources carry an explicit .js extension ('../engine/index.js') — tsc emits them verbatim and Node ESM resolves neither extensions nor directories, so extensionless ones would produce a tarball that throws on a host's first import while every command above stayed green. pack:smoke is the only check that runs against the built dist/, and it is what catches that. Test files under src/**/tests/ are excluded from the build and keep the extensionless style.

CI (.github/workflows/ci.yml) runs exactly those, in that order, on a runner with no access to the platform monorepo — it installs @game-hub/* from the registry with --frozen-lockfile.

Using this package in a host, before it is published: pnpm pack here, then depend on the tarball — the same vendored-tarball loop the hub uses for the Labyrinth pilot.

Rules, and what's original

The rulebook PDF is not in this repository — it is copyrighted, so it stays local (gitignored) and the code cites page numbers instead (see reference_materials/README.md). Mechanics and card names aren't copyrightable; the illustrations are, so every asset in src/client/art is drawn fresh in the house style and nothing is traced.

Licence

BSD-3-Clause (see LICENSE). Not affiliated with or endorsed by the rights holders of Saint Petersburg.