Files
metro-puzzle-jp/doc/mal-och-tester.md
claude 46bd330dfc
All checks were successful
release / build-release (push) Successful in 1m38s
M10d: toaletter innanför och utanför spärrarna
Björns tillägg 2026-10-24. Generatorn har nu ett betalt område per perrong
(paidDepth) och toilets inside/outside/both: 005 Ueno och 008b Nippori har
toalett bara innanför, 007b Ueno på båda sidor. Perrongceller görs till
återvändsgränder (bara passagen mot starten behålls, labyrinten repareras)
och det betalda området krymper om det skulle bli genomfart; regeln att
varje perrong nås utan att passera ett annat betalt område testas i
level och levelcheck. level.PathOutside används av testbotar och shots.
Alla genererade banor utom 003 omgjorda, skärmdumpar omtagna.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 23:09:27 +02:00

200 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 | 11 av 11 (inkl. 4 delbanor) |
| 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/level` | `TestFloorsStairsAndPaths`, `TestFloorsValidation` | **M9a:** flera våningar läses, trappor länkar, BFS-vägen byter våning bara via trappa, spärregeln och skyltar per våning; trappa utan motpart, olika storlekar, grid+floors och fel våning avvisas |
| `internal/sim` | `TestStairsMoveBetweenFloors` | Att kliva på trappan flyttar spelaren en våning, dimman lättar runt andra änden, perrongen hörs bara på egen våning, boten går hela vägen över våningar |
| `internal/level` | `TestCarsWomenOnlyAndCrowdFields` | **M9b:** vagnar, kvinnovagnens tidsfönster, crowd/clutter/noise 0–3, filrutor |
| `internal/nav` | `TestJudge`, `TestTooFarOnLongLoop`, `TestStopsRoutesAndNames`, `TestValidation`, `TestShippedNetwork` | **M9:** rätt/fel håll/för långt/ingen trafik på raka linjer och ringlinjer, stationer i färdriktningen, giltiga vägar sorterade, nätets validering, det riktiga nätets förutsättningar för banorna |
| `internal/level` | `TestAnnotateWithNetwork`, `TestPlatformTilesAndShippedVerdicts` | **M9:** `correct` räknas ut från nätet, felaktiga kopplingar ger fel, okopplade banor orörda; perrongtecknen `P p Q q R r`; 005/006 får väntade bedömningar |
| `internal/sim` | `TestNetworkRideUsesRealStops`, `TestWrongDirectionLoses`, `TestTooFarLoses`, `TestSignsModeBonus`, `TestTerminalLines` | **M9:** resan visar linjens riktiga stationer med resmålet på rätt plats; fel håll och för långt bort förlorar med rätt orsak; skyltläget ger +50 %; terminalen listar linjer med grannstationer |
| `internal/lang` | `TestNetworkNamesInDictionary` | **M9:** alla stations- och linjenamn i nätet finns i ordboken |
| `internal/level` | `TestStoryChains` | **M10:** `next`-kedjor validerade (saknad/fel sort/dubbel/cykel/osammanhängande byte ger fel), `ChainOf`/`FinalOf`, storyn börjar och slutar på flygplatsen |
| `internal/minigame` | `TestPayerPicksSourceAndRefusesPoor` | **M10c:** markören på första som har råd, nekande utan avslut, avbrott utan köp |
| `internal/sim` | `TestKioskAsksWhoPays`, `TestOneWayGatesNeedStaff` | **M10c:** kiosken frågar vem som betalar med egna kort, vald följares kort dras, kontant fungerar, avbrott drar inget; avgift vid inpassering i betalt område, spärren släpper inte ut incheckad, personal låser upp efter 10 s, ny inpassering kostar igen |
| `internal/level` | `TestOneWayGatesAndInside` | **M10c:** `oneWayGates` läses, `InsideGates` = rutorna innanför spärrarna |
| `internal/level` | `TestPathOutsideAvoidsOtherPaidAreas` + regel i `TestAllShippedLevels` | **M10d:** vägar som undviker andra perrongers betalda områden; varje perrong nås utan att gå genom ett annat betalt område |
| `internal/level` | `TestToiletSides` | **M10d:** 005 och 008b har toaletter bara innanför, 007b på båda sidor, 007 och 008 bara utanför; innanför bara på banor med spärrar utan återvändo |
| `internal/sim` | `TestJourneyWithTransfer`, `TestJourneyWrongTrainOnSecondLeg`, `TestChainLevelsLoad` | **M10:** bytet fortsätter omgången på nästa station med klocka, plånbok, sällskap och par-summa; vinst först på sista delbanan; fel håll på bytesstationen förlorar; delbanor laddas enskilt |
| `internal/minigame` | `TestCarPickAlightingAndChoice`, `TestCrowdLevelNarrowsZone`, `TestRideCrowdLetPass` | Folk av först (för tidigt räknas), seedad trängsel med minst en lugn vagn, val; trängsel smalnar zonen; resan: kortare tid per station, passagerare som vill förbi, blockerade räknas |
| `internal/sim` | `TestNPCsWanderStayOnFloorAndBlockPlayer`, `TestAlightingNPCsSpawnAndLeave` | Folk rör sig på golv, seedat, blockerar spelaren; avstigande spawnar när dörrarna öppnas och lämnar via utgången |
| `internal/sim` | `TestLanesSlowWrongSide`, `TestCarsWomenOnlyEarlyAndCrowd` | Fel sida i filen bromsar; kvinnovagn med man ger grova minus, för tidigt ombord räknas, resan ärver vagnens trängsel, lugn vagn ger bonus |
| `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, vagnar~~ ✓ | NPC:er, filer, röra, vagnval – finns, se tabellen ovan (bot med folk: testerna tömmer folket där det skulle störa, "fler NPC:er = längre tid" mäts manuellt) |
| ~~M8 Progression & sparfil~~ ✓ | Round-trip, skillkrav, upplåsningar, 33 %-regeln – finns, se tabellen ovan |
| ~~M9a Flera våningar~~ ✓ | Trappor, vägar över våningar, dimma per våning, gamla banor oförändrade – finns, se tabellen ovan |
| ~~M9 Navigeringslägen~~ ✓ | Nätets bedömningar (rätt/fel håll/för långt/ingen trafik), 1–4 rätta tåg, riktiga stationer på resan, fel-håll- och för-långt-förlust, skyltbonus, terminal – finns, se tabellen ovan |
| ~~M10 Story-innehåll~~ ✓ | Kedjorna valideras, byten hänger ihop, storyn börjar och slutar på flygplatsen, bytet för med sig tid/plånbok/sällskap – finns, se tabellen ovan; `tools/levelcheck` som manuell kontroll |
| ~~M10c Vem betalar, spärrar utan återvändo~~ ✓ | Väljaren betalar från rätt källa och nekar den utan pengar; spärren håller kvar, personalen låser upp, ny inpassering kostar – finns, se tabellen ovan |
| ~~M10d Toaletter innanför/utanför~~ ✓ | Toaletterna ligger på rätt sida av spärrarna per bana – finns, se tabellen ovan |
| M10b Bussar och vägar | Bussar följer tidtabell och vägrutor; hållplatser bedöms som perronger; överstimulerande UI-reglaget påverkar bara presentationen |
| 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
```