mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-13 22:02:04 +00:00
fix(*): apply the locomotion mask during in-game/eval inference, not just training
AIShipController (eval + real gameplay) ran the raw policy output unmasked regardless of allow_vertical/allow_pitch_roll, while ShipAIController (training) correctly discarded those axes for grounded curriculum stages. A grounded-trained model's untrained vertical/pitch-roll output reached the ship as noise during eval, understating it against models that were never handicapped this way.
This commit is contained in:
+29
-3
@@ -23,7 +23,16 @@ TRAINING_SCENE = "res://scenes/training.tscn"
|
||||
DEFAULT_GODOT_MACOS = "/Applications/Godot.app/Contents/MacOS/Godot"
|
||||
|
||||
|
||||
def run_half(godot_bin: str, model_a: str, model_b: str, episodes: int, speedup: int, seed: int) -> dict:
|
||||
def run_half(
|
||||
godot_bin: str,
|
||||
model_a: str,
|
||||
model_b: str,
|
||||
episodes: int,
|
||||
speedup: int,
|
||||
seed: int,
|
||||
grounded_a: bool = False,
|
||||
grounded_b: bool = False,
|
||||
) -> dict:
|
||||
cmd = [
|
||||
godot_bin,
|
||||
"--path",
|
||||
@@ -37,6 +46,14 @@ def run_half(godot_bin: str, model_a: str, model_b: str, episodes: int, speedup:
|
||||
f"--speedup={speedup}",
|
||||
f"--env_seed={seed}",
|
||||
]
|
||||
# Must match how each model was actually trained (see AIShipController's
|
||||
# allow_vertical/allow_pitch_roll) — a curriculum stage 1/2 model never
|
||||
# got a reward gradient on these axes, so leaving them unmasked here adds
|
||||
# untrained aerial noise the model's own training never had to contend with.
|
||||
if grounded_a:
|
||||
cmd += ["--eval_allow_vertical_a=false", "--eval_allow_pitch_roll_a=false"]
|
||||
if grounded_b:
|
||||
cmd += ["--eval_allow_vertical_b=false", "--eval_allow_pitch_roll_b=false"]
|
||||
timeout = episodes * 30 / speedup * 3 + 120 # worst case: all draws, plus margin
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||||
for line in result.stdout.splitlines():
|
||||
@@ -57,6 +74,12 @@ def main():
|
||||
)
|
||||
parser.add_argument("--speedup", type=int, default=16)
|
||||
parser.add_argument("--history", default=str(TRAINING_DIR / "eval_history.json"))
|
||||
parser.add_argument(
|
||||
"--grounded-a", action="store_true", help="model_a was trained with locomotion masked (curriculum stages 1-2)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--grounded-b", action="store_true", help="model_b was trained with locomotion masked (curriculum stages 1-2)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
model_a = str(pathlib.Path(args.model_a).resolve())
|
||||
@@ -65,8 +88,11 @@ def main():
|
||||
|
||||
# Half the episodes on each side to cancel any residual side asymmetry;
|
||||
# different seeds so the halves see different randomized episode states.
|
||||
first = run_half(args.godot_bin, model_a, model_b, half, args.speedup, seed=1)
|
||||
second = run_half(args.godot_bin, model_b, model_a, half, args.speedup, seed=2)
|
||||
# Groundedness is per physical model, so it swaps sides along with it.
|
||||
first = run_half(args.godot_bin, model_a, model_b, half, args.speedup, seed=1,
|
||||
grounded_a=args.grounded_a, grounded_b=args.grounded_b)
|
||||
second = run_half(args.godot_bin, model_b, model_a, half, args.speedup, seed=2,
|
||||
grounded_a=args.grounded_b, grounded_b=args.grounded_a)
|
||||
|
||||
record = {
|
||||
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds"),
|
||||
|
||||
Reference in New Issue
Block a user