Add Brain enum with hunting foxes; move the A* into the sim

NPC strategy is now a Brain field on the entity (Idle, Wander, Hunt(Prey))
that Sim::think matches on, so behavior is data independent of species.
Hunt follows the first step of an A* route to the nearest prey in sight,
replanned every window; the pathfinder moves from game/ to sim/ for that.
A greedy step-toward-target was tried first and oscillated at walls. Bumps
between the same species no longer fight, and six foxes join the hens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 11:27:47 +02:00
co-authored by Claude Opus 5
parent b64c83fce0
commit 03a12c82dc
8 changed files with 254 additions and 41 deletions
+18 -2
View File
@@ -47,7 +47,7 @@ crate's `tick`/`set_action` interface is the seam a server could be wrapped arou
(trees/rocks) and id 0 (empty / the invisible world border). A proper tile-data file
format replaces this table later.
- [x] Click-to-move: framebuffer click → world tile, A* over the world
(`game/src/game/pathfind.rs`, 8-connected, Chebyshev heuristic), route fed into the
(`sim/src/pathfind.rs`, 8-connected, Chebyshev heuristic), route fed into the
sim one step per movement window. Keyboard input cancels the route; each step is
re-validated at scheduling time and a blocked step replans toward the persistent
goal. Click-and-hold steers continuously: while the button is held the route keeps
@@ -74,7 +74,17 @@ crate's `tick`/`set_action` interface is the seam a server could be wrapped arou
drained by the game with `Sim::take_events` after every window — which is the
seam for sound, message log, camera shake, spawns. Player death quits for now.
Click-to-move treats an occupied goal as a target (route ends in a bump) but
walks around entities in the way; keyboard steps always bump.
walks around entities in the way; keyboard steps always bump. A bump into the
same `type_id` (species) is a refused move, not a fight.
- [x] NPC brains: `Brain { Idle, Wander, Hunt(Prey) }` as a field on `Entity` — strategy
is data, independent of species and swappable at runtime. `Sim::think` matches on
it. `Hunt` takes the first step of an A* route (the pathfinder moved into `sim/`
for this) to the nearest prey within `SIGHT_RANGE` (8), replanned every window;
out of sight or unreachable it wanders. A greedy step-toward-target was tried
first and oscillated at any wall between hunter and prey. Six foxes
(`Hunt(Kind(hen))`, 8 hp / 2 dmg) clear 150 wandering hens off `map_test` in
about a minute; `cargo test -p game population -- --ignored --nocapture` watches
it headlessly.
- [x] Entity interpolation: per-id lerp table (`EntityLerp` in `game.rs`) — previous/current
tile plus a clock, rendered as a pixel lerp over one movement interval. Jumps of
more than one tile (Chebyshev) snap. Purely cosmetic; game logic keeps using the
@@ -117,6 +127,12 @@ budget model goes once there are enough NPCs to need it. The action-point model
- [x] `EntityKind::Npc`, `Sim::think` scheduling intents through the same intent table
- [x] Bump-to-attack, death, `Event` queue
- [x] `Brain` enum, `Hunt` via A*
- A* per hunter per window is fine for six foxes; the think budget below becomes
necessary once hunters number in the dozens (or `hunt` caches its route and replans
only when the target moved).
- Hunters don't defend themselves: a fox bitten by a hen does not retaliate unless
the hen happens to be its nearest prey. `Brain::Hunt` could switch target on `Attacked`.
- Stats beyond `hp`/`attack` (defense, speed) as plain fields; a `Stats` substruct once
they crowd `Entity`. No ECS until entities actually differ in *which* components they
carry — see the discussion of 2026-09-19.