This commit is contained in:
33333-33333 2026-06-24 13:48:22 +09:00
commit d5c661bc22
53 changed files with 4264 additions and 950 deletions

View file

@ -348,6 +348,55 @@ function isEntityVisibleInRect(entity, rect, extra = 0) {
return x + r >= rect.left && x - r <= rect.right && y + r >= rect.top && y - r <= rect.bottom;
}
function renderStackSignature(worldRef) {
return [
(worldRef.items || []).length,
(worldRef.tarinai || []).length,
(worldRef.ants || []).length,
worldRef.terrainVersion || 0,
].join(":");
}
function ensureWorldRenderStack(worldRef) {
if (!worldRef._renderStack) {
worldRef._renderStack = { signature: "", backItems: [], layered: [], carriedPlushies: [], lodgedPins: [] };
}
const stack = worldRef._renderStack;
const signature = renderStackSignature(worldRef);
if (!worldRef.drawListDirty && stack.signature === signature) return stack;
stack.backItems.length = 0;
stack.layered.length = 0;
stack.carriedPlushies.length = 0;
stack.lodgedPins.length = 0;
for (const it of worldRef.items || []) {
if (!it || it.dead || isCachedTerrainItem(it)) continue;
if (isPinType(it.type) && it.pinState === "lodged") {
stack.lodgedPins.push(it);
continue;
}
if (it.isStructure && it.type === "plushie" && it.carriedById) {
stack.carriedPlushies.push(it);
continue;
}
if (isBehindSpriteLayerItem(it)) stack.backItems.push(it);
else stack.layered.push({ entity: it, y: renderLayerSortY(it), rank: renderLayerKindRank(it) });
}
for (const t of worldRef.tarinai || []) {
if (t && !t.dead) stack.layered.push({ entity: t, y: renderLayerSortY(t), rank: renderLayerKindRank(t) });
}
for (const a of worldRef.ants || []) {
if (a && !a.dead) stack.layered.push({ entity: a, y: renderLayerSortY(a), rank: renderLayerKindRank(a) });
}
stack.backItems.sort((a, b) => renderLayerSortY(a) - renderLayerSortY(b) || (a.x || 0) - (b.x || 0));
stack.layered.sort((a, b) => a.y - b.y || a.rank - b.rank || ((a.entity.x || 0) - (b.entity.x || 0)));
stack.signature = signature;
worldRef.drawListDirty = false;
return stack;
}
function ensureBackgroundCache(w, h, lighting) {
const key = backgroundCacheKey(world, lighting);
if (!backgroundCache.canvas) {
@ -756,19 +805,12 @@ function render() {
drawPlacementPreview(ctx, world);
if (terrainLayer) ctx.drawImage(terrainLayer, 0, 0, sceneW, sceneH);
// Low bedding is drawn one step behind the live sprite stack.
const backItems = [];
const layered = [];
for (const it of world.items) {
if (!it || it.dead || isCachedTerrainItem(it)) continue;
if (isPinType(it.type) && it.pinState === "lodged") continue;
if (it.isStructure && it.type === "plushie" && it.carriedById) continue;
if (!isEntityVisibleInRect(it, visibleRect)) continue;
if (isBehindSpriteLayerItem(it)) backItems.push(it);
else layered.push({ entity: it, y: renderLayerSortY(it), rank: renderLayerKindRank(it) });
// Dynamic render stacks are sorted on a short interval rather than every paint.
// Visibility is still checked per frame, so camera movement remains responsive.
const renderStack = ensureWorldRenderStack(world);
for (const it of renderStack.backItems) {
if (isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
}
backItems.sort((a, b) => renderLayerSortY(a) - renderLayerSortY(b) || (a.x || 0) - (b.x || 0));
for (const it of backItems) it.draw(ctx, world.time, lighting);
ctx.save();
ctx.lineWidth = 1.15;
@ -786,20 +828,15 @@ function render() {
ctx.setLineDash([]);
ctx.restore();
for (const t of world.tarinai || []) {
if (t && !t.dead && isEntityVisibleInRect(t, visibleRect)) layered.push({ entity: t, y: renderLayerSortY(t), rank: renderLayerKindRank(t) });
for (const entry of renderStack.layered) {
if (isEntityVisibleInRect(entry.entity, visibleRect)) entry.entity.draw(ctx, world.time, lighting);
}
for (const a of world.ants || []) {
if (a && !a.dead && isEntityVisibleInRect(a, visibleRect)) layered.push({ entity: a, y: renderLayerSortY(a), rank: renderLayerKindRank(a) });
}
layered.sort((a, b) => a.y - b.y || a.rank - b.rank || ((a.entity.x || 0) - (b.entity.x || 0)));
for (const entry of layered) entry.entity.draw(ctx, world.time, lighting);
for (const it of world.items) {
if (it.isStructure && it.type === "plushie" && it.carriedById && isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
for (const it of renderStack.carriedPlushies) {
if (isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
}
for (const it of world.items) {
if (isPinType(it.type) && it.pinState === "lodged" && isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
for (const it of renderStack.lodgedPins) {
if (isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
}
for (const ef of world.effects) {
if (isEntityVisibleInRect(ef, visibleRect)) ef.draw(ctx);