M9b: folk, filer, reklam, sorl och vagnval
All checks were successful
release / build-release (push) Successful in 1m33s

Folk på stationen (NPC:er som vandrar, kliver av tåg och går mot
utgången, blockerar spelaren men inte varandra), vänster-/högerfiler
som bromsar på fel sida, bredare gångar, reklamaffischer (clutter),
japanskt sorl i loop (noise), vagnval med kvinnovagn (stort avdrag med
man i sällskapet, tidsstyrd), kömarkeringar på perrongen, trängsel per
vagn (bonus för tom vagn, svettig-debuff och svårare resa i full vagn,
släpp förbi passagerare) och att folk kliver av innan man får gå på.
Ny bana 005 Ueno. Skärmdumpsverktyget kan filtrera på bana/skript.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 12:33:44 +02:00
parent 8fca14e42d
commit de61568e44
49 changed files with 1568 additions and 136 deletions

115
internal/sim/npc_test.go Normal file
View File

@@ -0,0 +1,115 @@
package sim
import (
"testing"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/rng"
)
func TestNPCsWanderStayOnFloorAndBlockPlayer(t *testing.T) {
l := loadLevel(t, "005-ueno")
if l.Crowd == 0 {
t.Fatal("005 ska ha folk")
}
r := NewRun(l, RunOptions{Seed: rng.FromInt(3)})
if len(r.NPCs) != l.Crowd*NPCPerCrowd {
t.Fatalf("npcs=%d, vill ha %d", len(r.NPCs), l.Crowd*NPCPerCrowd)
}
moved := 0
start := make([]level.Point, len(r.NPCs))
for i, n := range r.NPCs {
start[i] = n.Mover.Tile
}
for i := 0; i < 60*20; i++ {
r.Step(frame, None, false)
for _, n := range r.NPCs {
if !l.At(n.Mover.Tile).Passable() {
t.Fatalf("NPC på vägg: %v", n.Mover.Tile)
}
}
}
for i := range start { // vandrarna ligger först; avstigande kan ha tillkommit efter dem
if r.NPCs[i].Mover.Tile != start[i] {
moved++
}
}
if moved == 0 {
t.Fatal("ingen NPC rörde sig på 20 s")
}
// Spelaren kan inte gå in i en NPC.
npc := r.NPCs[0]
npc.Mover.Tile, npc.Mover.Target, npc.Pause = level.Point{X: 2, Y: 1}, level.Point{X: 2, Y: 1}, 1e9
if r.CanEnter(level.Point{X: 2, Y: 1}) {
t.Fatal("rutan med en NPC är upptagen")
}
r.Step(frame, Right, false)
if r.Player.Moving() || r.BumpedAt < 0 {
t.Fatal("att gå in i någon stoppar en och noteras")
}
// Determinism.
a := NewRun(l, RunOptions{Seed: rng.FromInt(4)})
b := NewRun(l, RunOptions{Seed: rng.FromInt(4)})
for i := 0; i < 60*5; i++ {
a.Step(frame, None, false)
b.Step(frame, None, false)
}
for i := range a.NPCs {
if a.NPCs[i].Mover.Tile != b.NPCs[i].Mover.Tile {
t.Fatal("folket ska vara seedat")
}
}
}
func TestAlightingNPCsSpawnAndLeave(t *testing.T) {
l := loadLevel(t, "005-ueno")
r := NewRun(l, RunOptions{Seed: rng.FromInt(5)})
r.Party.nextEventAt = 1e9
base := len(r.NPCs)
// Spola fram tills första tåget har öppnat dörrarna.
for i := 0; i < 60*60; i++ {
r.Step(frame, None, false)
if ph, _ := r.PlatformPhase(level.Platform); ph == level.Dwelling {
break
}
}
exits := 0
for _, n := range r.NPCs {
if n.Kind == Exit {
exits++
}
}
if exits != NPCAlightBase+l.Crowd {
t.Fatalf("avstigande=%d, vill ha %d", exits, NPCAlightBase+l.Crowd)
}
// De går mot utgången och försvinner.
for i := 0; i < 60*120 && len(r.NPCs) > base; i++ {
r.Step(frame, None, false)
}
if len(r.NPCs) != base {
t.Fatalf("avstigande ska ha lämnat stationen: %d kvar", len(r.NPCs)-base)
}
}
func TestLanesSlowWrongSide(t *testing.T) {
src := `{"id":"lanes","name":{"sv":"x"},"difficulty":1,"destination":{"sv":"x"},
"grid":["#######","#S<<<P#","#######"],
"trains":[{"platform":"P","track":"1","line":{"en":"A"},"destination":{"en":"B"},"correct":true}]}`
l, err := level.Parse([]byte(src))
if err != nil {
t.Fatal(err)
}
r := NewRun(l, RunOptions{Seed: rng.FromInt(1)})
r.Player.Tile, r.Player.Target = level.Point{X: 2, Y: 1}, level.Point{X: 2, Y: 1}
r.Step(frame, Right, false) // '<' är vänsterfil: höger är fel håll
if r.Player.Speed >= DefaultSpeed || !r.WrongLane {
t.Fatalf("fel sida ska bromsa: speed=%.2f wrong=%v", r.Player.Speed, r.WrongLane)
}
for r.Player.Moving() {
r.Step(frame, None, false)
}
r.Step(frame, Left, false)
if r.Player.Speed != DefaultSpeed || r.WrongLane {
t.Fatalf("rätt sida full fart: speed=%.2f wrong=%v", r.Player.Speed, r.WrongLane)
}
}