From 6ac6b0b967ee5cb5b96a6362fdd98b31c4f7c3b6 Mon Sep 17 00:00:00 2001 From: Natalie Date: Sat, 18 Apr 2026 21:25:26 -0700 Subject: [PATCH] =?UTF-8?q?feat(management):=20=E2=9C=A8=20add=20symmetric?= =?UTF-8?q?=20difficulty=20per-player=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- scripts/apricot-run.sh | 7 ++++- .../src/modules/management/turn_processor.gd | 30 +++++++++++-------- .../management/turn_processor_helpers.gd | 24 ++++++++------- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/scripts/apricot-run.sh b/scripts/apricot-run.sh index 494a78f9..d681cdd6 100755 --- a/scripts/apricot-run.sh +++ b/scripts/apricot-run.sh @@ -197,8 +197,13 @@ case "${MODE}" in SEEDS="${2:-10}"; TURNS="${3:-300}" GPU_ENV="AI_GPU_ROLLOUT=${AI_GPU_ROLLOUT:-false}" echo "[$(date +%H:%M:%S)] difficulty=${DIFF_TIER} batch: ${SEEDS} seeds T${TURNS} PARALLEL=${PARALLEL} ${GPU_ENV}" + # AI_DIFFICULTY_P0 + AI_DIFFICULTY_P1 apply the modifier to BOTH players + # (including the human-slot player 0 which is_human=true). This is + # required for symmetric Easy-vs-Easy / Hard-vs-Hard tier_peak differentiation. ssh "${APRICOT}" "set -euo pipefail; cd '${SCRATCH_ABS}' && \ - AI_USE_MCTS=true AI_DIFFICULTY='${DIFF_TIER}' ${GPU_ENV} PARALLEL=${PARALLEL} \ + AI_USE_MCTS=true AI_DIFFICULTY='${DIFF_TIER}' \ + AI_DIFFICULTY_P0='${DIFF_TIER}' AI_DIFFICULTY_P1='${DIFF_TIER}' \ + ${GPU_ENV} PARALLEL=${PARALLEL} \ bash tools/autoplay-batch.sh ${SEEDS} ${TURNS} ${RESULTS_ABS}/difficulty-${DIFF_TIER} 2>&1 | tail -30" ;; difficulty-asym) diff --git a/src/game/engine/src/modules/management/turn_processor.gd b/src/game/engine/src/modules/management/turn_processor.gd index 4a817c19..fc4a31bc 100644 --- a/src/game/engine/src/modules/management/turn_processor.gd +++ b/src/game/engine/src/modules/management/turn_processor.gd @@ -52,13 +52,16 @@ func _process_production(player: RefCounted) -> void: # Player if game_map == null: return - # Apply difficulty modifier for AI players (per-player override takes precedence) + # Per-player override applies to any player (including human in difficulty batches). + # Global ai_difficulty_modifier only applies to non-human players. var prod_modifier: float = 1.0 - if player is PlayerScript and not player.is_human: - var per_player: float = float( - GameState.ai_per_player_production_mult.get(player.index, 0.0) - ) - prod_modifier = per_player if per_player > 0.0 else GameState.ai_difficulty_modifier + var _per_player_prod: float = float( + GameState.ai_per_player_production_mult.get(player.index, 0.0) + ) + if _per_player_prod > 0.0: + prod_modifier = _per_player_prod + elif player is PlayerScript and not player.is_human: + prod_modifier = GameState.ai_difficulty_modifier # Unhappy penalty: -25% production when happiness < 0 if player.happiness < 0: prod_modifier *= 0.75 @@ -158,13 +161,16 @@ func _process_research(player: RefCounted) -> void: # Player if EnvConfig.get_bool("FORCE_UNLIMITED_RESEARCH"): player.research_progress = 999999 - # Apply difficulty modifier for AI players (per-player override takes precedence) + # Per-player override applies to any player (including human in difficulty batches). + # Global ai_research_modifier only applies to non-human players. var sci_modifier: float = 1.0 - if player is PlayerScript and not player.is_human: - var per_player: float = float( - GameState.ai_per_player_research_mult.get(player.index, 0.0) - ) - sci_modifier = per_player if per_player > 0.0 else GameState.ai_research_modifier + var _per_player_sci: float = float( + GameState.ai_per_player_research_mult.get(player.index, 0.0) + ) + if _per_player_sci > 0.0: + sci_modifier = _per_player_sci + elif player is PlayerScript and not player.is_human: + sci_modifier = GameState.ai_research_modifier player.research_progress += int(player.science_per_turn * sci_modifier) diff --git a/src/game/engine/src/modules/management/turn_processor_helpers.gd b/src/game/engine/src/modules/management/turn_processor_helpers.gd index 924374df..0529f54b 100644 --- a/src/game/engine/src/modules/management/turn_processor_helpers.gd +++ b/src/game/engine/src/modules/management/turn_processor_helpers.gd @@ -22,11 +22,13 @@ static func process_production( if game_map == null: return var prod_modifier: float = 1.0 - if player is PlayerScript and not player.is_human: - var per_player: float = float( - GameState.ai_per_player_production_mult.get(player.index, 0.0) - ) - prod_modifier = per_player if per_player > 0.0 else GameState.ai_difficulty_modifier + var _per_player_prod: float = float( + GameState.ai_per_player_production_mult.get(player.index, 0.0) + ) + if _per_player_prod > 0.0: + prod_modifier = _per_player_prod + elif player is PlayerScript and not player.is_human: + prod_modifier = GameState.ai_difficulty_modifier for city: RefCounted in player.cities: if not city is CityScript: continue @@ -358,11 +360,13 @@ static func build_tile_yields_json( static func _calculate_science_income(player: RefCounted) -> int: var sci_modifier: float = 1.0 - if player is PlayerScript and not player.is_human: - var per_player: float = float( - GameState.ai_per_player_research_mult.get(player.index, 0.0) - ) - sci_modifier = per_player if per_player > 0.0 else GameState.ai_research_modifier + var _per_player_sci: float = float( + GameState.ai_per_player_research_mult.get(player.index, 0.0) + ) + if _per_player_sci > 0.0: + sci_modifier = _per_player_sci + elif player is PlayerScript and not player.is_human: + sci_modifier = GameState.ai_research_modifier var science: int = int(player.science_per_turn * sci_modifier) var game_map: RefCounted = GameState.get_game_map() if game_map == null: