Handoff Artifact · Read-only codebase review
A neutral, factual description of the scrap codebase, produced so that a separate model — with no access to this repository — can propose game formats and gameplay styles without being primed toward the current shape of the experience.
Every factual claim is marked Observed (read directly in the code, data, or docs) or Inferred (a reading drawn from the code). Gaps are stated as gaps rather than guessed. Per the governing brief this document deliberately omits navigation, interface structure, layout, input, and interaction flow, plus mechanics internals — so the reader is not anchored to the current design. Section 09 is the evidence appendix: claim → file.
scrap is a TypeScript monorepo containing a browser-delivered game client, a separate HTTP backend service, an image-asset pipeline, and a documentation set. Observed
The client is a Vite 6 + React 19 single-page application, styled with Tailwind 4 and plain CSS, using lucide-react for icons and motion for animation. Type-checking is tsc --noEmit; tests run under Vitest. The client source is 12,235 lines of .ts/.tsx plus 1,174 lines of CSS. Its largest area is src/game/: 14 non-test modules totalling 5,543 lines, accompanied by 12 test files totalling 2,906 lines. Game content lives in src/data/gameContent.ts (904 lines) with a separate referential-integrity checker in src/data/contentValidation.ts (139 lines). src/types.ts (645 lines) holds the shared type surface and a literal initial-state object. Observed
The backend (api/) is an independent Node 20 + Fastify 5 service using better-sqlite3, Zod, Argon2id, JWT, Nodemailer, and Pino: 7,327 lines of TypeScript across routes/, services/, db/, middleware/, and config.ts, plus 6 SQL migrations (637 lines) and 5 transactional email templates (each in html/plain/subject form). Its own package.json describes it as "Self-hosted cloud saves & accounts backend for Scrapline". Observed
The art pipeline is 7 Node scripts (scripts/*.mjs, 669 lines) driven by artwork/art-direction.json (a style/palette/negative-prompt/role/dimension spec) and a runtime manifest src/assets/assetRegistry.ts. Deployment is Docker: an nginx container serving the static build, and a second additive Compose file for the API. docs/ holds 13 markdown files (3,478 lines); a further 307-line art checklist sits at the repo root. Git history is a single squashed commit (chore: initialize repository (scrapline3 v2.0.0)), so change history is not available. Observed
The setting is a post-industrial freight network of ten named urban districts — Rustport, East Grid, Glassmarket, Lowtown, Iron Mile, North Foundry, South Reef, Ashgate, Relay Nine, Morrow Yard — each carrying a type label (Salvage Dock, Industrial Market, Tech Bazaar, Residential Sprawl, Freight Corridor, Reactor Yard, Flood Quarter, Contraband Crossing, Signal Station, Salvage Depot), a prose description, a stability number, and a population number. Observed
The tradeable goods (25) are industrial and survival materials: scrap metal, copper wire, battery cells, water filters, circuit boards, fuel canisters, salvage parts, medical supplies, iron plating, fiber cable, solar cells, coolant packs, ration packs, protein supplements, stim packs, trauma kits, signal arrays, void crystals, collector artifacts, modified parts, coolant rods, rope & cable, sensor modules, hazmat kits, grain alcohol — sorted into seven categories (scrap, electronics, utility, power, medical, food, luxury). Observed
Four factions are defined: Ironclad Logistics Corp. (described in data as the licensing and debt-ledger authority), Rustport Dockhands, Glassmarket Brokers, Lowtown Civic Ring. Observed
Sixteen named speaker characters exist, each with a role, an archetype (contractor, lender, collector, official, neutral), an optional district affiliation and specialty, and three lines of dialogue (48 lines total). Roles include Community Organizer, Credit Broker, Debt Recovery Agent, Contract Dispatcher, Market Analyst, Dockmaster, Grid Foreman, Bazaar Runner, Clinic Quartermaster, Convoy Marshal, Reactor Steward, Canal Pilot, Checkpoint Broker, Signal Keeper, Yard Archivist, Field Mechanic. Observed
Three vehicles (Damaged Cart, Flatbed Hauler, Shielded Rig), six rank titles (Unlicensed Runner → Scrapline Magnate), and three license tiers (Provisional, Standard Salvage, Guild Certified) supply the remaining named fiction. Environmental fiction includes five disaster types (storm front, flood surge, grid blackout, industrial fire, dust squall). Observed
The art direction document states the visual register as "cinematic industrial salvage realism … near-black and slate-steel … oxidized metal, wet concrete, teal instrument light, sodium-amber practicals", and explicitly excludes "clean utopia, glossy showroom sci-fi, neon-cyberpunk excess, fantasy magic". Observed — artwork/art-direction.json
docs/STEAM_STORE_COPY.md (a draft, per its own heading) describes the product as "a focused single-player strategy game about making difficult decisions with limited time and imperfect information"; docs/RELEASE_READINESS.md records the product position as "single-player freight-trading strategy game". These are the project's own characterisations, reported here as such and not adopted. Observed
Categories of thing the player does, named at the highest level. No procedure, no steps.
Each entry names existence and, at most, inputs and outputs. No internals.
api/.src/game/cloudSync.tsServer-side systems: email/password and magic-link authentication, email verification, password reset, session listing and revocation, save slots with server-allocated revisions and conflict responses, save snapshots, idempotency keys, share links with public/unlisted/private visibility and abuse reporting, data export, scheduled account deletion, and an audit event log. Observed
Game content (src/data/gameContent.ts, 904 lines, 242 top-level data objects): 25 goods · 10 districts · 118 baseline market price rows · 1 hand-authored contract instance · 2 scripted day-advance fixtures · 3 vehicles · 11 upgrade definitions · 4 route types · 9 route-event types · 9 contract templates · 4 factions · 16 speakers (48 dialogue lines) · 3 loan tiers · 6 rank tiers · 3 license tiers · 18 route definitions. Observed
Additional typed content sets: 5 disaster types · 5 broadcast kinds · 7 market conditions · 6 failure reasons · 19 route-event choice definitions in src/game/travel.ts. Observed
Art: 95 registry entries, all approved with a non-null source. 95 source PNGs in artwork/source/, 95 optimized .webp files in public/artwork/approved/, 9 review renders. By kind: 25 good, 16 portrait, 11 upgrade, 10 district, 9 contract, 9 route-event, 6 rank, 3 foundation, 3 outcome, 3 vehicle. Also present: 7 favicon files and 8 marketing assets including one .mp4. No audio files of any format exist in the repository. Observed
Tests: 14 test files in src/ containing 169 it/test declarations, one of which is an it.each over 9 route-event ids. docs/RELEASE_READINESS.md records a pass at "178/178"; the reviewer did not execute the suite. api/ has 2 hand-run test scripts (202 lines) rather than a test-runner suite. Observed
Backend: 44 registered HTTP routes (20 auth, 9 shares, 6 saves, 4 account-deletion, 1 data-export, 1 test-mail, 3 service-health/version), 15 SQL tables across 6 migrations. Observed
Docs: DEV-CHECKLIST.md 673 · NEXT-ACTIONS.md 743 · DECISIONS.md 458 (numbered, dated, signed decision entries) · RUNLOG.md 372 · SECURITY-THREAT-MODEL.md 259 · SAVE-VERSIONING.md 237 · PRIVACY-POLICY.md 183 · CONFIG.md 168 · SECRETS.md 121 · TERMS.md 112 · TRAILER_VIDEO_PROMPTS.md 64 · RELEASE_READINESS.md 61 · STEAM_STORE_COPY.md 27 lines. Observed
Strictly bounded to what constrains what formats are possible — stated without describing the current experience.
GameState object mutated by pure functions in src/game/; no module in the gameplay path references another player, another session, or a server. Observedusers table, sessions, per-user save slots with revisions, and share links (public/unlisted/private) that carry a copied snapshot payload. There is no WebSocket, real-time channel, or shared-world endpoint anywhere in api/. ObservedcloudSync.ts targets endpoints that are implemented in api/src/routes/ (/auth/*, /saves/*). serverProfile.ts targets /api/profiles, /api/profile, /api/save-slots, /api/import/local-save, /api/completed-runs, /api/records, and /api/leaderboard — none of which are registered in api/src/routes/. The leaderboard path is therefore a client-side capability without a backend in this repository. Inferred — modestSAVE_SCHEMA_VERSION = 2, an explicit migration function, export/import of portable backups, plus a cross-run career record and a settings object. Remote: optional, server-authoritative integer revisions with declared parent-revision and conflict responses. docs/RELEASE_READINESS.md states local-first behaviour with cloud as opt-in. ObservedGAME_CONTENT bundle and checked by a separate validator. Gameplay modules import these arrays rather than embedding entities. Content is TypeScript literals compiled into the bundle, not external JSON or a database. Observedday counter with a bounded maxDays (30 in the initial state) and a runStatus of active/completed/failed. The only timers in the client are one animation delay and one 5-second clock refresh; there is no simulation tick, frame loop, or physics. ObservedMath.random appears nowhere in src/. Variation comes from an FNV-style hash over a per-run runSeed combined with day, district, and context strings, so a given seed reproduces the same events. Observeddocs/RELEASE_READINESS.md names a premium desktop/Steam build as the commercial target with the web build retained as a demo, and lists console, mobile, multiplayer, and leaderboards as out of scope for the first release. No desktop wrapper (Electron, Tauri, or similar) exists in the repository. ObservedAudio/howler/Web Audio references in any source file. ObservedPer the brief governing this document, the reviewer deliberately omitted all description of navigation, interface structure, layout, input, and interaction flow, and all mechanics internals — formulas, balance constants, per-action procedures, state-machine descriptions, and cost or timing tables. Where a system's internal procedure would have been the only way to convey a fact, the system was named and the description stopped. For the same reason the reviewer excluded client files whose content is onboarding or interface material rather than game data or simulation logic, and did not describe the composition of the marketing page component. This omission is deliberate so that the reader is not primed toward the current shape of the experience; it is not a statement that those parts are absent, small, or unimportant.
docs/RELEASE_READINESS.md, not a measurement it made. (Hermes independently verified this afterwards — 178/178 pass.)serverProfile.ts targets endpoints absent from api/: whether they were removed, never built, or live in a different repository. Unknown — not determinable from the code.docs/DECISIONS.md is explicitly scoped to the cloud-saves subsystem and states that "top-level game decisions for Scrapline live in the parent project's docs" — those docs are not in this repository.docs/STEAM_STORE_COPY.md and docs/RELEASE_READINESS.md speak to these, and both are internal drafts.| Claim | Evidence |
|---|---|
| Stack, scripts, dependencies (client) | package.json; vite.config.ts; tsconfig.json |
| Client LOC totals | wc -l over src/**/*.{ts,tsx,css} — 12,235 TS/TSX, 1,174 CSS |
| 14 gameplay modules, 5,543 LOC; 12 game test files, 2,906 LOC | src/game/*.ts (non-test / *.test.ts split) |
| 242 content objects across 16 arrays | src/data/gameContent.ts lines 20–885; per-array line ranges below |
| 25 goods; 7 categories | gameContent.ts:20–47; types.ts:270–277 |
| 10 districts with type/stability/population | gameContent.ts:49–60 |
| 118 baseline market price rows | gameContent.ts:62–204 |
| 1 authored contract; 2 day-advance fixtures | gameContent.ts:206–224, 226–250 |
| 3 vehicles; 11 upgrades (6 effect kinds) | gameContent.ts:252–277, 279–398; types.ts:332 |
| 4 route types; 9 route-event types; 18 routes | gameContent.ts:400–405, 407–417, 863–885 |
| 9 contract templates, tiers 1–4 | gameContent.ts:419–547 |
| 4 factions; 16 speakers; 48 dialogue lines | gameContent.ts:549–577, 579–820 |
| 3 loan tiers; 6 ranks; 3 license tiers | gameContent.ts:822–846, 848–855, 857–861 |
| Content bundled and validated as data | gameContent.ts:887+ (GAME_CONTENT); contentValidation.ts:41 (validateGameContent) |
| 5 disaster types; category mapping | disasters.ts:6–14; types.ts:223 |
| 5 broadcast kinds, 4 statuses | types.ts:235–250 |
| 7 market conditions; price state shape | types.ts:296–314; market.ts:5–21 |
| 6 failure reasons | types.ts:84 |
| 19 route-event choice definitions | travel.ts:554–799 (buildEventChoices) |
| Day counter, maxDays 30, run status | types.ts:26–65, 520–535 (INITIAL_STATE) |
| Deterministic seeding, no Math.random | broadcasts.ts:6 (hashSeed); characters.ts:33; contracts.ts:81; actions.ts:708; grep for Math.random in src/ → 0 hits |
| Only two client timers | App.tsx:248 (setTimeout), App.tsx:1248 (setInterval, 5,000 ms clock) |
| Local persistence: 3 slots, schema v2, migration, career record | persistence.ts:5–7, :127 (migrateSavedGameSnapshot), :287–331 (career) |
| Cloud sync targets implemented endpoints | cloudSync.ts:107–187 (/api/auth/*, /api/saves/*) |
| serverProfile targets unimplemented endpoints | serverProfile.ts:105,115,122,139,147,154,165; no matching handler in api/src/routes/* |
| 44 API routes | api/src/routes/{auth,saves,shares,test,data-export,account-deletions}.ts; api/src/server.ts:297–304 |
| Share visibility + abuse reporting | api/src/routes/shares.ts:1–23, :30–36 |
| 15 SQL tables, 6 migrations, 637 LOC | api/migrations/0001–0006*.sql |
| Server-authoritative save revisions and conflicts | docs/SAVE-VERSIONING.md §1–3 |
| Backend stack, LOC, purpose | api/package.json; wc -l over api/src/**/*.ts = 7,327 |
| Backend design decisions (self-hosted, repository abstraction, Argon2id, token rotation) | docs/DECISIONS.md D-001, D-002, D-007, D-010, D-011 |
| 95 art registry entries, all approved; kind breakdown | src/assets/assetRegistry.ts; ls public/artwork/approved = 95; ls artwork/source/*.png = 95 |
| Art direction spec (style, palette, negative prompt, 10 roles, 4 aspect ratios) | artwork/art-direction.json |
| Art pipeline scripts, 669 LOC | scripts/*.mjs; package.json art:* scripts |
| No audio anywhere | grep for Audio/howler/.mp3/.ogg/.wav in src/, api/src/, both package.json → 0 hits; find for audio files → 0 |
| No WebSocket / real-time channel | grep for websocket/socket.io in src/, api/src/ → 0 hits |
| Deployment: nginx static container, loopback port, Cloudflare Tunnel | docker/STACK.md; docker/docker-compose.yml; docker/docker-compose.api.yml |
| Client entry selects game or marketing component | src/main.tsx:8–12 |
| Product position, platform scope, out-of-scope list, "178/178" | docs/RELEASE_READINESS.md:5–25 |
| Store-copy self-description (attributed, not adopted) | docs/STEAM_STORE_COPY.md:5,13 |
| Game-design docs live outside this repo | docs/DECISIONS.md:3–5 |
| Single squashed commit | git log --oneline → 3c4f3c8 chore: initialize repository (scrapline3 v2.0.0) |
| 14 test files, 169 it/test declarations, one it.each over 9 ids | grep over src/**/*.test.ts; travel.test.ts:38 |
| 13 docs files, 3,478 lines | wc -l docs/*.md |