Rail-1 Phase-1 increment 1 — the units analogue of City's proxy over
presentation_cities. Unit's authoritative gameplay surface (position, hp,
max_hp, attack, defense, movement_remaining, xp/experience, the posture flags,
promotions) now lives in the Rust `presentation_units` slot, reached via
`GameState.get_gd_state().unit_*(_pi, _resolve_ui())`. The slot is positional,
so the view keys on a stable u32 `rust_id` and re-resolves its row index on
every access — a death (remove_unit shifts indices) or capture (transfer_unit
changes both _pi and _ui) never leaves a getter reading the wrong unit.
- Slot-backed fields become property getter/setter pairs that route to the slot
when spawned, falling back to `_local_*` mirrors when unspawned / no dylib, so
bare `Unit.new(...)` (arena tests, early construction) keeps working.
- DUAL ID: `id: String` stays the renderer/debug key; `rust_id: int` is the
Rust-backing key. unit_slot::spawn trusts the JSON-borne id (unlike
City::found which derives it), so GameState owns id assignment via a new
monotonic `next_unit_id()` counter (serialized; restored above loaded ids).
- Wild creatures (owner -1) land in a dedicated wilds row at `players.size()`
(`wilds_pi`/`unit_slot_pi`) so they never collide with player 0.
- spawn_into_slot / remove_from_slot / transfer_to_owner are the slot lifecycle;
from_save_dict reattaches a restored unit to a fresh slot entry keyed on its
saved rust_id (rust_id 0 = never-slotted synthetic unit, left on its mirror so
the save dict round-trips byte-identically).
- deserialize / reset drain the unit slot (incl. the wilds row), mirroring the
city-slot drain, so units do not accumulate across loads.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>