feat: melee combat #83
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/combat"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The first consumer of the hits mask, the killer argument and the death
sequences: a player attacks an NPC from its own Attack option, the creature
retaliates or attacks first, and the drops at the end are addressed to
whoever dealt the most damage.
The design in one line: the attacker states numbers, the defender rolls.
A swing crosses a process boundary as four integers — attack roll, max hit,
style, speed — and the defender computes the defence roll, the shared
hit-chance curve and the damage from facts only it holds. Neither side ever
reads the other's stats, so combat needs no cross-process stat table and no
call can block a tick. NPC facts live in :persistent_term; the two live
cross-entity facts ride existing point-lookup snapshot rows (an NPC's
hitpoints for the XP clamp and the corpse-stop, a player's combat level for
the aggression scan). MovementAuthority rows stay position-only.
World.Combat is the pure arithmetic, era-2017 and integer-exact: effective
level + stance bonus + 8 (+9 for NPCs), rolls as effective * (bonus + 64) on
one shared scale, max hit as (e * (b + 64) + 320) / 640 — pinned by property
tests against the float floor form and against the one-scale rule. XP is
4.0x damage for a focused stance, 1.33x each for controlled, hitpoints
always 1.33x, clamped to what the target had left.
An attack is Follow's other half in Contract.Interaction: a pursuit that
fires and persists. The new repeats?/1 (no catch-all, like its siblings)
keeps the action across firings; the dispatcher translates a click to
:attack from the cache's own action text, since Attack sits wherever a
definition's list puts it. Melee reach is the existing :npc reach clause.
The NPC half runs in the world's NPC phase: retaliation (first swing seeded
a full attack period out, so both sides opening at zero cannot trade
simultaneous first hits forever), an aggression scan gated on the record's
level cap, a leash at wander radius plus eight flat tiles, and a chase that
aims at a cardinal-adjacent tile because the dumb pathfinder wedges
diagonally one step short when aimed at the target itself. An unmeasured
creature neither retaliates nor scans.
An incoming hit is a strong queue entry — being punched closes your bank,
which the queue rules gave for free — with a clock of two, because an op
applied before a tick's queue pass spends its first clock in that pass: a
clock of one lands the hit on the swing's own tick, where its flinch runs
ahead of the defender's swing and, against any faster attacker, steals it
every period. The flinch itself only starts an expired cooldown and never
extends a running one, which is the engine's own rule and the other half of
the same stall.
The combat tab is live: style buttons 593:3/7/11/15 write varp 43,
auto-retaliate 593:27 toggles varp 172 (zero means on), and the weapon-type
varbit 357 — six bits of varp 843 in this cache — is derived from the worn
weapon on every redress, never persisted. Varps 43 and 172 persist.
Data: npc_combat.exs widens from four fields to seventeen (combat levels,
per-type attack and defence bonuses, style, observed max hit, attack speed,
aggression cap, swing and flinch animations — the walk-family vote now fills
all three animation fields, and correctly stays silent where a rig swings
per weapon). weapon_categories.exs is new: 72 categories and 573 weapons,
because a weapon category is obj opcode block 200-212, stripped server-side
like tradability, so no client cache of any revision carries it. Every id
and animation is vetted against the operator's own cache.
Kill credit is damage dealt now: World.Npc tallies applied damage per name,
the pile goes to the top damager with the last hit as fallback, and overkill
buys no credit. Closes #60. Closes the extraction half of #69.
World.Player nests path, pending_action and ctrl_run? into route — one
concept, written together since the day interact/4 existed — paying for the
new combat field with room to spare under the 32-field cliff.
The suite runs against a pacified copy of the shipped bestiary: with real
aggression loaded, the creatures around every test tile attack low-level
test players mid-test, and a wandering giant rat's hitsplat reads as an
unexpected player update in whichever test was watching. Combat tests
install their own aggression.
2,729 tests; format, Credo, Dialyzer and the doc gate clean.