class_name ArenaRegistry # Single source of truth for available arenas: the Free Play menu dropdown # lists all of these, and Match/Spectate pick at random each session from # those with "random" true. Training is exempt — training.tscn/ # training_elevated.tscn each keep their own fixed Arena child. # # Elevated-goal variants are Free-Play-only ("random": false) until a # checkpoint trained on training_elevated.tscn is promoted — the current # promoted bots (see main_menu.gd's DIFFICULTIES) were trained exclusively # on floor-level goals and can't be expected to score on an elevated one. # Flip a variant's "random" flag to true once that training/promotion has # happened. const ARENAS := [ {"name": "Starfield (Floor Goals)", "path": "res://scenes/arena_01.tscn", "random": true}, {"name": "Starfield (Elevated Goals)", "path": "res://scenes/arena_01_elevated.tscn", "random": false}, {"name": "Nebula (Floor Goals)", "path": "res://scenes/arena_02.tscn", "random": true}, {"name": "Nebula (Elevated Goals)", "path": "res://scenes/arena_02_elevated.tscn", "random": false}, {"name": "Asteroid Field (Floor Goals)", "path": "res://scenes/arena_03.tscn", "random": true}, {"name": "Asteroid Field (Elevated Goals)", "path": "res://scenes/arena_03_elevated.tscn", "random": false}, ] static func random_path() -> String: var candidates := ARENAS.filter(func(arena): return arena["random"]) return candidates[randi() % candidates.size()]["path"] # The arenas a server may rotate through, in declaration order. Same filter as # random_path(): an elevated-goal variant is Free-Play-only until a checkpoint # trained on it is promoted, and a dedicated server rotating onto one would # hand every bot-filled slot an arena it cannot score in. static func rotation_paths() -> Array: return ARENAS.filter(func(arena): return arena["random"]).map(func(arena): return arena["path"]) # Task 6.5's arena rotation, as pure arithmetic so it is unit-testable without # a server: given how many matches have already been played, which arena is # next. `random` deliberately still uses the global RNG (the caller wants # variety, not reproducibility); `sequential` is a pure function of the count, # which is what makes "the server cycles arenas" an assertable claim rather # than an observation about luck. static func path_for_match(match_index: int, mode: String) -> String: var paths := rotation_paths() if paths.is_empty(): return ARENAS[0]["path"] if mode == "random": return paths[randi() % paths.size()] return paths[posmod(match_index, paths.size())]