A fighter that leaves a finished fight, and creatures on command #144

Merged
sickday merged 3 commits from fix/bot-fighter-target into feat/pvp 2026-08-06 16:56:54 +00:00
Owner

Three commits from the sitting that preceded the PvP build, stacked on
#143 so the shared files apply cleanly. The fourth commit that branch
carried — the first cut of players attacking each other — is superseded by
#143's evolved version and is dropped rather than ported.

A fighter leaves a fight its target already lost. The :fighting phase
asked only about the bot's own body, so it could not tell "my click never
took" — which genuinely needs a timeout — from "my target is dead", which is
knowable at once. Both paid the full grace: the combat window re-arms the
grace on every poll, then eight decrements at jittered 1.8s, so a fighter
stood over a corpse for 16–26 seconds looking broken rather than slow.
gone?/1 now reads the snapshot — no row, or zero hit points, goes straight
back to hunting; nil hit points stays alive, matching what the quarry scan
already treats as attackable. The suite missed it because the hunt fixture
pins its goblin at 60 hit points with no respawn, deliberately, so it
observes a fight in progress and never one that ends; the repair is a pure
test that hand-builds the snapshot.

The anchor ranking gets its reasons written down. quarry/3 ranks
candidates from the grounds anchor rather than from the bot, and that reads
as a slip against the moduledoc until you notice nothing ever returns to
travelling once hunting starts — the anchor ranking is the only leash
holding a fighter to its patch. The doc now says so.

::npc puts creatures on the ground, and ::npcclear takes them back.
Forty-one commands and no way to spawn an NPC: what an operator could test
against was whatever the spawn file happened to place, which is how the
goblin sittings ended up hunting hit-point data faults instead of bots. A
commanded spawn is not a temporary one — it wanders, fights and respawns on
its own bestiary clock, which is why taking them back is a command rather
than a timer, and why the world's state gained a commanded_npcs set that
::npcclear empties without touching the world's own fixtures. --hp
overrides the bestiary row; --wander the radius. A cast rather than a
call, because a command runs inside a barrier participant.

Three commits from the sitting that preceded the PvP build, stacked on #143 so the shared files apply cleanly. The fourth commit that branch carried — the first cut of players attacking each other — is superseded by #143's evolved version and is dropped rather than ported. **A fighter leaves a fight its target already lost.** The `:fighting` phase asked only about the bot's own body, so it could not tell "my click never took" — which genuinely needs a timeout — from "my target is dead", which is knowable at once. Both paid the full grace: the combat window re-arms the grace on every poll, then eight decrements at jittered 1.8s, so a fighter stood over a corpse for 16–26 seconds looking broken rather than slow. `gone?/1` now reads the snapshot — no row, or zero hit points, goes straight back to hunting; `nil` hit points stays alive, matching what the quarry scan already treats as attackable. The suite missed it because the hunt fixture pins its goblin at 60 hit points with no respawn, deliberately, so it observes a fight in progress and never one that ends; the repair is a pure test that hand-builds the snapshot. **The anchor ranking gets its reasons written down.** `quarry/3` ranks candidates from the grounds anchor rather than from the bot, and that reads as a slip against the moduledoc until you notice nothing ever returns to travelling once hunting starts — the anchor ranking is the only leash holding a fighter to its patch. The doc now says so. **`::npc` puts creatures on the ground, and `::npcclear` takes them back.** Forty-one commands and no way to spawn an NPC: what an operator could test against was whatever the spawn file happened to place, which is how the goblin sittings ended up hunting hit-point data faults instead of bots. A commanded spawn is not a temporary one — it wanders, fights and respawns on its own bestiary clock, which is why taking them back is a command rather than a timer, and why the world's state gained a `commanded_npcs` set that `::npcclear` empties without touching the world's own fixtures. `--hp` overrides the bestiary row; `--wander` the radius. A cast rather than a call, because a command runs inside a barrier participant.
`:fighting` asked only about the bot's own body, so it could not tell a
click that never took — which genuinely needs a timeout — from a target
that is dead, which is knowable at once. Both paid the full grace, and a
kill therefore cost the tail of the in-combat stamp plus eight polls at
`jitter(1_800, 600)` before the bot looked for anything else: sixteen to
twenty-six seconds of standing over a corpse.

`state.target` had been written at three sites and read at none. Reading
it is the whole fix: a target the snapshot no longer holds, or one whose
row states zero hit points, sends the goal straight back to `:hunting`.
Hit points of `nil` stays alive, matching what `quarry/3` already treats
as attackable.

The world test could never have caught this — it pins its goblin at 60
hit points with `respawn_ticks: -1` so it observes a fight in progress,
never one that ends — so the case is covered by a pure test instead.
feat(command): put creatures on the ground, and take them back
All checks were successful
ci / gates (pull_request) Successful in 1m2s
build / image (push) Successful in 32s
ci / gates (push) Successful in 1m2s
3055b7f30f
Nothing in the server could spawn an NPC. `npc_spawns.exs` is read once at
boot and is the only authority, so what an operator can test against is
whatever an external dump happened to place — and where that dump is thin,
a test is simply unavailable. The sitting that asked for this wanted twenty
fighters against something durable, and found the world's goblins at three
hit points and its cow field holding ducks, a spider and Gillie Groats.

`::npc <id> [count] [--hp N] [--wander N]` scatters onto open ground from
the caller's own tile; `::npcclear` takes back exactly what an operator
placed. `--hp` overrides the bestiary row because that table carries the
source server's tuning and is wrong precisely where a new player stands.

A commanded spawn is not a temporary one: it wanders, it fights, and it
respawns on its own bestiary clock. That is why taking them back needs a
command rather than a timer, and why the world remembers their indices —
`::npcclear` must leave the world's own fixtures standing, which is the
one behaviour with a test of its own.

The spawn is a cast where `spawn_npc/3` is a call, and the tile search runs
in the world rather than the session, because a command runs inside a
barrier participant. `Bot.Manager` solved the same problem the same way.
sickday merged commit 3055b7f30f into feat/pvp 2026-08-06 16:56:54 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Revenant/Server!144
No description provided.