# crew-movement Second milestone of the `ftl-shape` vertical slice. Adds interactive crew: click a crew member, click a destination tile, watch them walk. Built on top of the `skeleton` milestone's tile grid and ship layout. ## Design **Purpose.** Introduce interactive crew: 3 crew members sit on walkable tiles; player clicks a crew to select, clicks a destination tile to issue a move order; crew walks tile-by-tile along a shortest path. Foundation for combat-milestone features that require crew to be "at" a specific system. **In scope.** Walkable-tile model derived from existing ship rooms; 3 named placeholder crew with distinct tints; click-to-select / click-to-move input; BFS pathfinding on the walkable tile graph; tile-by-tile movement animation; selection feedback (1-px ring around selected crew). **Out of scope.** Doors / walls, crew skills, crew health, hover-tile preview, multi-select, queued orders, damage, enemy ship, combat, pause. **Chosen approach.** - **Walkability.** New file `tiles.go`: `func walkableTiles(s Ship) map[[2]int]bool` — union of every room's interior tiles, keyed by `[x, y]`. Computed once at ship construction, cached on `Game`. Naturally connected because rooms share tile edges in the skeleton layout. - **Crew model.** New file `crew.go`: - `type Crew struct { ID int; Name string; Initial rune; Color color.RGBA; TileX, TileY int; Path [][2]int; MoveT float64 }` — tile position is canonical; `Path` is the remaining waypoints (empty = idle); `MoveT` is progress in `[0,1)` toward the next waypoint. - `func NewStartingCrew() []Crew` — Alice in Pilot, Bob in Shields, Carol in Engines. - **Pathfinding.** `func bfsPath(walk map[[2]int]bool, from, to [2]int) [][2]int` — standard 4-neighbour BFS over the walkable set; returns waypoints excluding `from` and including `to`. Empty path means unreachable or same-tile. - **Input.** `Game.Update` reads the mouse: left-click → convert window pixels to virtual pixels (using `ebiten.WindowSize` + the known `Layout` scale) → tile coords. If the click lands on a crew member, select that crew (store its index in `selectedCrew`). Otherwise, if a crew is selected and the click tile is walkable, replace that crew's `Path` with a fresh BFS result and reset `MoveT`. - **Movement tick.** `func updateCrew(c *Crew, dt float64)` — if `Path` non-empty, advance `MoveT += dt / tileTime` (default `tileTime = 0.35s`). When `MoveT >= 1`, snap `TileX, TileY` to the next waypoint, pop it, reset `MoveT`. Rendered position is linearly interpolated between the current tile and the next waypoint. - **Rendering.** `drawCrew(screen *ebiten.Image, c Crew, selected bool)` added to `render.go`. Crew is a 12 px colored circle centred on the interpolated position, plus the initial letter via `ebitenutil.DebugPrintAt`. Selected crew gets a 1-px white outer ring. - **Game struct growth.** `Game` gains `walk map[[2]int]bool`, `crew []Crew`, `selectedCrew int` (−1 if none), `lastTime time.Time`. `Update` computes `dt` from wall-clock delta. - **Tile occupancy.** No enforcement this milestone — two crew may overlap visually. A short comment flags the spot where a "reserve next tile" check belongs later. **Tradeoffs that settled the decisions.** - Tile-position-as-canonical (vs pixel-position + tile-on-arrival): cleaner gameplay queries ("is crew in Weapons?"), simpler save-state later; cost is one interpolation step in the renderer. - `map[[2]int]bool` (vs `[][]bool` grid): tiny ship, map lookup is fast enough, keeps the code resolution-independent. Memory overhead is irrelevant at ~30 tiles. - Per-crew path ownership (vs central path manager): each crew is independent at this scope; no coordination needed. **Unknowns.** Mouse-pixel → virtual-pixel conversion relies on `ebiten.WindowSize` reporting the current window size (including after resize). Ebitengine's documented behaviour confirms this. **TDD: yes** — `bfsPath`, `walkableTiles`, and the `updateCrew` tick are pure functions with clear inputs/outputs and meaningful branching (same-tile, unreachable, multi-room path, boundary-crossing path, tile snap on `MoveT >= 1`). Input handling and rendering stay manual — they need the real mouse and display. ### Invariants - `ship.go`, `tiles.go`, and `crew.go` must not import Ebitengine. Pathfinding and walkability are pure. - All drawing uses virtual-resolution coordinates (640×360). Mouse input is converted once at the boundary; crew positions are stored in tile units only. - Crew canonical position is `(TileX, TileY)` — a tile coordinate. Render-time interpolation is never written back to `TileX / TileY`. - `bfsPath` is deterministic: same walkable set + same `from` / `to` → same path, via a stable neighbour ordering. - Single codebase builds unchanged for `GOOS=js GOARCH=wasm` and native targets. Mouse input works in the browser via the same API. ### Principles - YAGNI: no doors, walls, queued orders, hover preview, or cancel animation. If the combat milestone needs it, it ships there. - Tests cover pure functions (path, walkability, tick); rendering and input stay manual. - Fail fast: unreachable paths return empty, not "closest-so-far". Clicking outside the ship is a no-op, not a teleport. - Separate data from rendering and input — `crew.go` has no `ebiten.` imports; `render.go` never mutates crew state. - Deterministic core: same input sequence and timestep → identical game state.