Boilerplate Guide
Seer is a starting point for a new browser-based, data-file-first
reverse-engineering project — extracted from the reusable parts of an
existing sibling project (see docs/architecture-overview.md for the
general architecture this follows). This guide describes what’s here, what’s
a working example vs. a stub you must replace, and what to build fresh.
Seer is structured as an npm workspace: genuinely reusable, game-agnostic
code lives in scoped packages/* (installable independently, in principle —
see docs/framework-plan.md for the roadmap toward that). Everything at the
project root (src/, tools/) is your project — templates and
placeholders you edit and diverge from freely, not framework code.
What’s genuinely reusable, taken as-is (the packages/* workspace)
Section titled “What’s genuinely reusable, taken as-is (the packages/* workspace)”These packages have no game-specific logic and should work unmodified for any target. Import from them; don’t edit their source directly.
| Package | Contains |
|---|---|
@seer-project/core |
binary.ts — endian-aware byte-reading primitives (r8/r16/r24/r32); binary-reader.ts — cursor-based BinaryReader, endianness is a constructor param |
@seer-project/engine-2d |
Camera.ts — 2D pan/zoom/bounds-clamped camera; InputManager.ts — keyboard + mouse input (pan, edge-scroll, wheel zoom, drag, click); DisplayMode.ts — zoom-bounds/scale-mode config; Game.ts — top-level orchestrator shape; pixi-helpers.ts — viewport culling, atlas slicing, label styling (PixiJS-specific; peer dep on pixi.js) |
@seer-project/pipeline |
Node-only: resolveDataDir()/findFileCI() — breadth-first, case-insensitive discovery of wherever the user dropped their game files; io.ts — generic file I/O (readBinary, writePNG, writeIndexedPNG, writeJson, scanFilesByExtension, resolveDataFile); hex-dump.ts — CLI binary inspector, your first tool when reverse-engineering a new format |
@seer-project/iff |
Generic EA IFF-85 FORM/chunk parser (depends on @seer-project/core). Optional — delete this package if your target doesn’t use IFF-derived formats (8SVX, ILBM, ANIM, SMUS, or a custom FORM-based format) |
@seer-project/smus |
SMUS (Simple Musical Score) interpreter — EA IFF 85 SMUS format parser, SampledSound .instr/.ss parser, Sonix audio engine with instrument converters. Optional — only relevant if your target uses SMUS audio |
Also reusable as-is at the project root:
| File | What it does |
|---|---|
tsconfig.json, eslint.config.js, .prettierrc |
Standard strict TS/lint/format setup, shared across all packages |
The __tests__/ colocation convention |
Vitest auto-discovers *.test.ts next to the code it tests, no config needed |
The testing philosophy used throughout (construct real bytes, assert on real decoded output, never mock the decoder) is worth keeping — it validates your reverse-engineering against actual byte-level ground truth.
Other framework packages, not wired into this scaffold by default
Section titled “Other framework packages, not wired into this scaffold by default”The five packages above are everything npm create seer-app puts in a new
project’s package.json — this scaffold only targets the 2D/PixiJS track.
The framework has grown other packages for genres and asset types this
scaffold doesn’t cover; add whichever ones fit your target by hand
(npm install @seer-project/<name> inside the monorepo, or the published
version once released — see docs/framework-plan.md):
| Package | For |
|---|---|
@seer-project/dungeon |
First-person grid dungeon crawlers — level/schema types, poses, collision, raycasting-style view-list construction, raster compositing, automaps. See docs/walker.md. |
@seer-project/engine-3d, @seer-project/mesh-viewer-ui, @seer-project/canvas-export, @seer-project/retro-display |
3D character/model viewers over real glTF or polygon-JSON meshes (skinning, materials, cloth/hair physics, cinematic rendering, screenshot/turntable export, CRT/LCD display filters) — no scaffold template yet, see docs/viewer.md §6. |
@seer-project/gfx |
Retro planar bitmap pixel-format decoders (Amiga/Atari ST). |
@seer-project/tracker, @seer-project/audio-dsp, @seer-project/audio-ui |
Tracker-module (ProTracker-style) audio playback, shared real-time DSP primitives, and the bottom-docked audio-bar viewer UI. |
@seer-project/amiga |
AmigaOS HUNK executable/relocatable-module parsing. |
@seer-project/probe |
Exploratory binary recon (triage, byte-map, sprite/stride/audio candidate detection) for an unknown format before you’ve written a real decoder. |
See docs/project-status.md for which packages are
settled vs. still moving, and each package’s own README for its actual API.
What’s a template/pattern — the shape is right, the content is a placeholder
Section titled “What’s a template/pattern — the shape is right, the content is a placeholder”These files demonstrate an architecture you should keep, but contain placeholder values you must replace as soon as you know your actual target:
src/game-id.ts— replaceGAME_IDS/PLATFORM_IDSwith your real game(s) and platform(s). Keep the pattern (string-literal arrays + type guards + display names) even for a single game — it costs nothing now and pays off the moment you add a second platform port.tools/shared/game-config.ts— replace the single placeholderGAME_PLATFORMSentry with your real config(s). This is the one file you should need to edit when adding a new game or platform port. It re-exports the generic lookup functions from@seer-project/pipelineand defines a locally-narrowedPlatformConfigtype (game: GameId, not barestring) so typos in your config table are still caught at compile time — seedocs/architecture-overview.md§5 for why this narrowing lives here rather than in the library.tools/game1/export-game-data.tsandbuild-assets.ts— these are stage 1 / stage 2 pipeline stubs. Rename thegame1directory to match your actual game ID and fill in real parsing logic once you’ve reverse-engineered the format (see below).tools/extract-game-data.ts— the CLI orchestration pattern (arg parsing, per-step failure tolerance, summary printing). Extend it with a third stage (e.g. audio) by following the same shape as the existing two.src/data/GameData.ts/AssetLoader.ts— the “oneGameAssetsinterface, one loader function, parallelfetch()” convention. Replace the placeholderAtlasMetafields with whatever your build-assets script actually produces.src/main.ts— renders a placeholder splash (game/platform identity fromgame-id.ts, a link to the asset viewer) into#game-container, so the scaffold shows something before you’ve written any gameplay. Replace its contents with acreateGame({ container, ... })call from@seer-project/engine-2donce you have a real engine to boot — see that package’sGame.tsfor theonInit/onUpdateshape, and delete the.splashCSS block inindex.htmlonce you do.vite.config.ts’sserveDataDir()plugin — only needed if some asset type (typically audio) is decoded at runtime rather than precompiled. Delete it if your pipeline precompiles everything.
@seer-project/engine-2d’s Game.ts is technically inside the packages workspace, but
unlike the rest of @seer-project/engine-2d it’s meant to be edited, not imported
as-is: it’s the top-level orchestrator shape (async init → camera/input
wiring → ticker loop) with TODOs for your actual rendering — tilemap,
sprite layers, dialogue screens, whatever your genre needs. This is not
assumed to be a map-based game — see docs/architecture-overview.md §8.
If you outgrow the single-file template, move your game-specific rendering
logic into your own src/ code and have it import Camera/InputManager/
DisplayMode from @seer-project/engine-2d directly, rather than continuing to edit
the package file.
What you’ll need to build from scratch
Section titled “What you’ll need to build from scratch”This is the actual reverse-engineering work, and no boilerplate can do it for you:
- Your container format decoder (if the target bundles multiple assets
into one file — a resource fork, a PAK/WAD file, ROM banks, etc). Use
@seer-project/pipeline’shex-dump.tsand@seer-project/core’sBinaryReaderto start probing; write your ownparseContainer()/findResource()once you understand the layout. If your container format is IFF-derived,@seer-project/iffalready gives you the generic FORM/chunk parsing. - Your bitmap/sprite decoder(s). Depends entirely on the platform: planar bitplane graphics (Amiga/Atari ST), tile-based (SNES/Genesis), packed indexed pixels (VGA), etc.
- Your palette/colour decoder, if the platform uses indexed colour.
- Your executable data-table parser, if game data (entities, items,
levels) is embedded directly in the executable rather than in separate
resource files. If your target is a 68k/AmigaOS executable, the hunk-format
loader in the sibling project’s
src/assets/formats/exe-data.ts(parseHunks()) is a reasonable reference — the table offsets are still unique to each compiled binary and must be found via disassembly. - Your audio format decoder, if applicable —
@seer-project/smusis populated with a working SMUS interpreter if your target uses SMUS. - Your actual game/rendering logic, built out from
@seer-project/engine-2d’sGame.tstemplate.
Suggested workflow for a new target
Section titled “Suggested workflow for a new target”- Get the original game files under
data/<game>/<platform>/. - Use
npm run hex-dump -- <file>to start probing headers and structure. - Write a minimal container/bitmap decoder as you understand more of the
format, with tests constructing synthetic byte fixtures (see
packages/iff/src/__tests__/iff.test.tsfor the pattern). - Update
tools/shared/game-config.tswith your real game+platform entry. - Fill in
tools/<game>/export-game-data.tsandbuild-assets.ts. - Wire up
src/data/GameData.ts/AssetLoader.tsto match what stage 2 actually produces. - Build out
@seer-project/engine-2d’sGame.ts(or your ownsrc/code importing from it) to render your actual content. - Run
npm testandnpm run lintbefore considering anything done.