All checks were successful
release / build-release (push) Successful in 1m41s
- sim.Wallet: IC-kort, kontanter, pappersbiljett, telefon med batteri, spenderat; följer med mellan banor (Game.Wallet) - internal/minigame: gemensamt Game-gränssnitt (Update/Done/Success), all slump från del-seeds: TopUp (hitta チャージ, belopp, mata sedlar), Ticket (taxetavla med resmål på japanska), Crowd (tidsfönster, två försök), Ride (stationerna passerar, kliv av vid resmålet) - sim.Run: spärren kräver biljett på biljettbanor annars användbart kort; telefonen ger ×1,1 fart men 15 % seedad omskanning (frysning 1,5 s), dött batteri gör kortet oanvändbart; automaten startar påfyllning eller biljettköp; tåget kontrollerar biljetten; fulla tåg kräver trängsel; resan avgör vinst/förlust (fel station, åkte förbi, fel tåg); StepMini låter klockan och sällskapet ticka medan rörelsen pausar - level: ticketRequired, rideStops, Train.Crowded; 002 är biljettbana med fullt tåg och fyra stopp; ordboken har stationer (kind) + sju nya namn - game: vyer för minispelen, plånbok i HUD, P i menyn = kort i telefonen, förlustorsaker och spenderat på resultatet, flaggor -phone/-card, skriptkommandon hold/waitstop; inforuta/minikarta döljs under minispel - plan: M7b (Björn) – tågvind, ankomstindikator nära rälsen, in-/utkörningsanimation för tåg och bussar – i spelbeskrivning, roadmap, testplan - docs/README/wiki med tre nya bilder (automat, biljettavla, tågresa) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
13 KiB
13 KiB
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
- Spelbar story från flygplatsen till flygplatsen, med stigande svårighet, flerbytesbanor, sidobanor, slutanimation och eftertexter.
- En binär + en
resources/-mapp per plattform. Inga installers, inga runtime-beroenden utöver vad ett vanligt skrivbord redan har. - 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. - Debug och release i VS Code med F5 respektive Ctrl+Shift+B, utan extra skript.
- All spellogik testbar utan fönster. Bara presentationslagret känner till Ebitengine.
- Deterministiskt. Samma seed ger samma minispel och samma mål i oändligt läge.
- Windows utan omskrivning.
-os windowsska 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 avinternal/archtest, som läser beroendegrafen medgo listoch faller om något annat paket äninternal/game,internal/ui,internal/audioellercmd/…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/rngmed 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.jsonfällergo 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/archtest |
TestOnlyPresentationPackagesImportEbitengine |
Beroenderegeln ovan |
Planerade tester per milstolpe
| Milstolpe (se roadmap) | Tester som ska finnas när den är klar |
|---|---|
| Kollision mot vägg, rörelse i 4 riktningar, kamera klampas till kartan – finns, se tabellen ovan | |
| Tidtabell, spärr med/utan saldo, vinst/förlust, bot klarar bana 001 headless – finns, se tabellen ovan | |
| Delvis översättning, engelska-andel, ordbokens fullständighet och täckning, hjälptid – finns, se tabellen ovan | |
| Mätare, händelsefrekvens efter gruppstorlek, seedade händelser – finns, se tabellen ovan | |
| Upptäckta rutor, nollställning per bana, avgångar från start – finns, se tabellen ovan | |
| Saldo, automat, biljetter, omskanning, batteri – finns, se tabellen ovan | |
| 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 |
| 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 |
| 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:
- Alla tester i raden ovan finns och är gröna.
go vet ./...ochgo test -race ./...är gröna.- Debug- och release-bygget går igenom för Linux och Windows.
- 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
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