refactor(@projects/@magic-civilization): 🧹 p3-18 P5b — trim vestigial embark inputs from the action FFI

Completes single-source on the action bridge. With auto-embark (no explicit
Embark action), the embark/adjacency inputs to legal_actions_for / can_invoke were
dead:
- api-gdext: drop is_embarked / adjacent_water / adjacent_land params from both
  #[func]s (+ the consume lines + doc).
- GDScript callers updated to the new arity: unit.gd (get_legal_actions +
  can_invoke_action), unit_panel.gd (legal_actions_for + its dead arg vars and the
  three dead _get_* helpers), test_unit_actions.gd (4 calls).
- unit.gd: remove the now-dead has_adjacent_water/has_adjacent_land fields (never
  set — their _refresh_unit_terrain_context setter is long gone) + their serialize/
  deserialize; test_unit_serialize.gd updated. is_embarked stays (real state).

Verified: cargo check green; dylib rebuilt + deployed; canonical GUT suite 745
tests / 732 passing / 0 failing (13 pending). No arg-mismatch errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-25 07:19:26 -04:00
parent c141a86b77
commit 1f3f535e10
5 changed files with 10 additions and 66 deletions

View file

@ -249,9 +249,6 @@ func _refresh_action_buttons() -> void:
var is_fort: bool = _get_is_fortified(_selected_unit)
var is_sentry: bool = _get_is_sentrying(_selected_unit)
var is_deployed: bool = _get_is_deployed(_selected_unit)
var is_embarked: bool = _get_is_embarked(_selected_unit)
var adj_water: bool = _get_has_adjacent_water(_selected_unit)
var adj_land: bool = _get_has_adjacent_land(_selected_unit)
var posture: Dictionary = {
"is_aiming": _get_is_aiming(_selected_unit),
@ -272,7 +269,7 @@ func _refresh_action_buttons() -> void:
var bridge: RefCounted = ClassDB.instantiate("GdUnitActions") as RefCounted
var raw: Array = bridge.legal_actions_for(
unit_type_str, keywords_str, has_movement, is_fort, is_sentry,
is_deployed, is_embarked, adj_water, adj_land, posture
is_deployed, posture
)
for entry: Variant in raw:
actions.append(entry as Dictionary)
@ -786,24 +783,6 @@ func _get_is_deployed(unit: RefCounted) -> bool:
return bool(unit.get("is_deployed") if "is_deployed" in unit else false)
func _get_is_embarked(unit: RefCounted) -> bool:
if unit is UnitScript:
return (unit as UnitScript).is_embarked
return bool(unit.get("is_embarked") if "is_embarked" in unit else false)
func _get_has_adjacent_water(unit: RefCounted) -> bool:
if unit is UnitScript:
return (unit as UnitScript).has_adjacent_water
return bool(unit.get("has_adjacent_water") if "has_adjacent_water" in unit else false)
func _get_has_adjacent_land(unit: RefCounted) -> bool:
if unit is UnitScript:
return (unit as UnitScript).has_adjacent_land
return bool(unit.get("has_adjacent_land") if "has_adjacent_land" in unit else false)
## p2-53g: fire arrows toggle state — read by legal_actions_for bridge.
func _get_is_fire_arrows(unit: RefCounted) -> bool:
if unit is UnitScript:

View file

@ -69,14 +69,9 @@ var fortified_turns: int = 0
var is_sentrying: bool = false
## True when a siege unit is in deployed posture (cannot move, can Bombard).
var is_deployed: bool = false
## True when an amphibious unit is on a water tile.
## True when a land unit is embarked on a water tile (p3-18). Mirrors the Rust
## MapUnit::is_embarked; round-tripped for save/load.
var is_embarked: bool = false
## True when the unit's current tile has at least one adjacent water hex.
## Populated each turn by the world-map via WorldMap._refresh_unit_terrain_context().
var has_adjacent_water: bool = false
## True when the unit's current tile has at least one adjacent land hex.
## Populated each turn by the world-map via WorldMap._refresh_unit_terrain_context().
var has_adjacent_land: bool = false
# ── Veterancy ─────────────────────────────────────────────────────────
var xp: int = 0
@ -318,9 +313,6 @@ func get_legal_actions() -> Array[Dictionary]:
is_fortified,
is_sentrying,
is_deployed,
is_embarked,
has_adjacent_water,
has_adjacent_land,
{}
)
@ -337,9 +329,6 @@ func can_invoke_action(kind: String) -> bool:
is_fortified,
is_sentrying,
is_deployed,
is_embarked,
has_adjacent_water,
has_adjacent_land,
kind
)
@ -444,8 +433,6 @@ func to_save_dict() -> Dictionary:
"is_sentrying": is_sentrying,
"is_deployed": is_deployed,
"is_embarked": is_embarked,
"has_adjacent_water": has_adjacent_water,
"has_adjacent_land": has_adjacent_land,
"xp": xp,
"level": level,
"base_str": base_str,
@ -505,8 +492,6 @@ func from_save_dict(data: Dictionary) -> void:
is_sentrying = bool(data.get("is_sentrying", is_sentrying))
is_deployed = bool(data.get("is_deployed", is_deployed))
is_embarked = bool(data.get("is_embarked", is_embarked))
has_adjacent_water = bool(data.get("has_adjacent_water", has_adjacent_water))
has_adjacent_land = bool(data.get("has_adjacent_land", has_adjacent_land))
xp = int(data.get("xp", xp))
level = int(data.get("level", level))
base_str = int(data.get("base_str", base_str))

