diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000000000000000000000000000000000..f8d8a77c09ca70f90fb67a67812884ad8a96662c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,84 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +`ftl-shape` — an FTL-like game in Go + Ebitengine. Vertical-slice scope: one player ship, one combat encounter, pixel-art placeholders. Single binary targets both desktop (macOS/Linux/Windows) and browser (WASM). + +Hosting: the git remote `origin` is sourcehut (`git@git.srht.bigb.es:~bigbes/game-prototype-ftl`); `master` is the only branch. The Go module path is still `sourcecraft.dev/bigbes/ftl-shape` — leave it as is unless a move is decided explicitly. + +## Common commands + +All via `Justfile`: + +- `just run` — native, `go run .` +- `just build` — native binary to `bin/ftl-shape` +- `just build-wasm` — produces `web/main.wasm` and copies `wasm_exec.js` from `$(go env GOROOT)/lib/wasm/` (Go ≥1.24 path) +- `just serve` — `go run github.com/hajimehoshi/wasmserve@latest .` on `:8080`; auto-rebuilds on source change +- `just play-web` — same as `serve` plus auto-opens the browser (macOS `open`, Linux `xdg-open`) + +Tests: + +- `go test ./...` — all tests +- `go test -run TestBFSPath_throughShields ./...` — single test by name +- `go test -v -race ./...` — verbose + race detector + +Manual browser verify pattern (used in `skeleton`'s verify step): + +```sh +cd web && python3 -m http.server 8765 +# then load http://localhost:8765/index.html in a browser +``` + +## Architecture + +### Rendering pipeline + +- **Virtual resolution is 640×360.** `Game.Layout` in `main.go` returns these dimensions. The window opens at 1280×720 (2× scale); Ebitengine handles upscaling and mouse-coordinate translation. **Never draw in window-pixel units.** +- **Tile grid is 16 px.** All positions in `Ship` / `Room` (and future crew) are stored in tile units. Pixel conversion (`coord * TilePx`) happens **only in `render.go`**. +- **Placeholder-first.** No external asset files — everything is drawn with `vector.DrawFilledRect` and `ebitenutil.DebugPrintAt`. Don't introduce PNG/TTF loading until a milestone's Design explicitly requires it. + +### Data vs rendering + +- Only `main.go` and `render.go` may import `github.com/hajimehoshi/ebiten/...`. Everything else — `ship.go`, `crew.go`, `tiles.go`, `systems.go`, `hud.go`, `combat.go` — holds pure data + pure logic. This is a hard invariant per Design; check after any change with `grep -l hajimehoshi/ebiten *.go`. +- `render.go` reads game data, never mutates it. Role-to-color mapping lives in `render.go` so `ship.go` stays renderer-agnostic. +- `main.go` owns the `Game` struct and the Ebitengine loop (`Update`/`Draw`/`Layout`/`main`), including input handling and the restart (whole-struct `NewCombat()` replacement). +- Subsystems: + - `ship.go` — rooms (with per-room crew cell slots), `NewPlayerShip`, `NewEnemyShip`. + - `crew.go` — crew struct, starting roster, movement tick. + - `tiles.go` — walkability, BFS pathfinding, cell-slot lookup (`cellSet`). The occupancy check (`Game.cellOccupied`) lives in `main.go` because it needs the crew list. + - `systems.go` — systems and reactor power (`addPower`/`removePower`). + - `hud.go` — HUD geometry constants and `hudHitTest`; the `Hud*` constants are defined only here. + - `combat.go` — `Combat` state and `updateCombat`; the only randomness source is an injected `*rand.Rand`. All balance tunables are `const`s in this file. + +### Native + WASM single codebase + +No `//go:build` directives, no `*_js.go` / `*_darwin.go` platform-suffixed files. Any API used must work under `GOOS=js GOARCH=wasm` (confirmed for Ebitengine core, `inpututil`, `time`). If a future feature forces platform divergence, flag it as a Design-level scope change, not an inline hack. + +### Layout philosophy + +Single `package main` at the module root. No sub-packages, no interfaces, no ECS — YAGNI. Split into `internal/...` only when a file crosses ~300 lines or a clear subsystem boundary emerges. Per the project's Design, premature structure is a tax. + +## Task workflow + +Milestones are planned and executed via the **`up:` (ultrapack) plugin**. Each milestone lives in its own `docs/tasks/.md` with sections: `## Design` → `## Plan` → `## Verify` → `## Conclusion`. The workflow commands are `/up:udesign`, `/up:uplan`, `/up:uexecute`, `/up:uverify`, `/up:ureview` — run in that order. + +Current state — all four sections filled in and merged to `master`: + +- `docs/tasks/skeleton.md` — **done.** The canonical example of a completed task file. +- `docs/tasks/crew-movement.md` — **done.** Later extended outside the task workflow by `1b674aa` (crew restricted to per-room cell slots, one crew per slot). +- `docs/tasks/systems-power.md` — **done.** +- `docs/tasks/combat.md` — **done.** Its Conclusion leaves two unknowns open: game-feel of the balance constants (UK1) and legibility of the shield/evasion readouts at 640×360 (UK2). Both need a browser playthrough. +- `docs/tasks/summary-crew-movement-resume.md` — historical resume pointer from before crew-movement was executed; stale, do not act on it. + +Remaining milestone: `polish` — not designed yet. Known candidates deferred to it by earlier tasks: Pilot and MedBay effects (their HUD columns take power but do nothing), `text/v2` with a real font face instead of `ebitenutil.DebugPrintAt`. + +## Gotchas worth remembering + +- `go build ./...` from the repo root produces a bare `./ftl-shape` binary (not under `bin/`). It's in `.gitignore` alongside `bin/`, `web/main.wasm`, `web/wasm_exec.js`. +- `ebiten.CursorPosition()` already returns logical (post-`Layout`) coordinates — no manual window→virtual scaling needed for mouse input. If browser behavior ever disagrees, scale via `ebiten.WindowSize()`. +- WASM binary is ~12 MB; known, out of scope for the vertical slice. +- `go test ./...` links Ebitengine (package `main`), so it needs a GUI session: it passes in a regular macOS terminal, but has panicked in GLFW `init()` in a session with no Window Server. The macOS `CVDisplayLink … deprecated` cgo warnings are noise. +- `GOOS=js GOARCH=wasm go vet .` is the quick check that nothing broke the WASM build. +- `render.go` (~320 lines) has crossed the ~300-line split threshold above; a candidate for splitting when `polish` touches it.