
PathWeaver
Mob pathfinding runs on the server thread and causes tick spikes. PathWeaver moves it to spare CPU cores: mean tick roughly halved with 1024 mobs, worst-1% ticks down 55-61%. Server-side, clients need nothing. Read the warning before installing.
Список изменений
Review of the 0.5.1 hotfix. It found that the hotfix was incomplete, that one of its fixes was a regression, and that the test written to prevent recurrence could catch neither failure.
Fixed
- A second attribute-backed read-modify-write is still in the A loop, and it affects all six
families.*
WalkNodeEvaluator.tryFindFirstGroundNodeBelow— reached fromgetNeighborsviafindAcceptedNode— callsMob.getMaxFallDistance(), which for a mob with a target readsgetMaxHealth(), which isgetAttributeValue(MAX_HEALTH)→AttributeInstance.getValue(). The sameif (dirty) { cachedValue = calculateValue(); dirty = false; }over plain non-volatile fields thatmaxUpStep()hides. Worse reach than the 0.5.1 blocker: it is declared byWalkNodeEvaluatoritself so every admitted family hits it, and it fires exactly when a mob is chasing something. The corrupted value is the mob's cached max health. - 0.5.1's own fix was a regression on the branch where vanilla does not recompute. The re-apply of
the claimed target ran at the
canUpdatePath()injection point, which is upstream of the branch. On the false branch — a ground mob mid-jump — vanilla jumps straight to settinghasDelayedRecomputation: it never readstargetPosand never nullspath. So the re-apply lefttargetPosnaming the claimed destination whilepathstill held the route to the old one, and the next request for that destination hit vanilla's reuse short-circuit and was handed the stale path, self-sustainingly. That is precisely the pairingrollbackOptimisticTargetexists to prevent. The re-apply now happens inside the wrap that runs only where vanilla actually recomputes. - The game-test assertion added in 0.5.1 pinned that regression as correct. It asserted the
claimed target survived on the airborne branch — where vanilla acts on nothing. It now asserts the
opposite invariant: when vanilla does not recompute,
targetPosmust not be left naming a destination the installed path does not lead to.
Changed
LiveMobReadCoverageContractTestis rewritten to be structural. 0.5.1's version keyed on class names against a hand-written literal, so it would have stayed green if the new mixin were deleted frompathweaver.mixins.json, and it could not detect a second read in a class already on the list — which is exactly whatgetMaxFallDistancewas. Coverage is now keyed on the (declaring class, declaring method, called method) triple, and the covered side is read out of the@Redirectannotations of the mixins actually listed in the shipped config, via ASM — Mixin's annotations areRetentionPolicy.CLASS, so reflection sees none of them and a reflective version would have compared a populated set against an empty one. Both failure modes are mutation-tested.
Known limitation, stated
The list of read names considered hazardous is still hand-maintained. The structural version walks
the call graph for the two real sinks — AttributeInstance.getValue() and RandomSource — instead of
naming their callers, and would have found both of these bugs without being told what to look for.
That is the top item on the 0.6 roadmap, and until it exists this list is the thing most likely to be
incomplete.
270 unit tests, four server harnesses, the client harness.
