Files
metro-puzzle-jp/doc/roadmap.md
claude ef6cfaccc8
All checks were successful
release / build-release (push) Successful in 1m42s
Skärmdumpar på osynlig skärm och deterministiska bildrutor
- tools/shots/headless.go: startar en egen headless Wayfire (wlroots
  headless-backend + Xwayland) och kör spelet där, så att inga fönster syns
  eller tar fokus från det man gör på datorn; snäll avstängning och
  städning av sockets; METRO_HEADLESS=0 använder skärmen
- skriptläget ritar scenen synkront vid shot så att bilden alltid speglar
  exakt den uppdateringen oavsett FPS (två körningar ger identiska PNG:er)
- README/roadmap: hur den osynliga skärmen fungerar och används manuellt

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 11:40:49 +02:00

484 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Roadmap och teknikval
Ordningen nedan är den ordning saker ska byggas i. Varje milstolpe är ett
körbart, testat steg som går att visa upp. Teknikvalen står först eftersom
de styr hur milstolparna byggs.
## Teknikval
| Område | Val | Varför |
|--------|-----|--------|
| Språk | **Go 1.27** | En enda installation ger kompilator, pakethanterare, testramverk och formatterare. Snabba byggen. Björn använder redan Go i andra projekt. |
| Spelbibliotek | **Ebitengine v2.10** (`github.com/hajimehoshi/ebiten/v2`) | Byggt för 2D-pixelspel: intern upplösning + heltalsskalning, nearest-neighbour-sampling, inbyggt ljud (OGG/WAV/MP3), tangentbord/gamepad/mus, textrendering med TTF. **Ren Go på Windows** → korsbygge från Linux utan C-verktyg. |
| Tester | Standardbiblioteket `testing` | Inget att installera. `go test ./...` kör allt. |
| Speldata | **JSON** i `resources/data/` | Läsbart, diffbart i git, `encoding/json` i standardbiblioteket. Byt till TOML bara om kommentarer blir ett verkligt behov. |
| Bygg | **`go run ./tools/build`** | Ett Go-program i stället för make/bash/PowerShell. Samma kommando på alla plattformar. Stämplar version och läge, kopierar resurser. |
| IDE | **VS Code + tillägget `golang.go`** | `.vscode/tasks.json` (bygg/test) och `.vscode/launch.json` (F5 med delve). Tillägget installerar delve åt dig. |
| Font | **PixelMplus** 10/12 px via `ebiten/v2/text/v2` | Pixelfont med kana **och kanji** (JIS 1+2), M+ FONT LICENSE. Valdes framför Misaki (8 px) för läsbarhetens skull – 10 px kanji går att läsa vid 3–4× skala. |
| Ljud | `ebiten/v2/audio` med OGG Vorbis (musik, jinglar) och WAV (korta effekter) | Ingår i Ebitengine, inga externa bibliotek. |
| Slump | `math/rand/v2` (PCG) genom `internal/rng` | Seedbart, deterministiskt, del-seeds per kontext. |
| Sparfiler | JSON i `os.UserConfigDir()/metro-puzzle-jp/` | Rätt plats på både Linux (`~/.config`) och Windows (`%AppData%`). |
| Versionshantering | Git, lokalt nu, Gitea senare | Enligt playbooken; CI-release när projektet är redo att delas. |
### Varför inte …
- **Godot:** utmärkt för pixel art, men bygger inte "i VS Code" på samma
sätt, kräver editor + exportmallar, och C#-spåret drar in .NET. Hade
passat om kravet varit "snabbast till prototyp", inte "få byggverktyg".
- **Rust + macroquad/Bevy:** bra, men Windows-korsbygge kräver MinGW eller
MSVC, och Bevy har långa byggtider. Rust-toolchain är en till sak att hålla
i gång på två OS.
- **C++ + raylib/SDL:** CMake + kompilator per plattform, pakethantering
för hand. Precis det "många byggverktyg"-problem vi vill undvika.
- **Love2D (Lua):** inget byggsteg alls, men distribution som en binär
kräver att man "fuserar" ihop filer, och det saknas ett naturligt
testramverk.
### Regler för Windows-portabilitet (gäller från dag 1)
1. Bygg sökvägar med `filepath.Join`, aldrig med `/` i strängar.
2. Inga Linux-specifika syscalls eller `/proc`-läsningar. Behövs OS-specifik
kod: lägg den i `*_linux.go` / `*_windows.go`.
3. Sparfiler och konfiguration via `os.UserConfigDir()`, aldrig `~` i
strängar.
4. Resurser hittas relativt binären (`internal/res`), aldrig relativt
arbetskatalogen i release.
5. Textfiler skrivs med `\n`; läsning tål `\r\n`.
6. Varje milstolpe avslutas med `go run ./tools/build -mode release -os windows`
så att Windows-bygget aldrig hinner gå sönder obemärkt.
7. Inget `cgo` i vår egen kod.
### Katalogstruktur
```
metro-puzzle-jp/
├── cmd/metro-puzzle-jp/ main.go – flaggor, fönster, startar spelet
├── internal/
│ ├── buildinfo/ version + läge (sätts av byggverktyget)
│ ├── res/ hittar och läser resources/ (ren Go)
│ ├── rng/ seedad slump (ren Go)
│ ├── level/ banformat, validering, BFS (ren Go)
│ ├── sim/ rutbaserad rörelse, kollision, kamera, banomgång (Run), sällskap (Party), dimma (Fog) – ren Go
│ ├── lang/ ordbok, tokenisering, kunskap, delvis översättning, engelska-andel, hjälptid – ren Go
│ ├── minigame/ påfyllning, biljettköp, trängsel, tågresa – seedade, ren Go
│ ├── progress/ XP, skillträd, items, banresultat, upplåsning – ren Go
│ ├── save/ sparfil: versionerad JSON, migrering, atomisk skrivning
│ ├── game/ Ebitengine-loop, scener, input, rendering, skriptläge ← får använda Ebitengine
│ ├── archtest/ beroenderegler
│ └── … kommande: lang/, nav/, score/, save/, minigame/, ui/, audio/
├── tools/build/ byggverktyget
├── tools/genart/ genererar placeholder-grafik (tiles.png, player.png)
├── tools/shots/ tar dokumentationens skärmdumpar till doc/img/
├── resources/ kopieras bredvid binären vid bygge
│ ├── sprites/ bilder, tilesets, UI
│ ├── sound/ OGG/WAV
│ ├── text/ sv.json, en.json, jp-dictionary.json, fonts/PixelMplus*.ttf
│ └── data/ levels/, items.json, skills.json
├── doc/ denna dokumentation (doc/img/ = skärmdumpar från tools/shots)
├── .gitea/workflows/ CI: test + release-zippar för linux/windows på brasse-linux01-runnern
├── .vscode/ tasks.json, launch.json
└── build/ (gitignorerad) debug/ och release/<os>-<arch>/
```
### Byggflöde
| Vad | Kommando | Resultat |
|-----|----------|----------|
| Debug (standard, Ctrl+Shift+B) | `go run ./tools/build` | `build/debug/metro-puzzle-jp` + `resources/`; inga optimeringar, full debuginfo |
| Debug med debugger (F5) | VS Code "Debug (dlv)" | Kompilerar och startar under delve med cwd = repots rot |
| Release Linux | `go run ./tools/build -mode release -os linux -arch amd64` | `build/release/linux-amd64/` – strippad, `-trimpath` |
| Release Windows | `go run ./tools/build -mode release -os windows -arch amd64` | `build/release/windows-amd64/metro-puzzle-jp.exe` – GUI-subsystem, inget konsolfönster |
| Tester | `go test ./...` | |
Versionen i binären kommer från `git describe --tags --always --dirty`,
så en tagg `v0.1.0` ger `metro-puzzle-jp v0.1.0` i `-version`.
### Skärmdumpar till dokumentationen
README och wikin visar bilder från `doc/img/`. De tas av spelet självt i
skriptläge så att de alltid är pixelexakta och går att återskapa:
```bash
go run ./tools/genart # bara om placeholder-grafiken ska genereras om
go run ./tools/shots # bygger debug, kör spelet med -script, skriver doc/img/*.png
```
**Regel:** när sprites, tileset eller UI ändras – kör `tools/shots` och
committa `doc/img/` i samma commit, så visar README och wiki alltid aktuell
grafik. Nya vyer läggs till i listan i `tools/shots/main.go`.
**Osynlig skärm:** på Linux startar `tools/shots` en egen headless
wlroots-kompositor (Wayfire med `WLR_BACKENDS=headless` och Xwayland) och
kör spelet där, så att skärmdumparna varken syns eller tar fokus från det
man själv gör på datorn. Bilderna är pixelidentiska med riktiga skärmen
(mjukvarurendering, nearest-filter). `METRO_HEADLESS=0` stänger av det;
saknas `wayfire` används skärmen. Samma knep för manuella körningar:
`DISPLAY=:N WAYLAND_DISPLAY=wayland-N ./metro-puzzle-jp …` mot en startad
headless-instans.
### CI
`.gitea/workflows/release.yaml` kör `go vet` + `go test`, bygger
`linux-amd64` och `windows-amd64` och lägger zipparna på den rullande
releasen `latest` (en tagg `vX.Y.Z` ger en versionerad release). Jobbet körs
på **brasse-linux01-runnern** (label `linux-amd64`), aldrig på Pi5:n.
## Milstolpar
### M0 – Skelett och byggkedja ✓ (klar 2026-10-23)
- Repo, Go-modul, Ebitengine-fönster 320×180 med heltalsskalning.
- `internal/res`, `internal/rng`, `internal/level` med tester.
- Byggverktyg, VS Code-tasks och launch-konfigurationer.
- Första banan (`001-flygplatsen`) renderad som färgade rutor med
placeholder-sprite.
- Verifierat: debug-bygge, Linux-release, Windows-korsbygge, smoke-test
med fönster, alla tester gröna.
### M1 – Rörelse, kollision och kamera ✓ (klar 2026-10-23)
- **Mål:** gå runt i en labyrint som är större än skärmen.
- Rutbaserad rörelse i 4 riktningar (pilar/WASD + gamepad d-pad/spak), mjukt
interpolerad, riktningsbyte vid rutgränser; kollision mot väggar; kamera
som följer spelaren och klampas till kartan (centrerar små kartor).
- Scen-system: meny (banlista) → bana → resultat (rätt/fel tåg, R = spela om).
- Tileset (`tiles.png`) och spelarsprite med gå-animation i fyra riktningar
(`player.png`), genererade av `tools/genart` tills riktig pixel art finns.
- Större testbana `002-shinjuku` (33×21 rutor) som bevisar kamerascrollen.
- Skriptläge (`-script`) och `tools/shots` som tar dokumentationens
skärmdumpar deterministiskt.
- **Teknik:** `internal/sim` (Mover, Camera – ren Go med tester),
`internal/game` (scener, input, assets), Ebitengine `inpututil`/`vector`.
- **Avsteg:** resultatskärmen visar redan rätt/fel tåg eftersom perrongen är
det naturliga slutet på en bana; tidtabell, spärrar och väntan på tåg
kommer i M2 som planerat.
### M2 – Stationen: tåg, spärrar och vinst/förlust ✓ (klar 2026-10-23)
- **Mål:** bana 001 går att klara och misslyckas.
- Tåg med tidtabell per perrong (`schedule`: första ankomst, intervall,
uppehåll). Tågvagnen ritas bortom perrongkanten när tåget är inne; HUD:en
visar spår, linje, resmål och "avgår om / nästa tåg om".
- Ombordstigning är ett aktivt val: stå på perrongen och tryck Enter medan
tåget är inne. Rätt tåg = vinst, fel tåg = förlust, resultatskärm med tid,
steg, spärrpassager och kortsaldo.
- Spärrar kräver kort med saldo (startsaldo 2000 ¥, avgift per bana, dras
när man går in i en spärr från en vanlig ruta). Tomt kort stoppas med
meddelande. Riktig ekonomi och påfyllning kommer i M6.
- Skyltar har text (`signs` i bandatan, sv/en/ja) som visas när man står
stilla på skyltrutan. Japanskan ritas först i M3.
- **Regel för banor:** alla perronger ska ligga innanför spärrar.
`level.UngatedPlatforms` kontrolleras i det datadrivna testet; bana 001
byggdes om när regeln avslöjade två genvägar.
- **Teknik:** `internal/sim.Run` (klocka, kort, spärrlogik, ombordstigning –
ren Go), `level.Schedule`, `level.Path` (BFS), skriptkommandot `waittrain`.
- **Klar när:** `TestBotWinsLevel001` låter en bot gå kortaste vägen,
passera spärren, vänta in tåget och kliva på – headless. ✓
### M3 – Japanska och skyltsystemet ✓ (klar 2026-10-23)
- **Mål:** spelets identitet – skyltar på japanska som man lär sig läsa.
- **Font:** PixelMplus 10/12 px (M+ FONT LICENSE, i repot under
`resources/text/fonts/`), ritad med `text/v2` på heltalspositioner. All
text i spelet går via den; debugfonten är borta.
- **Ordbok** (`resources/text/jp-dictionary.json`, 60 ord): japanska →
kana, romaji, sv, en, tier. Ett test kräver att all japanska i banorna
(skyltar, namn, resmål, linjer) finns i ordboken.
- **Kunskapsmodell** (`lang.Knowledge`): kända ord + kana-läsning.
Nivå 0–5 = kända ord / 4. Kana låses upp efter tre kända kana-ord.
- **Rendering** (`lang.Render`): skylten tokeniseras med längsta matchning;
kända ord markeras i gult i originalet och översätts i raden under,
okända står kvar på japanska i grått, kana-ord som kan läsas men inte
förstås visas som romaji i grönt. Avgångstavlan på perrongen är en skylt
som alla andra.
- **Engelska efter svårighet** (`lang.ShowEnglish`): andel skyltar med
engelsk rad 100/60/30/10/0 % för svårighet 1–5, deterministiskt per
bana + position.
- **Fråga någon om hjälp** (H): tar `lang.HelpSeconds(nivå)` sekunder
(6 s på nivå 0, ned till 2 s), avbryts om man går, avslöjar hela skylten
och lär spelaren det första okända ordet på den. Notis "Nytt ord".
- **Hintar efter nivå:** från nivå 1 visar HUD:en resmålet på japanska (att
matcha mot skyltarna), från nivå 2 visas läsningen efter kända ord.
- Kunskapen följer med mellan banor under en session (`Game.Know`); att
spara den på disk är M8. Flaggorna `-know ord,ord`, `-points N` och
`-seed N` ger förkunskap och determinism för tester och skärmdumpar.
- **Inlärning utöver hjälp** (Björns regler, tillägg samma dag):
- **Bra resultat lär ut resmålet.** Vinst ger 1–3 stjärnor efter tid mot
par (`parSec` i bandatan, annars kortaste vägen × 0,5 s + 20 s); från
två stjärnor lärs resmålets ord in.
- **Läsning ger framsteg.** Varje gång spelaren stannar på en skylt eller
perrong har varje okänt ord 35 % chans (seedat per bana, skylt och
besök) att få +1 framsteg; vid 3 framsteg kan man ordet.
- **Språkpoäng** (1 per stjärna) läggs på valfria ord i **ordlistan**
(L i menyn och på resultatskärmen): hela ordboken sorterad på tier,
kända ord, framsteg och kostnad (= tier). Blir ett riktigt träd i M8.
- **Teknik:** `internal/lang` (ren Go, 11 tester + datatester), `sim.Run`
(hjälp-tillstånd, avslöjade skyltar), `internal/game/text.go`
(pixelbaserad radbrytning av färgade textsegment).
- **Klar när:** samma skylt visar olika mycket beroende på kunskap, och det
är testat (`TestRenderPartialTranslation`, `TestKanaReadingWithoutMeaning`,
`TestAskHelpRevealsSignAndTeachesWord`); bana 001 spelas med japanska
skyltar. ✓
### M4 – Lämmeltåget och mätarna ✓ (klar 2026-10-23)
- **Mål:** sällskapet följer med och ställer till det.
- Följare som går i rad efter spelaren längs ledarens spår (`party` i
bandatan: 001 har två, 002 tre; max fem). Egna färgtoner tills de får
egna sprites.
- Mätare mat/dryck/toa per person (100 → 0 på ~10/7/8 min). Under 25
uppstår ett behov av sig självt.
- **Händelser** var `45 s / antal i sällskapet` (± 30 %), slumpade från
omgångens seed: någon måste på toa, blir törstig, hungrig eller
distraherad. Fler i lämmeltåget = oftare, testat.
- Behov saktar ned hela sällskapet (×0,8; distraktion ×0,5 i 4 s; tom
mätare ×0,5) och syns som markering över huvudet och i HUD:en.
- **Toalett** (W, Enter: 3 s per person) och **kiosk** (K, Enter: 150 ¥
per person från kortet) återställer mätarna och tar bort behoven.
Mätarpanel uppe till höger.
- **Teknik:** `sim.Party` (ren Go, 7 tester), `sim.Run` (fart, faciliteter),
nya rutor `W`/`K` i `level` och `tools/genart`; banor med sällskap måste ha
minst en toalett och en kiosk (testas).
- **Klar när:** fler i gruppen ger mätbart fler händelser i test
(`TestMoreMembersMoreEvents`), händelser är seedade
(`TestEventsAreSeeded`) och går att lösa i spelet (`TestToiletAndKioskInRun`). ✓
- **Skjutet till M7:** mätarnas värde vid målgång påverkar poängen; debuffar
vid tomma mätare.
### M5 – Minikarta och utforskning ✓ (klar 2026-10-24)
- **Mål:** orientering utan att avslöja labyrinten.
- **Dimma** (`sim.Fog`): när spelaren når en ruta avslöjas allt inom tre
*steg* via passerbara rutor plus de väggar som gränsar till dem – man ser
längs gången, inte genom väggen. Nollställs per omgång (ny `Run`).
- **Riktmärken** syns alltid: perrongerna (avgångarna), starten och
spärrarna. Allt annat (kiosker, toaletter, skyltar, automater, gångar)
ritas först när det setts.
- **Minikartan** ritas i ett hörn som varken täcker inforutan eller
spelaren, 1–3 px per ruta beroende på banans storlek. Under kartan står
en avgångslista per spår: `2 ●6s` = tåget inne, avgår om 6 s;
`1 ○14s` = kommer om 14 s. Perrong med tåg inne lyser vit. Spelaren
blinkar vit, följarna i sina färgtoner. **M** döljer/visar kartan.
- Resultatskärmen visar **utforskad andel** av banan (används av poängen i
M7) samt antal försörjda i sällskapet.
- **Teknik:** `sim.Fog` (ren Go, 3 tester), `internal/game/minimap.go`.
- **Klar när:** upptäckta rutor markeras och nollställs per bana, avgångar
syns från start – `TestFogRevealsAlongCorridorsNotThroughWalls`,
`TestLandmarksAlwaysVisible`, `TestFogGrowsWithMovementAndResetsPerRun`. ✓
### M6 – Kort, biljetter, telefon och minispel ✓ (klar 2026-10-24)
- **Mål:** resurshantering och de seedade minispelen.
- **Plånbok** (`sim.Wallet`, följer med mellan banor): IC-kort (2000 ¥),
kontanter (5000 ¥), ev. pappersbiljett, kortet i telefonen + batteri,
totalt spenderat (poäng i M7). Spärren drar från kortet; kiosken också.
- **Påfyllning** (automaten T): minispel – hitta チャージ bland japanska
knappar i seedad ordning (fel knapp = straffsekund), välj belopp, håll
Enter för att mata in sedlar. Kontanter → kort.
- **Biljettbanor** (`ticketRequired`, bana 002): spärren kräver
pappersbiljett i stället för kort. Automaten visar en taxetavla med
resmål på japanska i seedad ordning – köp rätt resmål kontant. Tåget
släpper bara på den vars biljett gäller dit; biljetten förbrukas.
- **Kortet i telefonen** (P i menyn, `-phone`): fart ×1,1, men 15 %
seedad chans per spärr att behöva skanna igen (1,5 s stillastående).
Batteriet laddar ur på 15 min; dött = kortet oanvändbart → köp biljett.
- **Fullt tåg** (`crowded`, 002:s rätta tåg): tidsminispel – tryck Enter
när markören är i den gröna zonen, två försök, annars går tåget
(räknas som missat tåg, M7).
- **Tågresan** (alla banor): stationerna passerar med 次は-utrop; kliv av
med Enter vid resmålet. Fel station eller att åka förbi förlorar banan;
på fel tåg finns resmålet inte på linjen. Namnen renderas genom
kunskapen, med romaji efter svårighetens engelska-andel. `rideStops`
styr längden (3 standard, 002 har 4).
- **Teknik:** `internal/minigame` (ren Go, gemensamt `Game`-gränssnitt,
all slump från del-seeds, 5 tester), `sim.Run.StepMini` (klockan och
sällskapet tickar, rörelsen pausar), vyer i `internal/game/minigame_view.go`,
skriptkommandon `hold N` och `waitstop`.
- **Klar när:** varje minispel är deterministiskt för en seed (test) och
spelbart; `TestTicketLevelFlow` spelar hela 002 headless: fel biljett
stoppas, rätt biljett, spärr, trängsel, resa, vinst. ✓
### M7 – Svårighet, tid, poäng och debuffar ✓ (klar 2026-10-24)
- **Mål:** banor som kan misslyckas och poäng att jaga.
- Tidsgräns och maxantal missade tåg på svåra banor; lätta banor
obegränsade.
- Poängformel: tid mot par, pengar spenderade, sällskapets mätare vid
målgång, utforskat, avdrag för missade tåg och omskanningar;
navigeringsläge ger multiplikator i M9.
- Stats (speed, törst, fokus, toalett, tur) som multiplikatorer och
**debuffar** som påverkar dem: onda fötter (många steg), trötthet (lång
bana), hunger/irritation (tom matmätare), matförgiftning (seedad chans
vid kioskköp, snabbare toamätare). De temporära debuffarna från
sidouppdrag (träningsvärk, skoskav, tungt köp, proppmätt, svettig) finns
i systemet med varaktighet 1–4 banor; sidouppdragen som ger dem kommer i
M10.
- **Svårighetsinställningar per bana** (Björns tillägg 2026-10-24):
- **Egna kort** (`individualCards`): varje person i sällskapet har sitt
eget tågkort; spärren kräver att alla har pengar, biljettbanor att alla
har biljett. Påfyllningsautomaten frågar vems kort.
- **Sittplatsval** (`seatSelection`, kräver biljettbana): efter resmålet
väljer man platser i vagnen (fönster/gång, vänster/höger, bredvid
någon) efter sällskapets seedade önskemål. Missnöjda i sällskapet blir
sura: mätaravdrag och poängavdrag.
- Våningar (`floors`) är också en svårighetsvariabel men byggs i M9a.
- **Teknik:** `sim/stats.go` (Stats, Debuff, Profile – ren Go),
`Run.Score()`, `level` får `maxMissedTrains`, `individualCards`,
`seatSelection`; `minigame.TopUp` får personval, `minigame.Ticket` får
sittplatssteg.
- **Poängformeln som byggdes:** tid 500 × min(1, par/tid) + pengar
(300 − spenderat/10) + mätare (200 × sällskapets medel) + utforskat
(100 × andel) − avdrag (50/missat tåg, 10/omskanning, 30/missnöjd).
- **Bana 003 Shibuya** (svårighet 3, fyra i sällskapet): biljettbana med
egna kort, sittplatsval, fullt tåg, fem stopp, 6 min tidsgräns, max ett
missat tåg. Bana 002 fick tidsgräns, maxantal missade tåg och
sittplatsval. Generatorn är nu spec-driven (JSON) så fler banor går fort.
- **Klar när:** poängformeln, tidsgränsen, debuffarna och de två
inställningarna är testade (`TestScoreBreakdown`,
`TestTimeLimitAndMissedTrainsLose`, `TestDebuffsAffectRunAndAreEarned`,
`TestIndividualCardsAtGateAndMachine`, `TestLevel003Flow`,
`TestSeatSelectionPreferences`); bana 003 använder dem. ✓
### M7b – Tåg som kommer och går ✓ (klar 2026-10-24)
- **Mål:** tågen ska synas, höras och kännas – man ska förstå när de kommer
och går, särskilt när missade tåg kostar (M7).
- **Tågfaser** ur tidtabellen (`level.Schedule.Phase`): på väg in
(approaching, 5 s före), kör in (arriving, 1,5 s), står inne (dwelling,
dörrar öppna), strax avgång (closing, sista 3 s, dörrsignal), kör ut
(leaving, 1,5 s), borta. Man kan bara kliva på när dörrarna är öppna.
- **Räls** ritas längs perrongkanten (egen ruta i `tools/genart`); tågen
rullar in längs rälsen i tre vagnar, står, och accelererar ut – inte
"dyker upp/försvinner". Vägrutor för bussar kommer med bussarna (M10).
- **Ankomstindikator nära rälsen:** står man inom två rutor från perrongen
när tåget är på väg in blinkar lampor vid kanten, strålkastare syns från
det håll tåget kommer och inforutan säger "tåg på väg in".
- **Vind:** dammpartiklar längs perrongen när tåget kör in och ut.
- **Ljud:** ljudsystemet (`ebiten/v2/audio`) införs här med två placeholder-
jinglar genererade av `tools/gensound` (pentatonisk avgångsmelodi när
tåget är på väg in, dörrsignal strax före avgång), med volym efter avstånd
till perrongen och `-mute`. Riktiga inspelningar/kompositioner i M12.
- **Teknik:** faser i `level` (ren Go, testade), `sim.Run.NearTrack`,
partiklar/tweening/ljud i `internal/game`, skriptkommandot `waitphase`.
- **Klar när:** faserna beräknas rätt ur tidtabellen (testat:
`TestSchedulePhases`, `TestNearTrackAndPhases`), indikatorn syns bara
nära rälsen, tåget rullar in/ut och melodierna spelas i rätt fas. ✓
- **Konsekvens för banorna:** uppehållet måste vara minst 5,5 s och tiden
borta minst 7,5 s (valideras), så att faserna får plats.
### M8 – Progression, items och sparfiler ✓ (klar 2026-10-24)
- **Mål:** något att låsa upp och en anledning att fortsätta.
- **XP och nivåer:** en klarad bana ger `poäng/10 + 25 × stjärnor` XP;
nivå n+1 nås vid 150·n(n+1)/2 XP (150, 450, 900 …). Varje level-up ger
en **skillpoäng**.
- **Skillträd** (`resources/data/skills.json`, 12 noder i fyra grenar:
språk, kropp, resenär, utforskare): kana, fokus, hjälp, fart, törst, toa,
tur, sikt, landmärken. Krav och kostnad valideras; effekterna är
stats-multiplikatorer plus flaggor. Språkpoängen på ord (M3) finns kvar
som egen valuta.
- **Items** (`resources/data/items.json`, 25 st: hattar, masker, kläder,
skor, prylar, skins) med buffar, färgton på spelaren och genvägar:
JR Pass (gratis spärrar), kartor (hela minikartan på en bana), powerbank
(oändligt batteri). Ett item per plats (huvud, ansikte, kropp, fötter,
väska). Köps kontant i **kioskens butik** (B) – banans sortiment eller ett
seedat urval. Designregeln "knappt 33 % per omgång" är ett test:
billigaste tredjedelen kostar mer än startkassan.
- **Bankartan** i spelbrädesstil ersätter menyn: huvudbanorna på en linje,
sidospår ovanför, låsta grå, klarade med stjärnor; nästa huvudbana öppnas
när föregående är klarad, sidospår när föräldern är. Ny sidobana
**002b Kabukicho** belönar skinnet Kimono och en skillpoäng.
- **Sparfil** (`internal/save`): versionerad JSON med kunskap, plånbok,
debuffar, progression och pågående bana + seed, atomisk skrivning,
migrering av äldre filer. `~/.config/metro-puzzle-jp/save.json` på Linux,
`%AppData%` på Windows. Autospar vid banstart (seeden), efter banan och
vid köp/upplåsningar. `⟳` på kartan betyder att banan återupptas med samma
slump. Flaggor `-save PATH`, `-nosave`.
- **Teknik:** `internal/progress` (ren Go, 6 tester), `internal/save`
(3 tester), `sim.RunOptions` får Extra/FogRadius/Landmarks/FreeGates/
RevealAll/InfiniteBattery, luck påverkar trängselzonen, nya scener
karta/skills/garderob och butiken i `internal/game`.
- **Klar när:** sparfil round-trip-test grön; "< 33 %" kontrolleras mot
itemlistan; skillkrav, upplåsningar och belöningar testade. ✓
### M9a – Flera våningar (Björns tillägg 2026-10-24)
- **Mål:** stationer i flera plan – ännu en svårighetsvariabel.
- Banformatet får **flera rutnät** (`floors`), ett per våning, med
**trappor** (och rulltrappor/hissar) som kopplar en ruta på en våning
till en ruta på en annan. Start kan ligga på en trappa upp eller ner:
under mark, ovan mark eller marknivå.
- Kamera, dimma, minikarta (en flik per våning eller stapel) och BFS-vägar
(`level.Path`, lösbarhetstestet, botarna) blir våningsmedvetna; skyltar
kan peka "↑ 2F / ↓ B1".
- **Teknik:** `level.Level.Floors []Grid` med `Stairs []Link`, `Point`
får `Z`; `sim.Mover` byter våning vid trappa; `tools/genart` får
trappruta; generatorn placerar trappor som flaskhalsar.
- **Klar när:** en bana med två våningar går att klara av boten headless,
minikartan visar rätt våning, och alla gamla banor fungerar oförändrat
(en våning).
### M9b – Folk, reklam och ljudröra (Björns tillägg 2026-10-24)
- **Mål:** stationen lever och överstimulerar – tre nya svårighetsvariabler.
- **Folk (NPC:er):** passagerare som går på och av tågen när dörrarna är
öppna, rör sig mellan kiosker, toaletter och utgångar, och in och ut från
stationen. De tar plats i rutnätet: man måste runda dem, och att gå in i
någon saktar ned (`crowd`-nivå per bana styr hur många).
- **Vänster-/högertrafik:** gångar och trappor kan markeras med en sida
(pilar i golvet); går man på fel sida möter man strömmen och bromsas.
- **Bredare gångar:** generatorn kan göra gångar två rutor breda där det
ska gå att passera folk; följarna går då i bredd när det får plats.
- **Visuell röra** (`clutter`-nivå): dekorationslager med reklam, affischer
och blinkande skyltar på väggarna, lockskyltar som liknar riktiga.
- **Ljudröra** (`noise`-nivå): japanskt prat, utrop och reklam i bakgrunden
som konkurrerar med melodierna; på de svåraste nivåerna överlappar de.
- **Teknik:** `sim.NPC` med enkla mål och BFS-vägar (ren Go, seedade),
dekorationslager i banformatet, `tools/genart` för folk/reklam,
`tools/gensound` för brusplaceholders; generatorn får bredd och sidor.
- **Klar när:** en bana med folk spelas av boten headless utan att fastna,
fler NPC:er ger mätbart längre tid, och röra/ljud styrs per bana.
### M9 – Navigeringslägen: lista och skyltläge
- **Mål:** de två sätten att spela story.
- Listläge: nästa tåg visas.
- Skyltläge: 1–4 giltiga vägar, mer poäng, förlust om man åker i fel
riktning / för långt bort.
- **Teknik:** `internal/nav` (graf över stationer/linjer, giltiga vägar,
riktningskontroll – ren Go).
### M10 – Story-innehåll och flerbytesbanor
- **Mål:** hela resan från flygplats till flygplats.
- Alla huvudbanor och sidobanor; banor med flera stopp och byten; buss (med
vägrutor och hållplatser); stationer i flera våningar (M9a), folk och röra
(M9b) och svårighetsinställningarna från M7 i stigande kombinationer.
- Slutanimation (karaktärerna flyger iväg, items och poäng visas) och
eftertexter.
- Överstimulerande UI/banor som extra svårighet.
- **Teknik:** baneditor-stöd behövs troligen här: ett `tools/levelcheck`
som ritar banan som bild och rapporterar onåbara rutor.
### M11 – Oändligt läge och New Game+
- Slumpade mål från seed, poäng per mål, egen poänglista. NG+ behåller skills.
- Låses upp när storyn är klar.
### M12 – Ljud, polish och release
- Riktiga avgångsmelodier/ankomstjinglar (ersätter placeholders från M7b),
utrop, effekter, musik; mixning och volyminställningar.
- Inställningar (ljudnivåer, språk sv/en, skala, tangenter).
- Windows-test på riktig maskin, ikon i exe:n, eventuell Gitea-CI med
releaser enligt playbooken.
## Prioritering
- **Måste för ett spelbart spel:** M1–M3, M6 (åtminstone kort/spärr), M7.
- **Gör spelet till *det här* spelet:** M3, M4, M9.
- **Kan skjutas utan att något går sönder:** M5, M11, delar av M8 (skill
tree), överstimulerande UI i M10.
- Windows-bygget körs vid varje milstolpe men testas på riktig Windows
först i M12.