extends Object ## Pure resolver for digit hotbar → cast intent (NEO-31). [code]main.gd[/code] delegates here so ## GdUnit can cover unbound slots and [code]lockedTargetId[/code] sourcing without booting ## the scene. ## No [code]class_name[/code] — match [code]prototype_target_constants.gd[/code] preload pattern. const KEY_OK := "ok" const KEY_REASON := "reason" const KEY_SLOT_INDEX := "slotIndex" const KEY_ABILITY_ID := "abilityId" const KEY_TARGET_ID := "targetId" const REASON_INVALID_SLOT := "invalid_slot_index" const REASON_EMPTY_SLOT := "empty_slot" ## [param slots] snapshot from [code]HotbarState.slots_snapshot()[/code] (fixed length, entries ## [code]null[/code] or [String] ability ids). ## [param target_cached_state] from [code]TargetSelectionClient.cached_state()[/code] ## (may be empty). ## Returns [code]{ "ok": true, "slotIndex", "abilityId", "targetId" }[/code] or ## [code]{ "ok": false, "reason" }[/code]. static func resolve(slot_index: int, slots: Array, target_cached_state: Dictionary) -> Dictionary: if slot_index < 0 or slot_index >= slots.size(): return {KEY_OK: false, KEY_REASON: REASON_INVALID_SLOT} var ability_variant: Variant = slots[slot_index] if ( ability_variant == null or not (ability_variant is String) or (ability_variant as String).strip_edges().is_empty() ): return {KEY_OK: false, KEY_REASON: REASON_EMPTY_SLOT} var ability_id: String = (ability_variant as String).strip_edges() var target_id: Variant = null var locked: Variant = target_cached_state.get("lockedTargetId", null) if locked is String and not (locked as String).is_empty(): target_id = locked as String return { KEY_OK: true, KEY_SLOT_INDEX: slot_index, KEY_ABILITY_ID: ability_id, KEY_TARGET_ID: target_id, }