Skip to content

Sitting, bows on the shared cooldown, reserved ceilings, beast copy (#637 #265 #285 #631) - #638

Merged
pruuk merged 23 commits into
masterfrom
fix/1019-batch5
Oct 20, 2026
Merged

pruuk merged 23 commits into
masterfrom
fix/1019-batch5

Conversation

@pruuk

@pruuk pruuk commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Closes #637. Closes #265. Closes #285. Closes #631.

The last small-fix batch of 2026-10-19: a real Sitting position, bows on the shared cooldown, pools that respect their reserved ceiling, and the beast-copy leftovers from the #541 slice 2 playtest. Every player-facing line here is owner-approved.

#637 Sitting (sit / rest)

  • New Sitting position (appended to the enum, never persisted). sit and rest are synonyms for players and mobs; stand gets up for free.
  • Regen while seated: SitRegenMultiplier 2.0, below SleepRegenMultiplier 5.0 (now shipped explicitly); the two never stack.
  • Combat stands a sitter (RiseForCombat); the reveal comes first, so a hidden sitter is never named before it is revealed. Death never narrates rising. Area attacks leave a sitter seated (owner: working as intended).
  • Every room move stands a sitter silently through one choke point per actor type: rooms.MoveToRoom (players) and Room.AddMob (mobs).
  • Room lines go through actions.SendSeen, so a hidden actor's sit or stand reaches no one.
  • Refusals: asleep ("You're asleep. Type 'wake' to wake up."; sit is the only command that does not wake a sleeper, via the keepsSleep set), already sitting, fighting, grappled, on the floor, busy. Busy is now shared by players and mobs in actions.Sit.
  • Auto-stand a player after 5 rounds prone or supine #398's auto-stand guard ignores sitters.

#265 Sentinel warbow, bows on the shared cooldown

  • Owner ruling: bows were always meant to fire on the shared special-move cooldown; ungated fire was a bug. ExecuteFire checks SpecialMoveReady before chambering and stamina, and claims the cooldown after. A refused shot spends nothing. Player line: "You need a moment to recover before you can shoot."; mobs are refused silently.
  • CommandIsReady and specialMoveVerbs know fire and shoot, so companions, behaviour trees and the client's readiness request wait instead of issuing a refused shot. Readiness and chambering share findAmmoBundle.
  • Sentinel 9552 wields Ironhorn Warbow 10046 (60% drop) with Quiver 30062, and its tree gains try_fire.

#285 Reserved ceiling

  • Character.PoolCeiling (raw max less GetPoolReservation) is the one ceiling: every restore, the recalculation clamp, and the new SetPoolFraction stop at it. Heal is retired in favour of ApplyRestore. PowerScore's atFullPools is unchanged (owner's call).

#631 Combat copy

  • Claws prepare and wait lines no longer promise a pounce; fumbles no longer make claws or jaws the subject; the double fumble's "weapon slips" is now "attack goes wide"; the mob hamstring defence label is "low lunge".
  • All 232 defender and room defence lines name the attacker (guard TestDefenceLinesNameTheAttacker).
  • Partial hits are one rule, sendPartialMove (mobcommands and usercommands): all three roles read the move's partial tier, for 12 mob moves, 9 player moves and the firebomb. Every partial line is defence-neutral (guard over a defence-word list). Guards cover store tiers and call sites.
  • The bleed stop line did not reproduce; a test now pins it.

Also

  • TestRunScenario_WallClockIncomplete now pumps its manual clock until the run returns; a bounded advance could end before a loaded machine set the deadline, hanging the package for 10 minutes.

Gate (on the batch head)

gofmt -l empty; go build ./...; full go test . ./internal/... ./modules/... -count=1 with no failures; golangci-lint run --new-from-merge-base=origin/master 0 issues; boot check exit 124, 0 panics, Server Ready once. Each task's tests were null-probed (the fix removed, the test seen to fail, restored). A separate review of #637 found four issues; three were fixed, and the fourth (area attacks) was ruled working as intended.

Patch notes: ## 2026-10-19: Take a seat and ## 2026-10-19: Bows wait their turn, and fights read true.

🤖 Generated with Claude Code

pruuk and others added 23 commits October 19, 2026 16:41
…he claws

