Ports of Call (Ship Voyage) — Edge-Hosted Age-of-Sail RPG

Ports of Call, an Age-of-Exploration trading and naval RPG on Cloudflare's edge with no game server and no AI at runtime, because every line of voice and text is generated at build time.

A fully voiced game that needs no server while it is played

80,882lines of code
512files
584commits
5HTTP endpoints
6data models
Ports of Call (Ship Voyage) — Edge-Hosted Age-of-Sail RPG: Ports of Call -- the title screenPorts of Call (Ship Voyage) — Edge-Hosted Age-of-Sail RPG: Shore party -- six hands ashore on an uncharted coastPorts of Call (Ship Voyage) — Edge-Hosted Age-of-Sail RPG: Night harbour -- London, and the market inside itPorts of Call (Ship Voyage) — Edge-Hosted Age-of-Sail RPG: The world chartPorts of Call (Ship Voyage) — Edge-Hosted Age-of-Sail RPG: A sea battlePorts of Call (Ship Voyage) — Edge-Hosted Age-of-Sail RPG: A lightning storm at sea

The hard part

A story game with spoken dialogue normally needs a server producing lines for each player, so every new player adds cost and every outage stops the game. The alternative is to do that work once, before release. It sounds simple, but it means designing the whole game so that nothing is ever called at runtime.

Every line of voice and text is generated ahead of time and shipped as static files, including more than 41,000 voice clips streamed from storage at the edge, on servers close to the player. Saves live on the player's device, with optional cloud sync, so the network is a convenience rather than a dependency. Pages are served from Cloudflare's edge, audio from its R2 storage and optional saves from its D1 database, and a modest phone still gets a smooth crossing.

584 commits on 39 working days; more than 41,000 voice clips served from the edge; live at ship-voyage-4zn.pages.dev.

What the measurements mean

Only what is unusual for this project. Every sentence names the field it came from; nothing here is asserted that cannot be checked against the data.

How it works

A narrative game with spoken dialogue normally needs a server and generation for each player, so every new player adds cost and every outage takes the game down. The alternative is to do that work once, at build time, and ship the result. It sounds obvious until a whole game has to be restructured around never calling anything while it runs. Every line of voice and text is generated ahead of time and shipped as static files. Saves are kept locally first, with optional cloud sync, so the network is an addition rather than a dependency. The game is live at ship-voyage-4zn.pages.dev, served from the edge, with the voice audio in R2 object storage and D1 holding optional saves, accounts and a leaderboard. Later changes were measured before they were made. Tests showed the warships in a battle all ran one AI routine, so they were split into distinct doctrines and formations. Wages and a bank that works only in port put pressure on gold, and cargo value grows with the distance sailed. The page also gained metadata that crawlers can read, because AI answer engines do not run its JavaScript.

What it is built from

Each bar is one of this project's own modules, sized by how many times the rest of the project imports it. The counts are measured, not estimated, and the module at the top is the one the rest of the project leans on most.

Module mapInternal modules by how often they are imported: revoice 6 times, voice_select 6 times, tts_pool 5 times, battle_bake_common 4 times, tts_text 3 timesrevoice6voice_select6tts_pool5battle_bake_common4tts_text3imports of each internal module, counted across the project

What kind of engineering this is

Six signals counted from the source: files that run models locally, that act without being asked each time, that check their own output, that survive failure, that touch parsers and protocols, and that were measured then improved. Every axis carries its file count.

Depth profileDepth signals: Local inference 17 files, Autonomy 2 files, Verification 3 files, Resilience 22 files, Low level 9 files, Iteration 0 filesLocal inference17 filesAutonomy2 filesVerification3 filesResilience22 filesLow level9 filesIteration0 files

What it is written in

Share of code bytes. Vendored bundles and generated output are set aside before this is computed, which is why it differs from a simple file count.

Language mixTypeScript 52.9%, JavaScript 39.5%, Python 3.7%, HTML 3.3%, Batchfile 0.2%, CSS 0.1%TypeScript 52.9%JavaScript 39.5%

What stands out

Capabilities

Grade

Every check names the field it measured. Checks that cannot apply are excluded, never counted as failures.

A
94.1% over 18 of 18 checks100% coverage

Not met

Architecture

Directories carrying real modules, largest first.

HTTP surface

5 endpoints in total.

Data model

Typed structures and tables found in the source.

Vulnerable dependencies

1 declared dependency matches a published advisory. Checked offline against the OSV database; only pinned versions can be checked, so anything unpinned here is unknown rather than clean.

Vocabulary

Parsed 19 of 19 Python file(s).

104functions
3classes
19documented modules

Classes

Functions

Command line

Depth

Signals counted from the code, not asserted.

584 commits across 39 separate working days over 4.4 months, 86 of them revising existing behaviour

Built from scratch

Closes its own loop

Proves itself

Survives failure

Local inference

Systems

Distributed systems

Working within limits

Environment engineering

Languages

Built with

Recurring themes in the commit history

Evidence

Taken from the code itself: a module docstring or the opening of the README.

Bake battle HAILS PER VOICE so each enemy's seeded voice has a clip, named for the game to play directly: bhail_{context}_{token}_{id}.opus. Each translated line is baked in EVERY Kokoro voice of its language; the runtime picks one via seededVoiceToken(...kokoroOnly) and uses it for the WHOLE fight. Also emits the English-subtitle table the game shows over the ship. Idempotent. See .claude/voice-c

At a glance

Status
active
Primary language
TypeScript
First commit
2026-06-13
Last activity
2026-10-25
Domain
Automation & scraping
Agent docs
CLAUDE.md, AGENTS.md, PROJECT_KNOWLEDGE.md, .claude