rename gauntlet→candy_survival + rewrite Candy Survival per boss design

- Rename enum GAUNTLET→CANDY_SURVIVAL, all gauntlet_*→candy_survival_*
- Rename files: gauntlet_manager→candy_survival_manager, candy_cannon→candy_survival_npc, gauntlet.tscn→candy_survival.tscn
- Rewrite candy_survival_manager.gd: blueprints, candy stack, Mekton delivery, Sugar Rush, knock/ghost charges, sticky-as-wall
- Update player_movement_manager.gd: smack→knock system, ghost integration
- All Candy Survival issues (#54-57, #65-70) retitled per boss design
- Shared managers (goals_cycle, goal, player_race, playerboard, turn) untouched
This commit is contained in:
god
2026-07-06 01:28:39 +08:00
parent 114748a54f
commit 528e22875d
33 changed files with 731 additions and 2181 deletions
+25 -25
View File
@@ -8,38 +8,38 @@ class_name BotStrategicPlanner
var actor: Node3D
var enhanced_gridmap: Node
# Optional explicit gauntlet_manager binding (set by tests to avoid scene-tree
# traversal collisions; production code uses _get_gauntlet_manager() instead).
var gauntlet_manager_override: Node = null
# Optional explicit candy_survival_manager binding (set by tests to avoid scene-tree
# traversal collisions; production code uses _get_candy_survival_manager() instead).
var candy_survival_manager_override: Node = null
# Tile type constants
const GOAL_TILES = [7, 8, 9, 10] # Heart, Diamond, Star, Coin
const HOLO_TILES = [11, 12, 13, 14] # Power-up holo tiles
# Gauntlet overlay layer (v2 ground-growth model — sticky/telegraph on layer 2).
# Candy Survival overlay layer (v2 ground-growth model — sticky/telegraph on layer 2).
# Bots must avoid these cells or use Ghost mode to cross.
const GAUNTLET_OVERLAY_LAYER: int = 2
const CANDY_SURVIVAL_OVERLAY_LAYER: int = 2
const TILE_STICKY: int = 17
const TILE_TELEGRAPH: int = 18
# =============================================================================
# Gauntlet mode helpers (#075 — Bot AI: Sticky Avoidance & Pathfinding)
# Candy Survival mode helpers (#075 — Bot AI: Sticky Avoidance & Pathfinding)
# =============================================================================
func is_gauntlet_mode() -> bool:
return LobbyManager and LobbyManager.is_game_mode(GameMode.Mode.GAUNTLET)
func is_candy_survival_mode() -> bool:
return LobbyManager and LobbyManager.is_game_mode(GameMode.Mode.CANDY_SURVIVAL)
func _get_gauntlet_manager() -> Node:
"""Resolve the active GauntletManager.
func _get_candy_survival_manager() -> Node:
"""Resolve the active CandySurvivalManager.
Order of resolution:
1. Explicit `gauntlet_manager_override` (used by tests).
2. Walk actor's ancestors for any node containing a GauntletManager child
1. Explicit `candy_survival_manager_override` (used by tests).
2. Walk actor's ancestors for any node containing a CandySurvivalManager child
(production path — robust to non-standard scene trees).
3. Fallback: scan /root children for a GauntletManager.
3. Fallback: scan /root children for a CandySurvivalManager.
"""
if gauntlet_manager_override and is_instance_valid(gauntlet_manager_override):
return gauntlet_manager_override
if candy_survival_manager_override and is_instance_valid(candy_survival_manager_override):
return candy_survival_manager_override
var root: Node = null
if actor and actor.is_inside_tree():
root = actor.get_tree().get_root()
@@ -47,7 +47,7 @@ func _get_gauntlet_manager() -> Node:
# nested under Main → Arena → Player).
var n: Node = actor.get_parent()
while n:
var gm = n.get_node_or_null("GauntletManager")
var gm = n.get_node_or_null("CandySurvivalManager")
if gm:
return gm
n = n.get_parent()
@@ -56,7 +56,7 @@ func _get_gauntlet_manager() -> Node:
# Last-resort scan of root children (helps in unusual scene trees).
for child in root.get_children():
if child.name.begins_with("Main") or child.name.begins_with("BotTestMain"):
var gm2 = child.get_node_or_null("GauntletManager")
var gm2 = child.get_node_or_null("CandySurvivalManager")
if gm2:
return gm2
return null
@@ -76,17 +76,17 @@ func _is_overlay_unsafe(pos: Vector2i) -> bool:
"""True if the cell carries a sticky or telegraphed overlay on layer 2."""
if not enhanced_gridmap:
return false
var item = enhanced_gridmap.get_cell_item(Vector3i(pos.x, GAUNTLET_OVERLAY_LAYER, pos.y))
var item = enhanced_gridmap.get_cell_item(Vector3i(pos.x, CANDY_SURVIVAL_OVERLAY_LAYER, pos.y))
return item == TILE_STICKY or item == TILE_TELEGRAPH
func _is_cell_unsafe_in_gauntlet(pos: Vector2i) -> bool:
"""Cell is unsafe in Gauntlet if it's sticky/telegraphed — unless the bot's
func _is_cell_unsafe_in_candy_survival(pos: Vector2i) -> bool:
"""Cell is unsafe in Candy Survival if it's sticky/telegraphed — unless the bot's
Ghost mode is active (grants sticky bypass)."""
if not is_gauntlet_mode():
if not is_candy_survival_mode():
return false
if _is_bot_ghost_active():
return false
var gm = _get_gauntlet_manager()
var gm = _get_candy_survival_manager()
if gm and gm.has_method("is_sticky_cell") and gm.is_sticky_cell(pos):
return true
return _is_overlay_unsafe(pos)
@@ -101,7 +101,7 @@ func _count_unsafe_neighbors(pos: Vector2i) -> int:
func should_activate_ghost_now() -> bool:
"""True if the bot is boxed in / about to be sealed and should use Ghost powerup."""
if not is_gauntlet_mode():
if not is_candy_survival_mode():
return false
if not _bot_has_ghost_powerup():
return false
@@ -613,12 +613,12 @@ func _is_valid_move_target(pos: Vector2i, ignore_players: bool = false) -> bool:
if not ignore_players and actor.is_position_occupied(pos):
return false
# Gauntlet mode (#075): reject cells that are sticky or telegraphed —
# Candy Survival mode (#075): reject cells that are sticky or telegraphed —
# stepping onto them either traps the bot or strands it within 1s.
# Safety applies even when ignore_players is true (a sticky cell is unsafe
# regardless of whether another player is on it). Ghost-active bots are
# exempt via the helper.
if _is_cell_unsafe_in_gauntlet(pos):
if _is_cell_unsafe_in_candy_survival(pos):
return false
return true