Commit graph

5 commits

Author SHA1 Message Date
Kamron Batman
5429465d64
feat: herding paces the movement clock, not the think clock
Herding is a movement scenario: the old CurrentSpeed getter hack forced
0.3 on the fused clock, which under the split misclassified - a herded
mob whose active think happens to be 0.3 walked at its (slow) move value
while any other mob got a bespoke 0.3. Now CurrentMoveSpeed drives a
herded creature at a fixed 0.3s per step - RunUO's forced pace, without
its TransformMoveDelay inflation to 0.6 - so herding is never penalized
by a slow creature. Thinking is untouched.

CheckHerding walks through MoveToPoint (cached boxed goal) instead of a
greedy per-think DoMove, so the movement clock actually governs pace
between think ticks - and herded creatures path around obstacles instead
of walking into them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 10:17:28 -07:00
Kamron Batman
41ae2d8ffe
fix: snap speeds to table values after paragon unconvert
Scaling down then back up drifts some values by an ulp (0.45 and 0.9 do
not survive /1.2 then *1.2 in IEEE doubles; the think values currently
do only by luck of the mantissas). Drifted speeds would read as
hand-tuned - notably by the planned skip-serializing-table-conformant
values optimization, which would then persist every former paragon
forever. UnConvert now snaps both clocks back to exact table values when
within rounding distance (1e-4, the existing speed epsilon); genuinely
tuned speeds are nowhere near it and keep.

Also removes MoveSpeedMod: never read, never written, never serialized.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 09:58:53 -07:00
Kamron Batman
658735fd45
feat: paragons scale the movement clock by SpeedBuff
RunUO had no deliberate paragon movement policy: dividing by 1.2 knocked
most speeds off TransformMoveDelay's exact-equality table, so those
paragons moved at the raw divided delay (2x+ faster than their transformed
base), while 0.3/0.6 creatures landed back on the table (0.25/0.5 are
exact in IEEE) for only a ~1.33x buff. This applies the uniform 1.2x the
buff always claimed: Convert/UnConvert scale movement overrides alongside
the think clock; inheriting values stay inheriting, since they already
follow the scaled think values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 09:46:27 -07:00
Kamron Batman
5bf9fbefb6
feat: seed move speeds for pre-v22 creatures on load
A creature deserialized from a pre-v22 save whose think speeds still match
what it would spawn with today was never hand-tuned: adopt today's table
move values, so existing worlds and pets pick up npc-speeds pacing without
a respawn. A creature with tuned think speeds no longer matches and keeps
movement inheriting its think clock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 09:36:54 -07:00
Kamron Batman
ebe730353b
feat: independent think/move clocks for creature AI
Creatures now run two clocks: the think clock (ActiveSpeed/PassiveSpeed,
seconds per decision - unchanged in meaning, storage, and cadence) and a new
movement clock (ActiveMoveSpeed/PassiveMoveSpeed, seconds per step, 0 =
inherit the matching think value). This lets movement pace be tuned freely -
e.g. toward RunUO's TransformMoveDelay pacing - without touching reaction
time, spell timing, or acquisition cadence.

- BaseCreature: move-speed pair seeded from npc-speeds.json (optional
  activeMove/passiveMove per bucket), [props-tunable per instance (0 to
  re-inherit), serialized (v22). SetSpeed keeps its legacy one-clock
  semantics: it sets the think clock AND clears move overrides, so existing
  callers cannot half-configure a creature; SetMoveSpeed/ClearMoveSpeed
  configure movement explicitly. CurrentMoveSpeed resolves by classifying
  CurrentSpeed - a verbatim active/passive value maps to the matching move
  value, while bespoke paces written directly (mount boosts, follow sprint)
  stay fused to both clocks, so external CurrentSpeed writers keep working
  untouched.

- Movement budget: one step consumes a full step of movement-clock budget,
  accumulative with RunUO's snap-to-now clamp, so long-run pacing averages
  CurrentMoveSpeed exactly and a stall banks at most one step of catch-up.

- AITimer: dual-deadline scheduling. Decisions run at the think cadence
  exactly as before; while a pursuit or investigation is live, the timer also
  wakes when the movement budget elapses and advances the step without
  running decisions. Steps no longer snap to the think grid, so any step
  delay paces smoothly on the 8ms timer wheel - no ping-pong stutter from
  incommensurate values. A blocked creature schedules no move wakes and
  stays think-paced.

- Move intent: ApproachTarget/MoveToPoint record their durable goal each
  en-route tick; arrival, failure, give-up, action changes, or missed
  renewals clear it.

- Badly-hurt slowdown now inflates the step delay only (RunUO parity) and is
  computed from the base each step. Previously it wrote CurrentSpeed back
  onto itself, compounding unboundedly while hurt and slowing decisions too.

No behavior changes by default: with no bucket or per-instance move values,
both clocks carry identical values and creatures pace as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 09:13:28 -07:00