feat(gauntlet): shrink arena per phase 20x20 -> 18x18 -> 7x7; sticky cells block movement
CI / Export Linux (push) Failing after 4s
CI / Export Windows (push) Failing after 36s
CI / Export Android (push) Failing after 38s
Test Suite / Unit Tests (GUT) (push) Failing after 26s
Test Suite / Integration Tests (push) Failing after 29s
Test Suite / Code Style Check (push) Failing after 38s
CI / Create Release (push) Has been skipped
Test Suite / Security Scan (push) Failing after 51s

This commit is contained in:
2026-07-02 18:07:40 +08:00
parent 419771c50b
commit d40a242cbc
2 changed files with 63 additions and 8 deletions
+61 -5
View File
@@ -340,9 +340,12 @@ func _start_phase(phase: Phase) -> void:
var phase_name = _phase_to_string(phase)
print("[Gauntlet] Phase changed to: ", phase_name)
if _can_rpc():
if _can_rpc() and multiplayer.is_server():
rpc("sync_phase", int(phase), phase_name)
# Update phase explicitly with setup_arena
_shrink_arena()
emit_signal("phase_changed", int(phase), phase_name)
func _phase_to_string(phase: Phase) -> String:
@@ -361,6 +364,14 @@ func sync_phase(phase_index: int, phase_name: String) -> void:
if not is_active:
activate_client_side()
current_phase = phase_index as Phase
if not multiplayer.is_server():
var bounds = get_arena_bounds()
for x in range(ARENA_COLUMNS):
for z in range(ARENA_ROWS):
var pos = Vector2i(x, z)
if pos.x <= bounds.min or pos.x >= bounds.max or pos.y <= bounds.min or pos.y >= bounds.max:
if not sticky_cells.has(pos):
sticky_cells[pos] = true
_update_hud_phase(phase_name)
# =============================================================================
@@ -419,11 +430,12 @@ func _apply_arena_setup() -> void:
continue
# Boundary walls: perimeter (row 0, row 19, col 0, col 19)
if x == 0 or x == ARENA_COLUMNS - 1 or z == 0 or z == ARENA_ROWS - 1:
if pos.x <= 0 or pos.x >= 19 or pos.y <= 0 or pos.y >= 19:
# Also make border walls visually walkable floors instead of red blocks
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WALKABLE)
gridmap.set_cell_item(Vector3i(x, 1, z), -1)
gridmap.set_cell_item(Vector3i(x, 2, z), -1)
gridmap.set_cell_item(Vector3i(x, 2, z), TILE_STICKY)
sticky_cells[pos] = true
continue
# Interior: walkable floor
@@ -1065,9 +1077,12 @@ func is_cleansed_cell(pos: Vector2i) -> bool:
func cell_state(pos: Vector2i) -> CellState:
"""Logical state of a playable cell (v2 ground-growth model)."""
var b = get_arena_bounds()
if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max:
return CellState.STICKY
if _is_npc_zone(pos) or _is_boundary(pos):
return CellState.BLOCKED
if sticky_cells.has(pos):
if is_sticky_cell(pos):
return CellState.STICKY
if cleansed_cells.has(pos):
return CellState.CLEANSED
@@ -1091,8 +1106,37 @@ func _tick_cleansed_cells(delta: float) -> void:
for pos in expired:
cleansed_cells.erase(pos)
func get_arena_bounds() -> Dictionary:
match current_phase:
Phase.OPEN_ARENA:
return {"min": 0, "max": 19} # 20x20
Phase.ROUTE_PRESSURE:
return {"min": 1, "max": 18} # 18x18
Phase.SURVIVAL_ENDGAME:
return {"min": 6, "max": 12} # 7x7
return {"min": 0, "max": 19}
func _shrink_arena() -> void:
if not multiplayer.is_server(): return
var b = get_arena_bounds()
var new_sticky = []
for x in range(ARENA_COLUMNS):
for z in range(ARENA_ROWS):
var pos = Vector2i(x, z)
if _is_npc_zone(pos) or _is_boundary(pos):
continue
if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max:
if not sticky_cells.has(pos):
new_sticky.append(pos)
if new_sticky.size() > 0:
if _can_rpc() and multiplayer.is_server():
rpc("sync_growth_apply", new_sticky)
else:
sync_growth_apply(new_sticky)
func _is_boundary(pos: Vector2i) -> bool:
return pos.x == 0 or pos.x == ARENA_COLUMNS - 1 or pos.y == 0 or pos.y == ARENA_ROWS - 1
return pos.x <= 0 or pos.x >= ARENA_COLUMNS - 1 or pos.y <= 0 or pos.y >= ARENA_ROWS - 1
# =============================================================================
# Coverage tracking (v2 target: 70-75%, down from v1's 80%)
@@ -1103,12 +1147,15 @@ const COVERAGE_TARGET_MAX: float = 0.75
func playable_cell_count() -> int:
"""Number of cells that can ever become sticky (interior, minus NPC zone)."""
var b = get_arena_bounds()
var count := 0
for x in range(ARENA_COLUMNS):
for z in range(ARENA_ROWS):
var pos := Vector2i(x, z)
if _is_boundary(pos) or _is_npc_zone(pos):
continue
if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max:
continue
count += 1
return count
@@ -1132,6 +1179,9 @@ const FORCED_TRAP_WINDOW: float = 30.0 # final seconds where trapping is allowed
func _is_cell_passable(pos: Vector2i, extra_sticky: Dictionary = {}) -> bool:
"""Can a player stand on / move through this cell, given a hypothetical sticky set?"""
var b = get_arena_bounds()
if pos.x <= b.min or pos.x >= b.max or pos.y <= b.min or pos.y >= b.max:
return false
if _is_boundary(pos) or _is_npc_zone(pos):
return false
if sticky_cells.has(pos) or extra_sticky.has(pos):
@@ -1390,15 +1440,21 @@ func _bubble_score_unfair_trap(pos: Vector2i) -> float:
func _bubble_blast_cells(center: Vector2i) -> Array:
"""The 3x3 (radius 1) sticky cells a bubble at `center` would create,
clipped to passable/playable cells."""
var b = get_arena_bounds()
var cells: Array = []
for dx in range(-BUBBLE_EXPLOSION_RADIUS, BUBBLE_EXPLOSION_RADIUS + 1):
for dz in range(-BUBBLE_EXPLOSION_RADIUS, BUBBLE_EXPLOSION_RADIUS + 1):
var c := center + Vector2i(dx, dz)
if _is_boundary(c) or _is_npc_zone(c):
continue
if c.x <= b.min or c.x >= b.max or c.y <= b.min or c.y >= b.max:
continue
cells.append(c)
return cells
func _bubble_footprint(center: Vector2i) -> Array:
return _bubble_blast_cells(center)
func _any_cleanser_holder_near(pos: Vector2i) -> bool:
"""True if a player holding a Cleanser charge is within the camping region."""
for player in get_tree().get_nodes_in_group("Players"):
+2 -3
View File
@@ -165,9 +165,8 @@ func simple_move_to(grid_position: Vector2i) -> bool:
gm.use_cleanser_cell(pid)
print("[Move] Cleanser cleared sticky cell at %s (%d cells left)" % [grid_position, gm.cleanser_cells_left.get(pid, 0)])
else:
print("[Move] Player stepping into sticky cell at %s — slowed" % grid_position)
if player.is_multiplayer_authority() or multiplayer.is_server():
gm.apply_sticky_slow(player)
print("[Move] Failed: Blocked by Gauntlet Sticky cell at %s" % grid_position)
return false
rotate_towards_target(grid_position)