All checks were successful
release / build-release (push) Successful in 1m33s
Folk på stationen (NPC:er som vandrar, kliver av tåg och går mot utgången, blockerar spelaren men inte varandra), vänster-/högerfiler som bromsar på fel sida, bredare gångar, reklamaffischer (clutter), japanskt sorl i loop (noise), vagnval med kvinnovagn (stort avdrag med man i sällskapet, tidsstyrd), kömarkeringar på perrongen, trängsel per vagn (bonus för tom vagn, svettig-debuff och svårare resa i full vagn, släpp förbi passagerare) och att folk kliver av innan man får gå på. Ny bana 005 Ueno. Skärmdumpsverktyget kan filtrera på bana/skript. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
527 lines
32 KiB
Markdown
527 lines
32 KiB
Markdown
# 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 [ord…] # bygger debug, kör spelet med -script, skriver doc/img/*.png (ord filtrerar på bana/skript)
|
||
```
|
||
|
||
**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 ✓ (klar 2026-10-24)
|
||
|
||
- **Mål:** stationer i flera plan – ännu en svårighetsvariabel.
|
||
- **Banformat:** `floors: [{name, grid}, …]` med lika stora rutnät; gamla
|
||
banor med `grid` läses som en våning ("1F"). Rutkoordinaten `Point` fick
|
||
`Z`. **Trappor** är rutor: `U` (upp) på en våning kräver `D` (ner) på
|
||
samma x,y en våning upp och tvärtom (valideras). Går man in på en trappa
|
||
hamnar man i andra änden i samma steg. Skyltar har `z`.
|
||
- **Logik:** `Neighbors` ger grannarna inkl. trappans andra ände, så
|
||
`Path`, lösbarhetstestet, spärregeln och `Unreachable` fungerar över
|
||
våningar. Dimman är per våning och avslöjar trappans andra ände. Man
|
||
ser/hör bara tågen på sin egen våning.
|
||
- **Spel:** bara spelarens våning ritas (följare på andra våningar syns
|
||
inte förrän de kommer upp), HUD och minikartan visar våningens namn,
|
||
trappor har egen grafik och färg på minikartan.
|
||
- **Bana 004 Tokyo** (svårighet 3, tre i sällskapet): start under mark på
|
||
B1 (Marunouchi-linjen), trappan upp till 1F där perronger, spärrar,
|
||
automat, kiosk och toalett finns. Generatorn gör våningar: labyrint per
|
||
våning, trappor på gemensamma öppna rutor nära mitten, start på en våning
|
||
och perronger på en annan.
|
||
- **Teknik:** `level` (Storey, Point.Z, Neighbors, FloorAt), `sim.Run`
|
||
(trappbyte, FloorChanges), `sim.Fog` 3D, spelet ritar per våning;
|
||
testbotarna och `tools/shots` förstår trappor.
|
||
- **Klar när:** en bana med två våningar går att klara av boten headless
|
||
(`TestStairsMoveBetweenFloors`, `TestFloorsStairsAndPaths`), minikartan
|
||
visar rätt våning, och alla gamla banor fungerar oförändrat (en våning). ✓
|
||
|
||
### M9b – Folk, reklam och ljudröra ✓ (klar 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.
|
||
- **Vagnar** (Björns tillägg 2026-10-24): tåg har `cars` vagnar; när tåget
|
||
står inne väljer man vagn på perrongen (kömarkeringar visar var man
|
||
ställer sig). **Folk kliver av först** – trycker man för tidigt blir det
|
||
avdrag. Varje vagn har en seedad **trängselnivå** (lite/mellan/mycket):
|
||
lite folk ger poängbonus, mycket folk gör trängselminispelet och resan
|
||
svårare (kortare tid per station, passagerare som måste **släppas förbi**
|
||
vid stationer där man inte ska av) och kan ge debuffen *svettig*.
|
||
**Kvinnovagn** (`womenOnlyCar`, ev. bara mellan vissa klockslag som står
|
||
på skyltarna) markeras rosa; har sällskapet en man och går på den blir det
|
||
**grova minus**. Sällskapets medlemmar får därför kön.
|
||
- **Teknik:** `sim.NPC` med enkla mål och BFS-vägar (ren Go, seedade),
|
||
`minigame.CarPick`, trängsel i `Crowd`/`Ride`, dekorationslager
|
||
(seedade affischer) och `clutter`/`noise`/`crowd` i banformatet,
|
||
`tools/genart` för folk/affischer/vagnmarkeringar, `tools/gensound` för
|
||
bakgrundsprat; generatorn får breda gångar med filer.
|
||
- **Så blev det:** `sim.NPC` (vandrare mellan kiosker/toaletter/skyltar/
|
||
perronger med BFS-vägar och seedade pauser; avstigande som kliver av när
|
||
dörrarna öppnas och går till utgången) tar plats i rutnätet – spelaren
|
||
stoppas ("Ursäkta…"), folket går igenom varandra så inget låser sig.
|
||
Filer `<`/`>` i breda gångar: fel sida ger ×0,7 fart. `clutter` ger
|
||
seedade affischer på väggar intill gångar (tre varianter, en liknar en
|
||
skylt); `noise` ger loopat bakgrundsprat (`chatter.wav`) som dämpar
|
||
melodierna. **Vagnval** (`minigame.CarPick`): folk kliver av i 1,5 s
|
||
(Enter för tidigt = avdrag), vagnarna visas med trängselfigurer och
|
||
kömarkeringar, kvinnovagn rosa med 女性専用; lugn vagn +50, full vagn ger
|
||
trängselminispel med smalare zon, kortare tid per station och
|
||
passagerare som ska släppas förbi (← ), samt 60 % risk för *svettig*.
|
||
Man i sällskapet på kvinnovagnen: −200. Sällskapet har kön (Anna/Sara
|
||
kvinnor, Ville/Omar män, spelaren okänd). **Bana 005 Ueno** (svårighet
|
||
4): breda gångar med filer, folk 2, röra 2, ljud 2, fyra vagnar där vagn
|
||
1 är kvinnovagn de första 2,5 minuterna.
|
||
- **Klar när:** NPC:er rör sig på golv, är seedade och blockerar spelaren
|
||
(`TestNPCsWanderStayOnFloorAndBlockPlayer`), avstigande kommer och går
|
||
(`TestAlightingNPCsSpawnAndLeave`), filer bromsar (`TestLanesSlowWrongSide`),
|
||
vagnval/kvinnovagn/trängsel/avdrag (`TestCarsWomenOnlyEarlyAndCrowd`,
|
||
`TestCarPickAlightingAndChoice`, `TestRideCrowdLetPass`). ✓
|
||
- **Kvar till senare:** NPC:er som väntar på och kliver på tåget, folk som
|
||
bildar riktiga köer vid markeringarna, utrop/reklam som ljud (M12).
|
||
|
||
### 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.
|