Put units on the map #19

Merged
icub3d merged 1 commit from 4-unit-definitions-and-spawning into main 2026-08-08 01:44:20 +00:00
Owner

Gets units onto the map: two of the player's cell against two K-A Asset Protection
hostiles, standing on the Proving Ground's spawn points.

What this adds

  • UnitDef (assets/data/units/skirmish.units.ron) — combat stats as data, loaded
    through the existing RonAsset trait on a *.units.ron extension. A CharacterDef is
    a person; a UnitDef is what a person or a hostile is worth in a fight. Separate
    files because a backstory and a balance number change for different reasons.
  • UnitRegistryVec-backed and validated against the roster, so a unit naming a
    character who is not there fails at startup with the offending id. Rejects zero health,
    speed, action points, attack damage, and attack range; allows zero movement, because a
    unit that holds ground is a design rather than a mistake.
  • ComponentsHealth, TurnSpeed, Movement, ActionPoints, BasicAttack,
    beside the existing GridPos and Team. Newtypes with meaning, per § Code Style.
    Health and action points carry their maximum, so the HUD can draw a bar and the
    scheduler can refill points without either one reaching for the registry.
  • Spawning — a pure plan() pairs each side's authored deployment with that side's
    authored spawn points; spawn_units carries the result into the ECS. Everything wears
    DespawnOnExit(GameState::Mission).

Acceptance criteria

  • A unit's identity comes from a CharacterId — worn as a component by deployed
    roster characters. Hostiles deliberately have none: giving a template an id would
    invent a person for a save to point at.
  • Allegiance is readable from shape as well as colour — a capsule for the cell, a
    cone for a hostile.
  • Spawning is deterministic. Both the deployment and the spawn list are authored
    orders, so the same map plus the same roster is the same fight; a test re-enters
    the mission and compares.
  • Unit tests cover the def-to-components mapping (combat_bundle in a bare World).

Notes for review

  • Deployment lives beside the unit templates. Who is in this fight is properly a
    mission's business, but missions are not data until M6 and M1 has exactly one. The
    field is commented as moving when they are; the templates stay.
  • More units than spawn points warns and deploys as many as fit, rather than refusing
    the mission. Map and deployment are authored in separate files today, so a mismatch is
    a content gap, not a corrupt file. That changes when a mission chooses its own map.
  • Unit validation is gated on CharacterRegistry existing — the cross-registry
    reference is the one ordering between two subsystems that the loading gate alone does
    not express. The gate still holds Loading open for "units".
  • No new canon. K-A Asset Protection is already in WORLD.md § Kessler-Arun; the
    hostiles are described with it rather than named into it.

Verification

cargo build, cargo test (94 passed), cargo clippy --all-targets -- -D warnings,
cargo fmt --check, and --features dev are all clean. There is no display here, so
the visuals are unverified: on entering a mission, look for two blue capsules on the
south edge at x=3 and x=5, two red cones on the north edge at x=6 and x=8, feet flush
with the tile tops, and nothing left behind on the way out.

Closes #4

Gets units onto the map: two of the player's cell against two K-A Asset Protection hostiles, standing on the Proving Ground's spawn points. ## What this adds - **`UnitDef`** (`assets/data/units/skirmish.units.ron`) — combat stats as data, loaded through the existing `RonAsset` trait on a `*.units.ron` extension. A `CharacterDef` is a *person*; a `UnitDef` is what a person or a hostile is worth in a fight. Separate files because a backstory and a balance number change for different reasons. - **`UnitRegistry`** — `Vec`-backed and validated against the roster, so a unit naming a character who is not there fails at startup with the offending id. Rejects zero health, speed, action points, attack damage, and attack range; allows zero movement, because a unit that holds ground is a design rather than a mistake. - **Components** — `Health`, `TurnSpeed`, `Movement`, `ActionPoints`, `BasicAttack`, beside the existing `GridPos` and `Team`. Newtypes with meaning, per § Code Style. Health and action points carry their maximum, so the HUD can draw a bar and the scheduler can refill points without either one reaching for the registry. - **Spawning** — a pure `plan()` pairs each side's authored deployment with that side's authored spawn points; `spawn_units` carries the result into the ECS. Everything wears `DespawnOnExit(GameState::Mission)`. ## Acceptance criteria - [x] A unit's identity comes from a `CharacterId` — worn as a component by deployed roster characters. Hostiles deliberately have none: giving a template an id would invent a person for a save to point at. - [x] Allegiance is readable from shape as well as colour — a capsule for the cell, a cone for a hostile. - [x] Spawning is deterministic. Both the deployment and the spawn list are authored orders, so the same map plus the same roster is the same fight; a test re-enters the mission and compares. - [x] Unit tests cover the def-to-components mapping (`combat_bundle` in a bare `World`). ## Notes for review - **Deployment lives beside the unit templates.** Who is in this fight is properly a mission's business, but missions are not data until M6 and M1 has exactly one. The field is commented as moving when they are; the templates stay. - **More units than spawn points warns and deploys as many as fit**, rather than refusing the mission. Map and deployment are authored in separate files today, so a mismatch is a content gap, not a corrupt file. That changes when a mission chooses its own map. - **Unit validation is gated on `CharacterRegistry` existing** — the cross-registry reference is the one ordering between two subsystems that the loading gate alone does not express. The gate still holds `Loading` open for `"units"`. - **No new canon.** K-A Asset Protection is already in `WORLD.md` § Kessler-Arun; the hostiles are described with it rather than named into it. ## Verification `cargo build`, `cargo test` (94 passed), `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`, and `--features dev` are all clean. There is no display here, so the visuals are unverified: on entering a mission, look for two blue capsules on the south edge at x=3 and x=5, two red cones on the north edge at x=6 and x=8, feet flush with the tile tops, and nothing left behind on the way out. Closes #4
A `CharacterDef` is a person; a `UnitDef` is what that person, or a hostile,
is worth in a fight. Two files, because a backstory and a balance number change
for different reasons — rewriting prose must not touch a number, and a balance
pass must not require reading fiction.

`UnitRegistry` validates against the roster, so a unit pointing at a character
who is not there fails at startup with the offending id rather than at deploy.
It rejects the numbers that would make a unit unplayable rather than merely
weak — zero health, speed, action points, damage, or reach — and allows zero
movement, because a unit that holds ground is a design, not a mistake.

On the map a unit is components, never bare integers: `Health`, `TurnSpeed`,
`Movement`, `ActionPoints`, `BasicAttack`, `GridPos`, `Team`, and the
`CharacterId` of whoever is wearing it. Only a deployed roster character wears
one; giving a hostile an id would invent a person for a save to point at.

Who stands where is a pure `plan` pairing each side's authored deployment with
that side's authored spawn points. Both orders are authored, so the same map
plus the same roster is the same fight every time, and the placement is
testable without an `App` — the ECS never gets to decide it.

Player units are capsules and hostiles are cones, tinted `UNIT_PLAYER` and
`UNIT_ENEMY`. Shape as well as colour, per § Visual Style: allegiance has to
survive a player who cannot tell blue from red.

Closes #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
icub3d merged commit 1f76e9e0e3 into main 2026-08-08 01:44:20 +00:00
icub3d deleted branch 4-unit-definitions-and-spawning 2026-08-08 01:44:20 +00:00
Sign in to join this conversation.
No description provided.