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

+6
View File
@@ -316,6 +316,7 @@ export async function createFieldView(host, options = {}) {
renderer: app.renderer,
frameNo: () => frameNo,
impostorInterval: () => impInterval,
impostorSlot: () => impSlot++,
clipAllowed: () => clipAllowed,
viewport: () => vp,
loadLevel: () => loadLevel,
@@ -1565,6 +1566,10 @@ export async function createFieldView(host, options = {}) {
let cpuMs = 0;
let frameNo = 0;
let impInterval = 0;
// Each frame the impostor units take slots 0, 1, 2 … in update order (units.js _updateImpostor): a unit refreshes when
// (frame + slot) % interval === 0, so a frame refreshes ⌊n/k⌋ or ⌈n/k⌉ of n units. Random per-unit phases left the
// busiest frame 20–66% above that (16–48 units, intervals 2–6) and set the frame-time peaks of a crowded battle.
let impSlot = 0;
let vp = { width: s0.width, height: s0.height }; // viewport (CSS px) of this frame: unit culling
let culledCount = 0;
// Adaptive load level 0–3: a device that cannot hold the frame rate with the current work switches crowds to
@@ -1605,6 +1610,7 @@ export async function createFieldView(host, options = {}) {
}
function frameBody(now) {
frameNo++;
impSlot = 0;
if (frameNo % 30 === 1) {
impInterval = pickImpostorInterval(); clipAllowed = pickClipping();
culledCount = 0;