## ZoneManager — 地面效果区域管理（Autoload: ZoneManager）
## 权威来源：architecture_design.md §8 ADR-R5-N1（P6-N71 tick 用 while + -=）
## SoA PackedFloat32Array stride=8；基于 SpatialGrid 空间索引（禁用 Area2D）
## [0]=cx [1]=cy [2]=radius [3]=status_id [4]=duration [5]=tick_interval [6]=tick_accum [7]=owner_id
##
## 注：本 GDScript 外壳承载 tick 热循环为 S5 功能验收路径（P-S5-04）；
##    C# 子节点 ZoneManagerCs 驱动的压力路径（P-S5-ZM-01，64 区×1000 敌 <1ms）留作后续优化，
##    与项目既有 C# 回退策略（R-08）一致。
extends Node

const ZONE_STRIDE:  int = 8
const MAX_ZONES:    int = 64

var _zone_data:    PackedFloat32Array = PackedFloat32Array()
var _active_zones: int = 0

func _ready() -> void:
	_zone_data.resize(MAX_ZONES * ZONE_STRIDE)

## 生成一个地面效果区域；满员时顶掉最旧（index 0 前移）
func spawn_zone(cx: float, cy: float, radius: float, status_id: int,
		duration: float, tick_interval: float, owner_id: int) -> void:
	if _active_zones >= MAX_ZONES:
		# 顶掉最旧 Zone（index 0），整体前移（O(N)，N≤64）
		for j in range(ZONE_STRIDE, _active_zones * ZONE_STRIDE):
			_zone_data[j - ZONE_STRIDE] = _zone_data[j]
		_active_zones -= 1
	var base: int = _active_zones * ZONE_STRIDE
	_zone_data[base + 0] = cx
	_zone_data[base + 1] = cy
	_zone_data[base + 2] = radius
	_zone_data[base + 3] = float(status_id)
	_zone_data[base + 4] = duration
	_zone_data[base + 5] = maxf(0.01, tick_interval)
	_zone_data[base + 6] = 0.0   # tick_accum
	_zone_data[base + 7] = float(owner_id)
	_active_zones += 1

func _physics_process(delta: float) -> void:
	if _active_zones == 0:
		return
	var i: int = 0
	while i < _active_zones:
		var base: int = i * ZONE_STRIDE
		_zone_data[base + 4] -= delta   # duration 倒计时
		_zone_data[base + 6] += delta   # tick_accum 累积

		# 速率限制：外层 if 守卫 + 内层 while + -= tick_interval（P6-N71，保留余量精度）
		# SpatialGrid.query_circle 仅在 if 守卫内查询一次，不在 while 内重复查询
		if _zone_data[base + 6] >= _zone_data[base + 5]:
			var cx:  float = _zone_data[base + 0]
			var cy:  float = _zone_data[base + 1]
			var rad: float = _zone_data[base + 2]
			var sid: int   = int(_zone_data[base + 3])
			var oid: int   = int(_zone_data[base + 7])
			var targets: PackedInt32Array = SpatialGrid.query_circle(Vector2(cx, cy), rad)
			while _zone_data[base + 6] >= _zone_data[base + 5]:
				_zone_data[base + 6] -= _zone_data[base + 5]  # -= tick_interval 保留余量
				for t_id in targets:
					StatusManager.apply(t_id, sid, 1, -1.0, oid)  # stacks=1, duration=默认, owner

		if _zone_data[base + 4] <= 0.0:  # duration 耗尽 → swap-and-pop 移除 + VFX 淡出
			VFXManager.play("zone_expire", Vector2(_zone_data[base + 0], _zone_data[base + 1]))
			if i < _active_zones - 1:
				var last: int = (_active_zones - 1) * ZONE_STRIDE
				for k in ZONE_STRIDE:
					_zone_data[base + k] = _zone_data[last + k]
			_active_zones -= 1
		else:
			i += 1

func get_active_count() -> int:
	return _active_zones

func reset() -> void:
	_active_zones = 0  # 惰性清理：SoA 数据不清零，_active_zones 控制有效范围
