All checks were successful
release / build-release (push) Successful in 1m40s
- sim.Fog: avslöjar tre gångsteg från spelaren plus gränsande väggar (ser längs gången, inte genom väggen); riktmärken (perronger, start, spärrar) syns alltid; Explored ger utforskad andel; nollställs per omgång - game/minimap: 1–3 px per ruta i det hörn som varken täcker inforutan eller spelaren, perrong vit när tåget är inne, spelaren blinkar, följarna i sina toner, avgångslista per spår (● inne / ○ kommer); M döljer/visar - resultatskärmen visar utforskad andel och antal försörjda; layout justerad - docs/README/wiki uppdaterade Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
160 lines
12 KiB
Markdown
160 lines
12 KiB
Markdown
# Mål och tester
|
||
|
||
## Projektmål
|
||
|
||
### Vision
|
||
|
||
Ett pixel art-pusselspel där känslan av att vara vilse på en japansk
|
||
station är själva spelet – och där man faktiskt blir bättre på att läsa
|
||
skyltarna ju mer man spelar. Spelet ska vara en enda binär som är lika lätt
|
||
att bygga på Linux som på Windows.
|
||
|
||
### Konkreta mål
|
||
|
||
1. **Spelbar story** från flygplatsen till flygplatsen, med stigande
|
||
svårighet, flerbytesbanor, sidobanor, slutanimation och eftertexter.
|
||
2. **En binär + en `resources/`-mapp** per plattform. Inga installers, inga
|
||
runtime-beroenden utöver vad ett vanligt skrivbord redan har.
|
||
3. **Få byggverktyg.** Go är det enda kravet (Linux behöver dessutom en
|
||
C-kompilator och X11/GL-headers för fönsterhanteringen). Allt byggs med
|
||
`go run ./tools/build`.
|
||
4. **Debug och release i VS Code** med F5 respektive Ctrl+Shift+B, utan
|
||
extra skript.
|
||
5. **All spellogik testbar utan fönster.** Bara presentationslagret känner
|
||
till Ebitengine.
|
||
6. **Deterministiskt.** Samma seed ger samma minispel och samma mål i
|
||
oändligt läge.
|
||
7. **Windows utan omskrivning.** `-os windows` ska vara allt som krävs för
|
||
att få en fungerande Windows-binär från Linux.
|
||
|
||
### Mätbara mål
|
||
|
||
| Mål | Mått | Status 2026-10-23 |
|
||
|-----|------|-------------------|
|
||
| Inkrementellt debug-bygge | < 10 s | 4,7 s |
|
||
| Release-binär | < 20 MB | 10,2 MB (Linux), 10,8 MB (Windows) |
|
||
| Testsviten | `go test ./...` grön på < 30 s | ~0,1 s |
|
||
| Banor | 100 % passerar det automatiska lösbarhetstestet | 2 av 2 |
|
||
| Windows-korsbygge från Linux | fungerar utan extra verktyg | ✓ (CGO_ENABLED=0) |
|
||
| Linux-binärens länkade bibliotek | bara libc | ✓ (GL/X11 laddas dynamiskt vid körning) |
|
||
| Bildfrekvens | 60 fps vid 4× skala på integrerad grafik | ej mätt |
|
||
| Uppstart | < 2 s till första bildruta | ej mätt |
|
||
|
||
## Teststrategi
|
||
|
||
### Principer
|
||
|
||
- **Ren logik → enhetstester.** Paket under `internal/` som inte är
|
||
presentation får inte importera Ebitengine. Det kontrolleras av
|
||
`internal/archtest`, som läser beroendegrafen med `go list` och faller om
|
||
något annat paket än `internal/game`, `internal/ui`, `internal/audio`
|
||
eller `cmd/…` drar in Ebitengine.
|
||
- **Data testas som data.** Varje bana, språkfil och ordbok som ligger i
|
||
`resources/` valideras av tester, så att trasigt innehåll stoppar bygget
|
||
i stället för att krascha spelet.
|
||
- **Slump går alltid via `internal/rng`** med en känd seed. Då går
|
||
minispel och event-timing att testa deterministiskt.
|
||
- **Inga tester kräver display.** Smoke-testet av själva fönstret körs
|
||
manuellt (eller på en maskin med skärm) med `-smoke`.
|
||
- **Innehållet testas mot ordboken.** Ny japansk text i en bana som inte
|
||
finns i `jp-dictionary.json` fäller `go test` – det är så vi garanterar
|
||
att skyltsystemet alltid kan översätta allt spelaren kan möta.
|
||
|
||
### Testnivåer
|
||
|
||
| Nivå | Vad | Hur | Körs |
|
||
|------|-----|-----|------|
|
||
| 1. Enhet | Funktioner i ett paket | `*_test.go` bredvid koden | `go test ./...` |
|
||
| 2. Data | Allt under `resources/` | Datadrivna tester som läser filerna | `go test ./...` |
|
||
| 3. Arkitektur | Beroenderegler | `internal/archtest` | `go test ./...` |
|
||
| 4. Simulering | En bot spelar en bana headless | Deterministisk körning av spelloopen utan rendering (kommande) | `go test ./...` |
|
||
| 5. Smoke | Binären öppnar fönster och avslutar | `metro-puzzle-jp -smoke 90`, eller `tools/shots` som dessutom spelar igenom bana 001 i skriptläge | manuellt / med skärm |
|
||
| 6. Speltest | Känsla, svårighet, buggar | Checklistan nedan | före varje release |
|
||
|
||
### Tester som finns i dag
|
||
|
||
| Paket | Test | Verifierar |
|
||
|-------|------|------------|
|
||
| `internal/res` | `TestLocate_*` | Uppslagsordningen env → bredvid binären → uppåt från cwd |
|
||
| `internal/res` | `TestDir_RealRepo` | Att repots `resources/` hittas och att sv.json + placeholder.png går att läsa |
|
||
| `internal/rng` | `TestDeterministic`, `TestDeriveIsStableAndContextual` | Samma seed → samma sekvens; del-seeds per kontext |
|
||
| `internal/level` | `TestParseAndSolve`, `TestUnsolvableWhenWalledOff` | BFS hittar/hittar inte väg till rätt perrong |
|
||
| `internal/level` | `TestValidationErrors` | Ojämna rader, okända tecken, fel antal starter, tåg vid saknad perrong … |
|
||
| `internal/level` | `TestAllShippedLevels` | Varje bana i `resources/data/levels` parsar, har id = filnamn, är lösbar och har inga onåbara interaktiva rutor |
|
||
| `internal/level` | `TestListAndLoadFile` | Banlistan är sorterad, laddning från disk, `TrainAt` hittar rätt/fel tåg |
|
||
| `internal/sim` | `TestMoveFourDirections`, `TestBlockedByWallTurnsButStays` | Rörelse åt alla håll; vägg stoppar men vänder figuren |
|
||
| `internal/sim` | `TestContinuousMovementKeepsPace`, `TestStopsAtTileBoundaryWithoutInput`, `TestDirectionChangesOnlyAtTileBoundary` | Jämn fart med hållen tangent, stopp vid rutgräns, riktningsbyte bara vid rutgräns |
|
||
| `internal/sim` | `TestWorldPosInterpolates`, `TestFrameExactArrival`, `TestHugeDtIsBounded` | Pixelposition mellan rutor; exakt 10 bildrutor per ruta vid 6 rutor/s; inga oändliga loopar |
|
||
| `internal/sim` | `TestCamera*` | Kameran centrerar, klampas mot kanter och centrerar små världar |
|
||
| `internal/level` | `TestScheduleStatus`, `TestScheduleAndFareDefaults` | Tidtabellen ger rätt "inne/borta" och sekunder kvar; standardvärden fylls i |
|
||
| `internal/level` | `TestSignAt`, `TestPath`, `TestUngatedPlatforms` | Skylttext per ruta; BFS-väg är kortast och sammanhängande; perronger utan spärr framför upptäcks |
|
||
| `internal/level` | `TestAllShippedLevels` (utökat) | …plus: tidtabeller giltiga, alla skyltrutor har sv/en-text, alla tåg har spår, **inga perronger nås utan spärr** |
|
||
| `internal/sim` | `TestBotWinsLevel001` | **Simuleringstest M2:** bot går kortaste vägen, passerar spärren (saldo dras en gång), väntar in tåget, kliver på → vinst; Step är no-op efteråt |
|
||
| `internal/sim` | `TestBotLosesOnWrongTrain`, `TestBoardOnlyWhenTrainPresent` | Fel tåg → förlust; Enter utan tåg inne gör inget |
|
||
| `internal/sim` | `TestGateNeedsCardBalance`, `TestSignHereOnlyWhenStill` | Tomt kort stoppas och registreras för HUD; mellan två spärrutor gratis; skylt visas bara stilla |
|
||
| `internal/lang` | `TestTokenizeLongestMatchAndKinds` | Längsta matchning, ord/okänt/övrigt, `Unknown` pekar ut vad ordboken saknar |
|
||
| `internal/lang` | `TestRenderPartialTranslation`, `TestKanaReadingWithoutMeaning`, `TestReadingsShownFromLevel2` | **Samma skylt visar exakt det spelaren kan**: kända ord översatta, okända kvar, kana som romaji när kana kan, läsningar från nivå 2 |
|
||
| `internal/lang` | `TestLearnUnlocksKanaAndLevels`, `TestFirstUnknownWord` | Nivåformeln, kana låses upp efter tre kana-ord, vilket ord hjälpen lär ut |
|
||
| `internal/lang` | `TestHelpSeconds`, `TestEnglishShareAndDeterminism` | Hjälptiden sjunker med nivån men inte under golvet; engelska-andelen per svårighet (1 = alltid, 5 = aldrig), deterministisk, ~60 % på svårighet 2 |
|
||
| `internal/lang` | `TestParseDictionaryValidation`, `TestShippedDictionary`, `TestAllJapaneseInLevelsIsInDictionary` | Ordboken är komplett per post och **täcker all japanska i banorna** |
|
||
| `internal/sim` | `TestAskHelpRevealsSignAndTeachesWord`, `TestHelpIsCancelledByWalkingAway`, `TestNoHelpOffSign` | Hjälpen tar rätt tid, avslöjar skylten, lär första okända ordet; avbryts när man går; kräver skylt |
|
||
| `internal/lang` | `TestProgressLearnsAfterEnoughReads`, `TestBuyWordsWithPoints` | Tre framsteg lär ordet; köp för poäng (kostnad = tier), stjärnor ger poäng |
|
||
| `internal/level` | `TestPartyAndParDefaults` | Sällskap/par-standardvärden, max sällskap, toalett/kiosk passerbara, avgångstavlans text |
|
||
| `internal/sim` | `TestStarsFromParTime`, `TestGoodResultTeachesDestination` | Par från kortaste vägen eller bandatan, stjärntrösklar; tre stjärnor lär resmålet och ger poäng, långsam vinst gör det inte |
|
||
| `internal/sim` | `TestReadingGivesSeededProgress` | Att stanna på en skylt ger seedade framsteg; samma seed = samma utfall; ord lossnar efter många läsningar |
|
||
| `internal/sim` | `TestMetersDecayAtRates`, `TestUrgeArisesBelowThreshold` | Mätarnas takt; behov under tröskeln |
|
||
| `internal/sim` | `TestMoreMembersMoreEvents`, `TestEventsAreSeeded` | **M4:** fyra i sällskapet ger minst 3× så många händelser som en; samma seed = samma logg |
|
||
| `internal/sim` | `TestUrgesSlowAndFacilitiesRestore`, `TestFollowersTrailLeader`, `TestPartySizeIsClamped` | Fartstraff, toalett/kiosk återställer och kostar, distraktion går över, följarna står på ledarens spår |
|
||
| `internal/sim` | `TestToiletAndKioskInRun`, `TestPartySlowsLeader` | Enter på W startar ett besök som tar tid och avbryts om man går; K drar från kortet; sällskapet saktar ledaren |
|
||
| `internal/sim` | `TestFogRevealsAlongCorridorsNotThroughWalls`, `TestLandmarksAlwaysVisible`, `TestFogGrowsWithMovementAndResetsPerRun` | **M5:** dimman lättar längs gångar men inte genom väggar; perronger/start/spärrar syns alltid; dimman växer när man går och nollställs per omgång |
|
||
| `internal/archtest` | `TestOnlyPresentationPackagesImportEbitengine` | Beroenderegeln ovan |
|
||
|
||
### Planerade tester per milstolpe
|
||
|
||
| Milstolpe (se roadmap) | Tester som ska finnas när den är klar |
|
||
|------------------------|----------------------------------------|
|
||
| ~~M1 Rörelse & kamera~~ ✓ | Kollision mot vägg, rörelse i 4 riktningar, kamera klampas till kartan – finns, se tabellen ovan |
|
||
| ~~M2 Stationen~~ ✓ | Tidtabell, spärr med/utan saldo, vinst/förlust, bot klarar bana 001 headless – finns, se tabellen ovan |
|
||
| ~~M3 Japanska & skyltar~~ ✓ | Delvis översättning, engelska-andel, ordbokens fullständighet och täckning, hjälptid – finns, se tabellen ovan |
|
||
| ~~M4 Lämmeltåget~~ ✓ | Mätare, händelsefrekvens efter gruppstorlek, seedade händelser – finns, se tabellen ovan |
|
||
| ~~M5 Minikarta~~ ✓ | Upptäckta rutor, nollställning per bana, avgångar från start – finns, se tabellen ovan |
|
||
| M6 Kort/biljetter/minispel | Saldo dras i spärr; tomt kort → automatkrav; biljettminispel: rätt/fel biljett; telefon-skanning misslyckas med given procent per seed; batteri tar bara slut över tröskeltid |
|
||
| M7 Svårighet/poäng/debuffs | Poängformeln (tid, pengar, mätare, läge); tidsgräns och maxantal missade tåg; varje debuff ändrar rätt stat; temporära debuffar räknar ned 1–4 banor |
|
||
| M8 Progression & sparfil | Sparfil round-trip (skriv → läs ger samma tillstånd inkl. seed); skill tree-krav; upplåsningar efter rätt bana; "< 33 %"-regeln kontrolleras mot itemlistan |
|
||
| M9 Navigeringslägen | Listläge genererar rätt ordning av tåg; skyltläge: 1–4 giltiga vägar; fel-riktnings-förlust triggar vid rätt slutstation |
|
||
| M10 Story-innehåll | Alla banor lösbara (redan automatiskt); flerbytesbanor har sammanhängande byten; sista banan slutar på flygplatsen |
|
||
| M11 Oändligt läge & NG+ | Målsekvens deterministisk per seed; NG+ behåller skills men nollställer items |
|
||
| M12 Ljud & polish | Alla ljudfiler som refereras finns och går att avkoda; Windows-binär startar (manuellt) |
|
||
|
||
### Acceptanskriterier för "klar"
|
||
|
||
En milstolpe är klar när:
|
||
|
||
1. Alla tester i raden ovan finns och är gröna.
|
||
2. `go vet ./...` och `go test -race ./...` är gröna.
|
||
3. Debug- och release-bygget går igenom för Linux och Windows.
|
||
4. Roadmapen är uppdaterad med datum och eventuella avsteg.
|
||
|
||
### Manuell testchecklista (speltest)
|
||
|
||
- [ ] Spelet startar till meny på under 2 s; ESC/avsluta fungerar.
|
||
- [ ] Fönstret kan skalas; pixlarna är skarpa (heltalsskalning).
|
||
- [ ] Bana 001 går att klara i både listläge och skyltläge.
|
||
- [ ] Fel tåg ger tydlig förlust med förklaring.
|
||
- [ ] Mätarna syns, sjunker och går att fylla på.
|
||
- [ ] Tomt kort tvingar till automaten; minispelet är begripligt första gången.
|
||
- [ ] Minikartan fylls i när man går runt och nollställs vid ny bana.
|
||
- [ ] Spara mitt i en bana, ladda, och få samma minispelsutfall.
|
||
- [ ] Ljud spelas vid ankomst och avgång; inget knaster vid loop.
|
||
- [ ] Samma sparfil fungerar på Linux och Windows.
|
||
|
||
### Kommandon
|
||
|
||
```bash
|
||
go test ./... # hela sviten
|
||
go test -race -v ./... # utförligt med race-detektor
|
||
go test ./internal/level/ -run TestAllShippedLevels -v # bara banorna
|
||
go vet ./...
|
||
go run ./tools/build -mode debug -run -- -smoke 90 # bygg + smoke-test med fönster
|
||
```
|