All checks were successful
release / build-release (push) Successful in 1m46s
- internal/progress: katalog (skills.json 12 noder, items.json 25 items) med validering; XP/nivåer (150·n(n+1)/2) och skillpoäng; skillträd med krav och kostnad; items per plats med buffar, genvägar (JR Pass, kartor, powerbank) och färgton; banresultat och upplåsning (huvudbanor i ordning, sidospår efter föräldern); 33 %-regeln som test - internal/save: versionerad JSON med kunskap, plånbok, debuffar, progression och pågående bana + seed; atomisk skrivning; migrering; ~/.config/metro-puzzle-jp/save.json resp. %AppData% - sim: RunOptions får Extra/FogRadius/Landmarks/FreeGates/RevealAll/ InfiniteBattery; luck påverkar trängselzonen; Fog.RevealAll och extra landmärken - game: bankarta i spelbrädesstil ersätter menyn (⟳ återupptar med sparad seed), skillträd (T), garderob (I), butik i kiosken (B), XP/nivå/items/ upplåsningar på resultatet, spelaren tonas av skins; autospar vid banstart, efter banan och vid köp; flaggor -save/-nosave - level: kind/order/parent/rewards/shop; ny sidobana 002b Kabukicho som belönar skinnet Kimono; 001 belönar Salaryman-skinnet; butikssortiment på 002/003 - tools/shots: demo-sparfil (build/, aldrig den riktiga), bilder av karta, skills, garderob och butik; alla körningar med -nosave - docs/README/wiki uppdaterade Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
180 lines
16 KiB
Markdown
180 lines
16 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 | 4 av 4 |
|
||
| 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/level` | `TestSchedulePhases`, `TestScheduleValidationForPhases`, `TestTrackTile` | **M7b:** faserna away→approaching→arriving→dwelling→closing→leaving ur tidtabellen, ombordstigning bara med öppna dörrar, Status och Phase konsekventa; för korta uppehåll/borta-tider avvisas; rälsrutan |
|
||
| `internal/sim` | `TestNearTrackAndPhases` | Närmaste perrong inom två rutor, inte tre; under inrullningen syns tåget men man kan inte kliva på; med öppna dörrar kan man |
|
||
| `internal/progress` | `TestShippedCatalogAndThirdRule`, `TestCatalogValidation` | **M8:** katalogerna parsar och validerar; **billigaste tredjedelen av items kostar mer än startkassan**; banornas belöningar/butiker pekar på riktiga items |
|
||
| `internal/progress` | `TestXPLevelsAndSkillPoints`, `TestSkillTreeRequirementsAndCost` | Nivåtrösklar, skillpoäng per level-up, krav och kostnad, effekter multipliceras |
|
||
| `internal/progress` | `TestItemsBuyEquipAndEffects`, `TestLevelRecordsAndUnlocking` | Köp, bär/ta av, en per plats, tint, genvägar; bästa resultat behålls, upplåsning av huvud- och sidobanor |
|
||
| `internal/save` | `TestRoundTrip`, `TestCorruptAndFutureVersions`, `TestDefaultPath` | Skriv → läs ger samma tillstånd inkl. seed; trasig/nyare fil ger fel; version 0 migreras |
|
||
| `internal/sim` | `TestRunOptionsFromProgression` | Extra stats, sikt, landmärken, gratis spärrar, hela kartan, powerbank |
|
||
| `internal/minigame` | `TestCrowdLuckWidensZone` | Tur gör zonen bredare, otur smalare, klampat |
|
||
| `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~~ ✓ | Faser, ombordstigning, indikatoravstånd – finns, se tabellen ovan; ljud och animation verifieras manuellt/med skärmdumpar |
|
||
| M9b Folk, reklam, ljudröra | NPC:er följer seedade mål utan att fastna; krock saktar ned; fel sida i gång bromsar; bot klarar bana med folk; fler NPC:er = längre tid; clutter/noise läses per bana |
|
||
| ~~M8 Progression & sparfil~~ ✓ | Round-trip, skillkrav, upplåsningar, 33 %-regeln – finns, se tabellen ovan |
|
||
| 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
|
||
```
|