feedback7 R22-HUD: the cached-atlas refreshes of a crowded battle take their turns from this frame's update order, not random phases — each frame refreshes ⌊n/k⌋ or ⌈n/k⌉ of n units (step 1 of GitHub #327, PR #334 by @Cloudnyco); golden: unchanged

Applied unchanged after review (Grok static review: MERGE; the diff read in full: no network, no dependency).
UnitView._updateImpostor refreshed a unit when (frame + phase) % interval === 0 with a random phase per unit, so the
refreshes of a frame were binomial: reproduced on the parent with 7 pen figures at interval 3 — 5 1 1 5 1 1 … and
2 4 1 2 4 1 … refreshes per frame instead of 2 / 3. Now render/app.js hands every impostor unit the next slot of the frame
(ctx.impostorSlot, a counter reset in frameBody), the unit refreshes when (frame + slot) % interval === 0, and a unit
whose slot keeps moving with the frame (the units before it culled on and off in step) is still refreshed after 2
intervals (imp.last); a context without slots keeps the random phase. Each unit's rate and the total work are unchanged;
no sim or pixel-as-state change.
Step 2 of #327 (a time budget by priority) is not part of this and still needs a decision.

- Tests: test/render/unitview.test.js 'impostor refresh slots' (3: slots in update order — fails on the parent, the
  starvation cap, the no-slots context); unit view / render suites unchanged.
- Docs: design/client.md. Golden: unchanged (client only).
This commit is contained in:
Cloudnyco authored and sganggs committed 2026-10-08 19:22:08 +08:00
1 parent 3484178e2e
commit bc11c0d61a
4 files changed
+86 -3

No files matched your search

+8 -2
View File
@@ -1457,16 +1457,22 @@ export class UnitView {
if (this.actor.spine.parent) this.actor.spine.parent.removeChild(this.actor.spine);
if (atlas) atlas.park(this.actor.spine);
this.body.addChild(sprite);
this.imp = { sprite, slot: null, rt: null, sc: 0, acc: 0, phase: (Math.random() * 64) | 0, dirty: true };
this.imp = { sprite, slot: null, rt: null, sc: 0, acc: 0, phase: (Math.random() * 64) | 0, dirty: true, last: 0 };
}
const imp = this.imp;
imp.acc += animDt;
const frame = this.ctx.frameNo ? this.ctx.frameNo() : 0;
const due = imp.dirty || interval <= 1 || (frame + imp.phase) % interval === 0 || Math.abs(sc - imp.sc) > imp.sc * 0.12;
// the view's slot in this frame's update order (app.js impostorSlot) spreads the refreshes evenly over the interval;
// a context without slots keeps the random phase. A unit whose slot keeps moving with the frame (the units before it
// culled on and off in step) is still refreshed after 2 intervals at the latest.
const turn = this.ctx.impostorSlot ? this.ctx.impostorSlot() : imp.phase;
const due = imp.dirty || interval <= 1 || (frame + turn) % interval === 0 || frame - imp.last >= 2 * interval
|| Math.abs(sc - imp.sc) > imp.sc * 0.12;
if (due) {
this.actor.update(imp.acc);
imp.acc = 0;
imp.dirty = false;
imp.last = frame;
this._renderImpostor(sc, atlas);
}
const k = imp.sc > 0 ? sc / imp.sc : 1;