View file

@ -116,7 +116,7 @@ func test_gd_unit_actions_bridge_legal_actions_for_military() -> void:
pass_test("GdUnitActions not loaded in headless — skipping bridge test")
return
var bridge: RefCounted = ClassDB.instantiate("GdUnitActions") as RefCounted
var actions: Array[Dictionary] = bridge.legal_actions_for("military", "", true, false, false, false, false, false, false, {})
var actions: Array[Dictionary] = bridge.legal_actions_for("military", "", true, false, false, false, {})
assert_true(actions.size() >= 2, "military unit with movement gets multiple actions")
var kinds: Array[String] = []
for d: Dictionary in actions:
@ -130,7 +130,7 @@ func test_gd_unit_actions_bridge_founder_gets_found_city() -> void:
pass_test("GdUnitActions not loaded in headless — skipping bridge test")
return
var bridge: RefCounted = ClassDB.instantiate("GdUnitActions") as RefCounted
var actions: Array[Dictionary] = bridge.legal_actions_for("civilian", "founder", true, false, false, false, false, false, false, {})
var actions: Array[Dictionary] = bridge.legal_actions_for("civilian", "founder", true, false, false, false, {})
var kinds: Array[String] = []
for d: Dictionary in actions:
kinds.append(d.get("kind", ""))
@ -142,7 +142,7 @@ func test_gd_unit_actions_bridge_fortified_cannot_fortify_again() -> void:
pass_test("GdUnitActions not loaded in headless — skipping bridge test")
return
var bridge: RefCounted = ClassDB.instantiate("GdUnitActions") as RefCounted
var actions: Array[Dictionary] = bridge.legal_actions_for("military", "", false, true, false, false, false, false, false, {})
var actions: Array[Dictionary] = bridge.legal_actions_for("military", "", false, true, false, false, {})
for d: Dictionary in actions:
if d.get("kind", "") == "fortify":
assert_false(d.get("enabled", true), "fortify disabled when already fortified")
@ -154,7 +154,7 @@ func test_gd_unit_actions_bridge_sentrying_unit_shows_unsentry() -> void:
return
var bridge: RefCounted = ClassDB.instantiate("GdUnitActions") as RefCounted
# is_sentrying = true
var actions: Array[Dictionary] = bridge.legal_actions_for("melee", "", true, false, true, false, false, false, false, {})
var actions: Array[Dictionary] = bridge.legal_actions_for("melee", "", true, false, true, false, {})
var by_kind: Dictionary = {}
for d: Dictionary in actions:
by_kind[d.get("kind", "")] = d

View file

@ -42,8 +42,6 @@ const REQUIRED_KEYS: Array[String] = [
"is_sentrying",
"is_deployed",
"is_embarked",
"has_adjacent_water",
"has_adjacent_land",
"xp",
"level",
"base_str",

View file

@ -2,15 +2,11 @@
//!
//! Exposes `GdUnitActions` to GDScript:
//! - `legal_actions_for(unit_type, keywords, has_movement, is_fortified, is_sentrying,
//! is_deployed, is_embarked, adjacent_water, adjacent_land,
//! posture)` →
//! is_deployed, posture)` →
//! `Array[Dictionary]` with `{kind, enabled, disabled_reason}` entries
//!
//! NOTE (p3-18): `is_embarked` / `adjacent_water` / `adjacent_land` are
//! vestigial — embarkation is now automatic (Civ-VI: move onto water →
//! embarked) with no explicit Embark/Disembark action, so these inputs no
//! longer affect legality. They remain in the signature for ABI stability and
//! are slated for removal in the P5 GDScript-mirror pass.
//! (p3-18: embarkation is automatic — move onto water → embarked, no explicit
//! Embark action — so the old embark/adjacency inputs were dropped.)
//!
//! `posture` is a Dictionary carrying archetype state fields (all optional,
//! defaulting to false/0 if absent):
@ -61,16 +57,8 @@ impl GdUnitActions {
is_fortified: bool,
is_sentrying: bool,
is_deployed: bool,
is_embarked: bool,
adjacent_water: bool,
adjacent_land: bool,
posture: Dictionary,
) -> Array<Dictionary> {
// p3-18 — embarkation is auto (Civ-VI): there is no explicit Embark
// action, so these legacy embark inputs no longer affect action
// legality. Accepted for ABI stability; the bridge + GDScript callers
// shed them in the p3-18 P5 GDScript-mirror pass.
let _ = (is_embarked, adjacent_water, adjacent_land);
let cap = UnitCapability {
unit_type: unit_type.to_string(),
keywords: keywords
@ -122,9 +110,6 @@ impl GdUnitActions {
is_fortified: bool,
is_sentrying: bool,
is_deployed: bool,
is_embarked: bool,
adjacent_water: bool,
adjacent_land: bool,
posture: Dictionary,
kind: GString,
) -> bool {
@ -132,9 +117,6 @@ impl GdUnitActions {
let Some(action_kind) = ActionKind::from_str(&kind_str) else {
return false;
};
// p3-18 — auto-embark: legacy embark inputs no longer affect legality
// (see legal_actions_for). Kept for ABI stability; trimmed in P5.
let _ = (is_embarked, adjacent_water, adjacent_land);
let cap = UnitCapability {
unit_type: unit_type.to_string(),
keywords: keywords