claws.yaml narrates every claws species (birds, bats, skeletons, the
smoke-wrought Elemental Prince), none of which can pounce: three prepare
and wait records now promise a strike. Two claws and two bite fumble
records made the body part the subject of FUMBLE ("A figure's claws
FUMBLE pathetically as they stumble!"); the attacker fumbles now, per the
#455 rule. Guards: no body part before a FUMBLE banner in any natural
pool, and no pounce in the claws pool.

Refs #631

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The double-fumble comedy lines are picked without looking at either
side's gear, so "Your weapon slips at the exact moment Steppe Wolf trips
over their own guard!" reached an unarmed admin. The record now reads
"attack goes wide" in all three roles; a test fails any line that names
a weapon.

Refs #631

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow lunge

"You hardly need to move, the attack is so far off!" named no attacker;
the record now names {actor} for the defender and the room. A defended
hamstring read "sweeps aside the fumbled hamstring slash", the command
name, beside store lines that narrate a low lunge at the legs; the
defence label is now "low lunge", and a test holds the label to words
the hamstring store already shows the reader.

Refs #631

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#631 reported eight "Blood seeps from your wounds!" lines and no end
line. Driving the round tick and the turn prune together shows the stack
ticking once per round and the end line arriving once; this pins it.
The run length is HamstringBleedRounds (12) / ThrottleBleedRounds (8) at
a 1-point floor, a balance setting.

Refs #631

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Sentinel (9552) never shot: the warbow (10046) was carried loot,
never wielded, it had no arrows, and its per-mob tree had no try_fire.
It now wields the bow (keeping the 60% drop chance on the equipped
item), carries a Quiver of Arrows (30062), and its tree runs try_fire
on mob_combat_round, the archer archetype's shot without the
keep_distance kite. When the quiver runs dry the round falls through to
its combat commands and melee as before.

try_fire now fails when the mob cannot see clearly into the target's
room, the same messaging.CanSeeClearly test actions.ExecuteFire refuses
a shot on (TooDarkToAim / Blinded). Before, the tree claimed the round
with a shot the fire rule then refused, so an archer in the dark did
nothing at all; now it falls through to melee. Archer tests stand in
lamp-lit rooms accordingly.

Closes #265
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restores capped at the raw max and only the RecalculateStats reservation
clamp knew about reservation, so regen, potion and heal ticks, drain and
the lifesteal proc filled a reserved pool past what it could hold, and
the prompt, status and GMCP showed the inflated value until the next
clamp pulled it back.

PoolCeiling (raw max less GetPoolReservation, floored at 1 for health
and 0 otherwise) is now the one ceiling: applyVitalChange caps every
ApplyRestore at it (never lowering a pool already above it), the
RecalculateStats reservation clamp uses it, and the new SetPoolFraction
caps spawn, respawn, companion summon and recovery, ReviveOnDeath and
admin restore at it. Heal, which capped at raw HealthMax, is retired;
its five callers pass positive amounts and now call ApplyRestore.
Players and mobs share the rule. Nine files leave the pool-mutation
guard's exemption list.

Closes #285

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…layers and mobs alike (#265)

Owner ruling 2026-10-19: bows were always meant to fire on the shared
special-move cooldown. ExecuteFire claimed the timer on every shot but
never waited for it, so a shooter could fire every round. It now asks
SpecialMoveReady before the auto-chamber and before admission (a shot
refused for the timer spends no stamina, arrow or round) and takes the
timer with ClaimSpecialMove after admission and the stale-weapon check,
the order bash and trip use. FireResult.OnCooldown reports a refusal.

The player fire/shoot command answers with refusal.CooldownAfterCost,
the line bash and trip give; the mob shoot command stays silent. The
ambush is gated like any shot. try_fire fails while the timer runs, on
the same SpecialMoveReady helper, so the Sentinel and every archer mob
melee and use their other moves between shots.

Tests: the ungated-opener test flipped to the ruling, the admission
guard now pins two cooldown sites (ask, then claim), a second shot
inside the cooldown is refused at no cost and fires once it clears,
the player and mob wrappers refuse on the cooldown, and the Sentinel
holds its bow for exactly the cooldown and then fires again. Help for
fire, special, ambush, ranged-combat and cooldowns says so.

Closes #265
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

CommandIsReady and specialMoveVerbs had no fire entry, so a caller that
asks before issuing a command (behaviour-tree command_best_of, the AI
companion, gmcp CharOp) could issue a shot that ExecuteFire then refused
on the shared special-move cooldown. Both now carry fire and its alias
shoot. Beyond the universal activity and cooldown gates, a shot reads
not ready with no ranged weapon, or with an empty one and nothing to
chamber: the verb-level refusals ExecuteFire gives (NoWeapon,
NotLoaded). The ammo lookup is now one helper, findAmmoBundle, shared
with chamberNextRound.

Drift rows cover fire and shoot on crafting, cooldown, no weapon and no
ammo, and TestCommandIsReady_ShotWaitsForTheCooldown proves readiness
and the fire rule agree before, during and after the cooldown.

Closes #265
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sitting is a new Position state (appended, nothing renumbers), shared by
players and mobs. `sit` and `rest` are one command (players: rest is a
keywords.yaml command alias; mobs: both registered). The old `sit` emote
alias is gone.

- Edges: Standing -> Sitting; Sitting -> Standing (stand, walking off,
  or combat), Sitting -> Prone/Supine (a knockdown landing on a sitter).
- Combat never runs against a sitter: Character.RiseForCombat stands one
  who is attacked (RevealForCombat) or enters combat (CombatPhase
  Idle->Engaging observer, hooks/Position_SitRise.go), narrated once.
  Not in CancelCombatConditions, so a dying sitter is not narrated up.
- Refusals: asleep, already sitting, in combat, grappled, on the floor.
- stand from a seat is free; go stands a sitter after the step is paid.
- Regen: Balance.SitRegenMultiplier, shipped 2.0, below sleep; never
  stacks with sleep. SleepRegenMultiplier now shipped (5.0) too.
- Not a knockdown: AttemptRecovery and the #398 guard ignore it.
- Look lists "(sitting)"; {pos} prompt shows it in white; help sit/rest.

Closes #637

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urns

TestRunScenario_WallClockIncomplete advanced its manual clock fifty times
and stopped. On a loaded machine (the full suite) RunScenario could set its
deadline after the advancing ended, so the deadline never arrived and the
test hung until the 10-minute package timeout. It now uses the package's
pumpClock helper on the pumpCtx it already built, which advances until the
run returns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ints (#637)

Teleport, recall, summon and the other forced moves left a sitter seated
in the new room; only go stood them. The stand now lives in the two room
choke points, so every move path, present or future, is covered:
rooms.MoveToRoom (every player move) and Room.AddMob (every mob room
entry) call Character.StandForMove, which stands a sitter silently under
the new position.TriggerMoveStand. The combat-rise narration keys on
TriggerCombatRise only, so no move (respawn included) narrates rising.

The go wrappers' own narrated stand is removed: MoveToRoom and AddMob
run after the step is paid, so a refused step still leaves a sitter
seated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Stealth: sit, stand and the combat leap send their room line through
  actions.SendSeen, which is silent for a hidden actor (#274, owner R3);
  a hidden player who sits or stands is told "No one sees it; you are
  hidden." RevealForCombat now reveals before it stands a sitter, so the
  leap never names a hider.
- Sleep (owner ruling 2026-10-19): sit never wakes a sleeper. The refusal
  now reads "You're asleep. Type 'wake' to wake up." and players can reach
  it: sit (and rest, its alias) is the one command in keepsSleep, which
  the dispatcher's cancel-on-action wake skips. Mobs are refused silently,
  as before.
- Busy: the busy refusal moved into actions.Sit, so a casting, crafting or
  salvaging mob is refused as a player is.
- Area attacks leave a sitter seated: owner ruling, working as intended.
- Prompt comment counts 15 states; patch notes mention "Sitting" in the
  prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner ruling on #631: a line that tells the defender or the room about a
defended attack names the attacker, so a defender facing two foes and a
room watching a crowd know whose attack was stopped. 232 lines change
(113 defender, 119 room) across block, dodge, parry, defy, quell and one
counter-parry line; each keeps its meaning and voice and follows its
file's colour convention. TestDefenceLinesNameTheAttacker holds every
shipped defence store to the rule (it flags all 232 on the old files).

Refs #631

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner ruling on #631: one outcome is told in one tier. A special move a
defence blunted but did not stop swapped its room line for the defence
triad's, so the room read "Ordel Quist smoothly sweeps aside the fumbled
low lunge." while the defender read that the lunge still caught them.

sendPartialMove (skill_move_defence.go, a twin in mobcommands and
usercommands as moveDefenceLines already is) is the one rule: every role
reads the store's partial event; only the defender's shortage note comes
from the defence. All 12 mob moves and 9 player moves call it.

Guards: TestSpecialMoveStoresCarryEveryTierTheRuleDraws (each partial
event tells every role its hit tells; every rule caller has a partial),
TestPartialMovesNarrateThroughTheRule (no hand-rendered partial outside
shoot and throw, each exempted with its reason), and a behaviour test per
package that the attacker, defender and room read the partial line. The
two root guards that recognise move call-site helpers now know the new
one.

Refs #631

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rule

Owner rulings on #631. A partial is one tier told for every defence that
can blunt the move, so every partial line in the special-move stores now
says the move was blunted ("gets mostly clear", "stops most of it", "only
half lands") and never how: no dodge, sidestep, roll, twist, block, guard
or miss framing. Eleven stores change; bash and trip already read true.

The firebomb's defended partial now goes through sendPartialMove: the
thrower and the room read partial_hit, which gains its room line, "The
edge of {actor}'s blast still catches {actee}!". The defence triad speaks
only when the defence took the whole blast. The guard's throw exemption
is gone; shoot's stays (its store has no room role to swap).

Guards: defenceWordInPartial fails a partial line that names a defence
(it flags 87 lines in the old stores), and the firebomb behaviour test
drives the real Throw (it fails on the old throw.go, where the room read
"Target withstands Thrower's firebomb.").

Refs #631

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two grappling knee lines put "who" after "the grapple", so the grapple
read as the thing that stopped the knee; the clause now leads. The throttle
line names the throat as the target's own. Owner-approved wording.

Refs #631

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pruuk
pruuk merged commit 7e3bfae into master Oct 20, 2026
6 checks passed
@pruuk
pruuk deleted the fix/1019-batch5 branch October 20, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant