All checks were successful
release / build-release (push) Successful in 1m37s
- sim/stats.go: Stats (fart, törst, fokus, toalett, tur), debuffar som multiplikatorer, Profile som följer med mellan banor och räknar ned - sim.Run: tidsgräns och maxantal missade tåg förlorar banan; poängformel (tid mot par, pengar, sällskapets mätare, utforskat, avdrag); debuffar tjänas av många steg, lång tid, tom matmätare och seedad otur vid kiosken; stats påverkar fart, mätartakter, läsning och hjälptid - egna kort (individualCards): Member.Card, spärren kräver allas saldo och drar från alla, automaten frågar vems kort, biljetter köps åt alla - sittplatsval (seatSelection): Ticket får ett sittplatssteg med seedade önskemål (fönster/gång/sida/bredvid); missnöjda ger mätar- och poängavdrag - level: maxMissedTrains, individualCards, seatSelection; bana 003 Shibuya (svårighet 3, fyra i sällskapet) och 002 med tidsgräns och sittplatser; ordboken utökad; generatorn är spec-driven - game: tid kvar/missade i HUD, debuffar i meny/HUD/resultat, poäng och förlustorsaker på resultatet, vyer för personval och vagn, skript-pick, flaggan -debuffs; HUD och resultat komprimerade för fyra i sällskapet - plan (Björn 2026-10-24): flera våningar med trappor som M9a, egna kort och sittplatsval som svårighetsinställningar i M7 – i spelbeskrivning, roadmap, testplan - docs/README/wiki med sittplatsbilden Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
171 lines
14 KiB
Markdown
171 lines
14 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 | 3 av 3 |
|
||
| 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/minigame` | `TestTopUpFlowAndDeterminism`, `TestTicketBoardSeededAndAffordable`, `TestCrowdZoneAndAttempts`, `TestRideStopsAndGettingOff`, `TestRideWithoutFillersStillHasDestination` | **M6:** varje minispel är deterministiskt per seed; fel knapp straffar, sedlar rinner tillbaka; taxor och råd; zon och försök; kliva av rätt/fel/förbi; fel tåg saknar resmålet |
|
||
| `internal/sim` | `TestWalletPayTopUpAndPhone` | Kort, kontanter, påfyllning, spenderat, batteri gör kortet i telefonen oanvändbart |
|
||
| `internal/sim` | `TestTopUpFlowAtMachine`, `TestTicketLevelFlow`, `TestCrowdFailureMissesTrain` | Tomt kort → automaten → påfyllning → spärren släpper; **hela bana 002 headless** med fel/rätt biljett, trängsel och resa; två bom = missat tåg |
|
||
| `internal/sim` | `TestPhoneSpeedRescanAndBattery`, `TestRideWrongOrMissedStopLoses` | Fartbuff, seedad omskanning som fryser spelaren, dött batteri stoppar spärren; fel station / åka förbi förlorar |
|
||
| `internal/sim` | `TestDebuffEffectsAndProfile` | **M7:** varje debuff påverkar något, kombineras multiplikativt, profilen förlänger i stället för dubblerar och räknar ned per bana |
|
||
| `internal/sim` | `TestTimeLimitAndMissedTrainsLose`, `TestScoreBreakdown` | Tidsgräns och maxantal missade tåg förlorar banan (001 har inga gränser); poängformeln och avdragen |
|
||
| `internal/sim` | `TestDebuffsAffectRunAndAreEarned` | Skoskav/matförgiftning/hunger sänker fart, snabbar mätare och förlänger hjälptiden; många steg, lång tid och tom matmätare ger debuffar |
|
||
| `internal/sim` | `TestIndividualCardsAtGateAndMachine`, `TestLevel003Flow` | Egna kort: spärren kräver allas saldo, automaten frågar vems kort, spärren drar från alla; **hela bana 003 headless** med biljetter åt alla, sittplatser, trängsel och resa |
|
||
| `internal/minigame` | `TestTopUpPicksMemberWithIndividualCards`, `TestSeatSelectionPreferences` | Personval och 2×2-rutnät; seedade önskemål, upptagna platser, missnöje, fönster/gång/sida/bredvid, Esc avbryter köpet |
|
||
| `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, automat, biljetter, omskanning, batteri – finns, se tabellen ovan |
|
||
| ~~M7 Svårighet/poäng/debuffs~~ ✓ | Poäng, tidsgräns, debuffar, egna kort, sittplatsval – finns, se tabellen ovan |
|
||
| M7b Tåg som kommer och går | `TrainPhase(t)` ger approaching/arriving/dwelling/departing/away med rätt fas ur tidtabellen; indikatorn aktiv bara inom N rutor från rälsen; vindzonen = rutor längs perrongkanten |
|
||
| 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 |
|
||
| M9a Flera våningar | Trappa byter våning för Mover och följare; `Path` över våningar; lösbarhetstest med trappor; dimma/minikarta per våning; gamla banor = en våning |
|
||
| 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
|
||
```
|