Serfbound is the pure-browser workspace for the PMO roadmap in
pm/roadmap/serfbound/. Product code is TypeScript-first browser code. Do not
add .NET runtime code, desktop launchers, Electron/Tauri shells, native
companions, unlicensed server-side asset hosting, or bundled raw original
DOS/Amiga data. The only hosted asset path is the Phase 31 licensed converted
runtime package flow recorded in LICENSE-CONSENT.md and
pm/roadmap/serfbound/adoption/asset-and-legal-boundary.md.
From the repository root:
# from the repository root
source ~/.nvm/nvm.sh
nvm use
npm ci
npx playwright install chromiumThe workspace uses the Node version in .nvmrc and the npm version declared in
packageManager.
@serfbound/enginecontains deterministic simulation primitives. It must not use DOM, rendering, storage, audio, or local file APIs.@serfbound/assetsowns browser asset import and catalog parsing boundaries. It must not include original asset payloads.@serfbound/appowns browser UI, input, IndexedDB persistence, rendering integration, and local game wiring.@serfbound/test-supportreads committed CI-safe fixture data for tests. Product packages must not depend on it.
Run:
npm run check:boundariesNormal CI must pass without serfbound-local-data/ and without any original
game data.
Use:
npm test
npm run ci:releasenpm test builds the workspace, runs Node CI tests against committed data-free
fixtures, builds the static browser shell, and runs Chromium browser smoke
tests.
npm run ci:release runs the release gate: CI-safe tests, module boundary
checks, public README/CONTRIBUTING link checks, licensed-asset
consent/artifact checks, static release build and artifact inspection,
subpath static-hosting verification, and the local asset skip path.
CI-safe oracle fixtures live in:
pm/roadmap/serfbound/reference-fixtures/ci/
Current CI-safe fixtures:
rng-fixed-seed-sequence.jsonmap-geometry-facts.json
Tests consume these files as data. Product runtime code must not import or run
pm/roadmap/serfbound/reference-tools/.
Local/manual oracle outputs live under ignored serfbound-local-data/ paths and
must remain metadata-only. Do not commit raw .PA bytes, extracted sprites,
music, sounds, palettes, original executables, disk images, or unlicensed
converted original assets.
Local asset checks are opt-in. They are not part of normal CI.
Use the local file only when the user has provided it under ignored
serfbound-local-data/:
npm run build
SERFBOUND_RUN_LOCAL_ASSET_TESTS=1 \
SERFBOUND_SPAU_PA="serfbound-local-data/sources/TheSettlersDemo/Serf-City-Life-is-Feudal_DOS_EN/SPAU.PA" \
npm run test:local:assetsIf the environment variable is not set, the command exits successfully with a skip message. If local data is present, the command compares parser, typed asset catalog, and render-layer facts against metadata-only local oracle output, then decodes real palettes and sprites, composes terrain triangles, and builds a decoded render scene.
To capture decoded-scene screenshots from real local data (used for phase
evidence; same opt-in environment variables; SERFBOUND_CAPTURE_DIR and
SERFBOUND_CAPTURE_PREFIX select the output folder and file prefix,
defaulting to uncommitted .tmp/browser-screenshots/):
SERFBOUND_RUN_LOCAL_ASSET_TESTS=1 \
SERFBOUND_SPAU_PA="serfbound-local-data/sources/TheSettlersDemo/Serf-City-Life-is-Feudal_DOS_EN/SPAU.PA" \
npm run capture:local:screenshotsPhase 31's licensed package path starts with a deterministic conversion step.
It reads a local original archive and writes a browser-runtime package that
records LICENSE-CONSENT.md, source checksum, content checksum, decoded sprite
payloads, composed serf torsos, decoded SFX, and parsed music events. Do not
commit generated packages unless a later Phase 31 story explicitly opens that
path.
npm run build
node scripts/convert-licensed-assets.mjs \
--input serfbound-local-data/sources/TheSettlersDemo/Serf-City-Life-is-Feudal_DOS_EN/SPAU.PA \
--output .tmp/serfbound-assets.sb31.json \
--archive-name SPAU.PA
node scripts/convert-licensed-assets.mjs --inspect .tmp/serfbound-assets.sb31.jsonThe package format is sb31-runtime-v1. Normal CI covers it with generated
fixtures only; real-data conversion remains opt-in until an audited package is
published for a release.
SB-31-03 adds the browser delivery/cache path for sb31-runtime-v1 packages,
and SB-31-04 adds the default public manifest discovery path. The app accepts a
package URL and release checksum, downloads the package once, verifies the
checksum and embedded provenance before activation, stores it in a separate
IndexedDB database from imported SPAU.PA, and restores it on reload without a
second network request.
For public-style releases, serve:
/licensed-assets/manifest.json
with this shape:
{
"kind": "serfbound.licensed-asset-delivery",
"schemaVersion": 1,
"formatVersion": "sb31-runtime-v1",
"permissionRecord": "LICENSE-CONSENT.md",
"pmoStory": "SB-31-01",
"packageUrl": "serfbound-assets.sb31.json",
"packageChecksum": {
"algorithm": "fnv1a32",
"value": "<fnv1a32>"
}
}For local override runs:
/?licensedAssetPackage=/licensed-assets/serfbound-assets.sb31.json&licensedAssetChecksum=<fnv1a32>
Import data remains an override path. When a player imports local SPAU.PA,
the Data panel source switches from Licensed package to Imported data, and
the imported file still never uploads.
Run the release guard whenever package hosting changes:
npm run check:licensed-assetsThe guard requires LICENSE-CONSENT.md, the amended asset/legal boundary, no
raw original archives in public/, deploy/, or dist/, and valid
public/licensed-assets/*.sb31.json or dist/licensed-assets/*.sb31.json
packages when such artifacts are deliberately present.
Run the public-origin audit after the manifest/package are deployed:
npm run audit:licensed-assets:public -- --base https://serfbound.comThe public audit fetches /licensed-assets/manifest.json, verifies the served
package checksum and embedded LICENSE-CONSENT.md / SB-31-01 provenance,
requires HTTPS outside localhost, and probes common raw-archive paths so the
served origin does not accidentally expose original data.
Build and inspect the static artifact:
npm run release:staticServe and verify the static artifact under a path mount:
npm run test:release:staticThe release artifact is serfbound/dist/. It is static browser output and must
not contain original data, .NET assemblies, native runtime files, desktop
launchers, installers, or app bundles.
See docs/static-hosting-release.md for hosting and cache policy.
Before shipping a roadmap story:
- Read the story file under
pm/roadmap/serfbound/phase-*/. - Make the code/docs change.
- Run the relevant documented commands and read the output.
- Add or update
evidence-story-{n}.mdwith command output summaries and artifact paths. - Flip the story status to
done. - Update
current-phase-status.md,pm/roadmap/serfbound/README.md, and any affected adoption or release docs in the same commit. - Let the PMO pre-commit hook run normally. Do not use
--no-verify.
The pre-commit hook requires .tmp/CONTRACT.md with all seven PMO checkboxes
set to [x]. If a story flips to done, its matching evidence file must ship
in the same commit.
The current release path proves the first browser slice only: import, start, select, build one flag, save, reload, resume, reset, and release packaging. Full original gameplay, audio, AI, multiplayer, original save compatibility, and physical-device certification remain outside this slice until future stories add evidence.
@serfbound/engine (deterministic, DOM-free):
map-generator— the classic generator (fixture parity) and map data tables;game-world— flags/roads/buildings/territory/conquest;pathfinder— A* road routing;serfs— the serf state machine, transport, professions, construction, combat;inventory— castle stock and the supplies presets;missions— the 31-mission campaign;ai— the classic AI (replayable world actions, decision logs);dos-savegame— the original .SAV reader;local-game/simulation— game state, snapshots, world-action replay;commands— the pointer command router.
@serfbound/assets (decode-only, no bundled data):
dos-sprites— the .PA archive and sprite payload decoders;terrain-tiles— triangle composition;serf-sprites— animation table and player-color torsos;sprite-atlas— runtime packing;ui-art— fonts/icons/frames/cursor/logo;audio-sfx— DOS clips;audio-xmi— XMI music parsing.
@serfbound/app (browser shell):
render-layer-scene— decoded assets and the WebGL2 renderer;landscape-scene— the live world scene with the authentic chrome;panel-bar/popup/init-screen— the original interface logic;audio— the gesture-gated WebAudio service; the IndexedDB stores;main— the shell, input, AI drivers, autosave, error intake, and the service-worker registration.
npm run test:unit— the CI engine/app suites (170+ tests, data-free).npm run test:browser— Playwright: founding e2e through the authentic UI, mobile touch play, PWA offline, shell recovery.npm run measure:performance/npm run measure:scale— the recorded performance baselines.npm run check:links— README/CONTRIBUTING local-link and media-reference integrity, with external URLs syntax-checked.SERFBOUND_RUN_LOCAL_ASSET_TESTS=1 SERFBOUND_SPAU_PA=... npm run test:local:assets— opt-in real-data decode checks.npm run ci:release— the full data-free release gate set.
Releases: tag v* (see docs/static-hosting-release.md).