From 13c8710bf5bf08d1c5a71e9acd7aedff67bfab11 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 15:07:14 +0000 Subject: [PATCH] Light3D: address review findings on the per-kind light budget - Evict lights that no longer fit the moment the device budget is shared with a new light kind, so a frame never renders over the budget. - Read the renderer on every budget recomputation instead of capturing it once, so a scene created before the renderer exists is not stuck on the guaranteed minimums. - Say in the max lights count descriptions that the caps are per light kind and that devices can enforce a lower limit. - Drop a stale mention of distance inflation for ties in insertByDistance. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GX7gMRj2UUgEFkBV4vpajs --- extensions/reviewed/Light3D.json | 33 +++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/extensions/reviewed/Light3D.json b/extensions/reviewed/Light3D.json index aa5b72be6..e72ed1c35 100644 --- a/extensions/reviewed/Light3D.json +++ b/extensions/reviewed/Light3D.json @@ -774,18 +774,18 @@ "function getLightManager(runtimeScene) {", " if (!runtimeScene.__lightManager) {", " // Create the shared manager if necessary.", - " const threeRenderer = runtimeScene", - " .getGame()", - " .getRenderer()", - " .getThreeRenderer();", " const requestedCount = isInGameEdition", " ? editorLightCountMax : lightCountMax;", " const requestedShadowCount = isInGameEdition", " ? editorLightShadowCountMax : lightShadowCountMax;", "", + " // The renderer is read anew on every recomputation: it may not", + " // exist yet when the manager is created on the first frame.", " runtimeScene.__lightManager = new LightManager(", " requestedCount, requestedShadowCount,", - " kindCount => computeDeviceLightBudget(threeRenderer, kindCount));", + " kindCount => computeDeviceLightBudget(", + " runtimeScene.getGame().getRenderer().getThreeRenderer(),", + " kindCount));", " }", " return runtimeScene.__lightManager;", "}", @@ -825,10 +825,16 @@ " this.objects.length = 0;", " }", "", + " /** Drop the farthest lights until the list fits its capacity again. */", + " trimToCapacity() {", + " while (this.objects.length > this.capacity.value) {", + " this.onDeletion(this.objects.pop().object);", + " }", + " }", + "", " /**", " * @param object {LightRuntimeObject}", - " * @param sortDistance {number} The squared distance to the camera, which", - " * more expensive lights can inflate to lose ties against cheaper ones.", + " * @param sortDistance {number} The squared distance to the camera.", " */", " insertByDistance(object, sortDistance) {", " let insertionIndex = 0;", @@ -955,6 +961,15 @@ " this.usedKinds.add(kind);", " this.deviceBudget = this._computeDeviceBudget(this.usedKinds.size);", "", + " // The budget just shrank: evict the lights that no longer fit, so", + " // that even this frame stays within what the device can take.", + " for (const list of this.visibleLists.values()) {", + " list.trimToCapacity();", + " }", + " for (const list of this.shadowLists.values()) {", + " list.trimToCapacity();", + " }", + "", " if (this.deviceBudget.count < this._requestedCount.value", " || this.deviceBudget.shadowCount", " < this._requestedShadowCount.value) {", @@ -1051,7 +1066,7 @@ "objectGroups": [] }, { - "description": "the maximum number of nearest lights displayed simultaneously.", + "description": "the maximum number of nearest lights displayed simultaneously, counted separately for each kind of light (point lights, spot lights, spot lights projecting a texture). Devices that can't take that many lights enforce a lower limit.", "fullName": "Max lights count", "functionType": "ExpressionAndCondition", "name": "LightCountMax", @@ -1124,7 +1139,7 @@ "objectGroups": [] }, { - "description": "the maximum number of nearest lights displayed with shadow simultaneously.", + "description": "the maximum number of nearest lights displayed with shadow simultaneously, counted separately for each kind of light (point lights, spot lights, spot lights projecting a texture). Devices that can't take that many shadows enforce a lower limit.", "fullName": "Max lights with shadow count", "functionType": "ExpressionAndCondition", "name": "LightShadowCountMax",