package domain import ( "os" "path/filepath" "regexp" "testing" ) // arenaRegistryPath is the Godot-side single source of truth for the arena // list (CLAUDE.md says so explicitly). rankedArenas in ranked.go is a // hand-maintained mirror of its floor-goal entries, and nothing has ever // checked the two against each other -- ranked_test.go asserts the same three // paths the production code hardcodes, so both could drift together silently. // // Drift is not hypothetical in either direction: // // - The registry's own comment anticipates flipping an elevated variant's // `random` flag to true once a checkpoint trained on that geometry is // promoted. Ranked would keep excluding it indefinitely. // - Adding an arena leaves ranked never selecting it. // - Renaming or removing one leaves the allocator handing out a scene path // that no longer exists, and an allocated ranked server fails to load its // arena at match start -- after allocation, so it burns a real match. const arenaRegistryPath = "../../Game/scripts/arena_registry.gd" // gameScenesDir resolves a res:// path to the checked-out scene file. const gameScenesDir = "../../Game" var arenaEntryPattern = regexp.MustCompile(`\{"name":\s*"([^"]*)",\s*"path":\s*"([^"]*)",\s*"random":\s*(true|false)\}`) type registryArena struct { Name string Path string Random bool } func parseArenaRegistry(t *testing.T) []registryArena { t.Helper() source, err := os.ReadFile(arenaRegistryPath) if err != nil { t.Fatalf("read the Godot arena registry: %v", err) } matches := arenaEntryPattern.FindAllStringSubmatch(string(source), -1) arenas := make([]registryArena, 0, len(matches)) for _, match := range matches { arenas = append(arenas, registryArena{Name: match[1], Path: match[2], Random: match[3] == "true"}) } // Guard the guard. If the literal format changes and the pattern stops // matching, every assertion below would pass vacuously against an empty // list -- which is the exact failure mode this test exists to prevent. if len(arenas) < 2 { t.Fatalf("parsed %d arenas from %s; the entry format probably changed and this parser needs updating", len(arenas), arenaRegistryPath) } var eligible, ineligible int for _, arena := range arenas { if arena.Random { eligible++ } else { ineligible++ } } if eligible == 0 || ineligible == 0 { t.Fatalf("parsed %d eligible and %d ineligible arenas; expected both kinds, so the `random` flag is probably not being read correctly", eligible, ineligible) } return arenas } // TestRankedArenasMatchTheGodotRegistry is the cross-language contract. It is // the arena equivalent of the golden join-authorisation token in // Game/tests/cases/test_match_net.gd: one side owns the truth, and this fails // loudly when the other stops agreeing. func TestRankedArenasMatchTheGodotRegistry(t *testing.T) { registry := parseArenaRegistry(t) expected := map[string]string{} var expectedOrder []string for _, arena := range registry { if !arena.Random { continue } expected[arena.Path] = arena.Name expectedOrder = append(expectedOrder, arena.Path) } actual := map[string]string{} for id, arena := range rankedArenas { actual[arena.Path] = id } for path, name := range expected { if _, present := actual[path]; !present { t.Errorf("registry arena %q (%s) is ranked-eligible in Godot but missing from rankedArenas.\n"+ "If a checkpoint trained on this geometry was promoted, add it to rankedArenas and rankedArenaOrder in ranked.go.", path, name) } } for path, id := range actual { if _, present := expected[path]; !present { t.Errorf("rankedArenas contains %q (id %q), which is not a random:true entry in %s.\n"+ "Ranked would allocate a scene the Godot registry no longer offers.", path, id, arenaRegistryPath) } } // Rotation order must follow the registry's declaration order, since // RankedArenaForProposal indexes rankedArenaOrder and callers reason about // "the arenas, in order" across both languages. if len(rankedArenaOrder) != len(expectedOrder) { t.Fatalf("rankedArenaOrder has %d entries, registry has %d eligible", len(rankedArenaOrder), len(expectedOrder)) } for index, id := range rankedArenaOrder { arena, known := rankedArenas[id] if !known { t.Fatalf("rankedArenaOrder[%d] = %q, which is not a key of rankedArenas", index, id) } if arena.Path != expectedOrder[index] { t.Errorf("rotation position %d is %q, registry declares %q there", index, arena.Path, expectedOrder[index]) } } } // A ranked arena path is handed to an allocated server after allocation, so a // path with no scene behind it fails at match start rather than at selection -- // burning a real match and a real server. Cheap to catch here instead. func TestRankedArenaPathsResolveToRealScenes(t *testing.T) { for id, arena := range rankedArenas { relative, ok := scenePathFromRes(arena.Path) if !ok { t.Errorf("ranked arena %q has path %q, which is not a res:// path", id, arena.Path) continue } if _, err := os.Stat(filepath.Join(gameScenesDir, relative)); err != nil { t.Errorf("ranked arena %q points at %q, which does not exist: %v", id, arena.Path, err) } } } // Elevated-goal variants stay ranked-ineligible until a policy trained on that // geometry is promoted; the current bots cannot score on one. Assert this // against the registry's own flag rather than a second hardcoded list, so the // exclusion tracks the registry instead of drifting alongside it. func TestIneligibleRegistryArenasAreRejectedForRanked(t *testing.T) { registry := parseArenaRegistry(t) checked := 0 for _, arena := range registry { if arena.Random { continue } checked++ if IsRankedArenaPath(arena.Path) { t.Errorf("%q (%s) is random:false in the Godot registry but accepted for ranked", arena.Path, arena.Name) } } if checked == 0 { t.Fatal("no ineligible arenas were checked") } } func scenePathFromRes(path string) (string, bool) { const prefix = "res://" if len(path) <= len(prefix) || path[:len(prefix)] != prefix { return "", false } return path[len(prefix):], true }