mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 00:14:00 +00:00
fix(*): jitter kickoff resets to break deterministic same-model mirror matches
GameMode.reset_ball()/reset_ships() teleported to exact, identical spawn transforms every kickoff. Combined with deterministic bot inference (action_noise = 0 by default), two ships running the same policy from a mirror-symmetric state produced mirrored, non-diverging play instead of a real contest — most visible when both sides use the same exported model. Adds a small position/yaw jitter (well under anything a player would notice as "not a real kickoff") so kickoff-style resets stop being bit-for-bit identical.
This commit is contained in:
@@ -85,15 +85,36 @@ func spawn_camera_rig(target: Ship) -> ShipCameraRig:
|
||||
return rig
|
||||
|
||||
|
||||
# Tiny per-reset randomization, well below anything a player would notice as
|
||||
# "not a real kickoff" — just enough that two ships running the identical
|
||||
# deterministic AI policy (action_noise = 0) don't start every kickoff from a
|
||||
# bit-for-bit mirror-symmetric state. A perfectly symmetric state feeds both
|
||||
# controllers identical (canonicalized) observations, so they emit mirrored
|
||||
# actions and can lock into a repetitive, non-scoring stalemate — much more
|
||||
# visible bot-vs-bot (same model both sides) than bot-vs-human, since a human
|
||||
# never satisfies "identical policy" in the first place. See training_mode.gd's
|
||||
# _end_eval_episode, which randomizes eval episode states for the same reason.
|
||||
const KICKOFF_POSITION_JITTER := 0.3
|
||||
const KICKOFF_YAW_JITTER := deg_to_rad(15.0)
|
||||
|
||||
|
||||
func reset_ball() -> void:
|
||||
if is_instance_valid(ball):
|
||||
_reset_body(ball, arena.get_ball_spawn())
|
||||
_reset_body(ball, _jittered(arena.get_ball_spawn(), KICKOFF_POSITION_JITTER, 0.0))
|
||||
|
||||
|
||||
func reset_ships() -> void:
|
||||
for ship in ships:
|
||||
if is_instance_valid(ship):
|
||||
_reset_body(ship, _ship_spawn_transforms[ship])
|
||||
_reset_body(ship, _jittered(_ship_spawn_transforms[ship], KICKOFF_POSITION_JITTER, KICKOFF_YAW_JITTER))
|
||||
|
||||
|
||||
func _jittered(to: Transform3D, position_jitter: float, yaw_jitter: float) -> Transform3D:
|
||||
var offset := Vector3(randf_range(-position_jitter, position_jitter), 0.0, randf_range(-position_jitter, position_jitter))
|
||||
var basis := to.basis
|
||||
if yaw_jitter > 0.0:
|
||||
basis = basis.rotated(Vector3.UP, randf_range(-yaw_jitter, yaw_jitter))
|
||||
return Transform3D(basis, to.origin + offset)
|
||||
|
||||
|
||||
func _reset_body(body: RigidBody3D, to: Transform3D) -> void:
|
||||
|
||||
Reference in New Issue
Block a user