mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-12 15:53:42 +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:
@@ -17,6 +17,16 @@ extends ShipController
|
||||
# Uniform noise magnitude added to each action axis (0 = play at full skill).
|
||||
@export_range(0.0, 1.0) var action_noise: float = 0.0
|
||||
|
||||
# Must mirror whatever the model was actually trained with (see
|
||||
# ShipAIController's identical exports on the training side, curriculum
|
||||
# stages 1-2 in TRAINING.md). A model trained grounded (mask on) never got a
|
||||
# reward gradient on these axes, so its raw output there is untrained noise —
|
||||
# leaving this true for such a model doesn't make it fly well, it just lets
|
||||
# that noise reach the ship instead of being discarded like it was in
|
||||
# training. Set false to match a grounded-trained model's actual behaviour.
|
||||
@export var allow_vertical := true
|
||||
@export var allow_pitch_roll := true
|
||||
|
||||
var _policy: PolicyNetwork
|
||||
var _action := ShipAction.new()
|
||||
var _ticks_until_decision := 0
|
||||
@@ -53,13 +63,13 @@ func _decide() -> void:
|
||||
# gymnasium orders by SORTED key name — rotation xyz, thrust xyz, turbo
|
||||
# (> 0 means on) — NOT ShipAction's thrust-first declaration order.
|
||||
_action.rotation = Vector3(
|
||||
_axis(out[0]),
|
||||
_axis(out[0]) if allow_pitch_roll else 0.0,
|
||||
_axis(out[1]),
|
||||
_axis(out[2])
|
||||
_axis(out[2]) if allow_pitch_roll else 0.0
|
||||
)
|
||||
_action.thrust = Vector3(
|
||||
_axis(out[3]),
|
||||
_axis(out[4]),
|
||||
_axis(out[4]) if allow_vertical else 0.0,
|
||||
_axis(out[5])
|
||||
)
|
||||
_action.turbo = out[6] > 0.0
|
||||
|
||||
@@ -91,6 +91,12 @@ var _agents: Array[ShipAIController] = []
|
||||
# Eval mode state (see header comment)
|
||||
var _eval := false
|
||||
var _eval_models: Array[String] = ["", ""]
|
||||
# Per-model locomotion mask — must match how each model was actually trained
|
||||
# (see AIShipController's identical exports), so a stage 1/2 (grounded)
|
||||
# candidate isn't unfairly penalized by untrained aerial noise during eval
|
||||
# that its training environment never had.
|
||||
var _eval_allow_vertical: Array[bool] = [true, true]
|
||||
var _eval_allow_pitch_roll: Array[bool] = [true, true]
|
||||
var _eval_episodes := 20
|
||||
var _eval_goals := {0: 0, 1: 0}
|
||||
var _eval_draws := 0
|
||||
@@ -123,6 +129,8 @@ func _start() -> void:
|
||||
for team in [0, 1]:
|
||||
var bot := AIShipController.new()
|
||||
bot.model_path = _eval_models[team]
|
||||
bot.allow_vertical = _eval_allow_vertical[team]
|
||||
bot.allow_pitch_roll = _eval_allow_pitch_roll[team]
|
||||
spawn_ship(team, 0, bot)
|
||||
return
|
||||
|
||||
@@ -161,6 +169,10 @@ func _parse_eval_args() -> void:
|
||||
_eval_models[0] = args["eval_model_a"]
|
||||
_eval_models[1] = args["eval_model_b"]
|
||||
_eval_episodes = int(args.get("eval_episodes", str(_eval_episodes)))
|
||||
_eval_allow_vertical[0] = _typed_like(args.get("eval_allow_vertical_a", "true"), true)
|
||||
_eval_allow_vertical[1] = _typed_like(args.get("eval_allow_vertical_b", "true"), true)
|
||||
_eval_allow_pitch_roll[0] = _typed_like(args.get("eval_allow_pitch_roll_a", "true"), true)
|
||||
_eval_allow_pitch_roll[1] = _typed_like(args.get("eval_allow_pitch_roll_b", "true"), true)
|
||||
|
||||
|
||||
# TrainingMode @export names a curriculum run may override from the cmdline.
|
||||
|
||||
Reference in New Issue
Block a user