From 560a76ef438a0dcacbc721e5640ad2749da15955 Mon Sep 17 00:00:00 2001 From: Jimmy-Zheng <264134310+JimmyZheng-ZJU@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:12:42 +0800 Subject: [PATCH 1/2] test(nodes): preserve elevator dirty tracking investigation --- .../nodes/src/elevator/definition.test.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 packages/nodes/src/elevator/definition.test.ts diff --git a/packages/nodes/src/elevator/definition.test.ts b/packages/nodes/src/elevator/definition.test.ts new file mode 100644 index 0000000000..ebf7c9c633 --- /dev/null +++ b/packages/nodes/src/elevator/definition.test.ts @@ -0,0 +1,54 @@ +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' +import { + type AnyNodeDefinition, + type AnyNodeId, + ElevatorNode, + nodeRegistry, + registerNode, + useScene, +} from '@pascal-app/core' +import { elevatorDefinition } from './definition' + +describe('elevatorDefinition', () => { + // The elevator has no dirty consumer: it ships no `def.geometry` (so + // GeometrySystem skips it and never calls clearDirty), and none of its + // three systems (runtime / interaction / opening) read `dirtyNodes`. + // Without the opt-out, the scene-load full markDirty leaves the elevator + // permanently dirty — perf HUD shows "DIRTY 1", the frame limiter never + // sees an idle scene (elevator has `def.system`, so its dirty mark counts + // as pending render work), and post-processing scheduling sees a non-zero + // dirty count forever. + test('opts out of dirty tracking — no system ever clears its dirty mark', () => { + expect(elevatorDefinition.dirtyTracking).toBe(false) + }) + + describe('markDirty with the registered definition', () => { + beforeEach(() => { + nodeRegistry._reset() + registerNode(elevatorDefinition as unknown as AnyNodeDefinition) + }) + + afterEach(() => { + nodeRegistry._reset() + }) + + // Membership asserts (not set size/equality): the scene store is a module + // singleton, and subscribers leaked by other test files can add their own + // dirty marks when `setState` fires. + test('scene-load style markDirty leaves no permanent elevator residue', () => { + const elevator = ElevatorNode.parse({ + id: 'elevator_dirty_test' as never, + type: 'elevator', + }) + useScene.setState({ + nodes: { [elevator.id]: elevator } as never, + rootNodeIds: [elevator.id], + dirtyNodes: new Set(), + } as never) + + useScene.getState().markDirty(elevator.id as AnyNodeId) + + expect(useScene.getState().dirtyNodes.has(elevator.id as AnyNodeId)).toBe(false) + }) + }) +}) From 39c59acaa3e14c7c4da7d56db4839fab15d5ca47 Mon Sep 17 00:00:00 2001 From: Jimmy-Zheng <264134310+JimmyZheng-ZJU@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:28:28 +0800 Subject: [PATCH 2/2] fix(nodes): stop tracking elevator dirty state --- packages/nodes/src/elevator/definition.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/nodes/src/elevator/definition.ts b/packages/nodes/src/elevator/definition.ts index bd181115f1..5d610b099c 100644 --- a/packages/nodes/src/elevator/definition.ts +++ b/packages/nodes/src/elevator/definition.ts @@ -242,6 +242,9 @@ export const elevatorDefinition: NodeDefinition = { parametrics: elevatorParametrics, handles: elevatorHandles, + // No dirty consumer rebuilds this kind — see NodeDefinition.dirtyTracking. + dirtyTracking: false, + renderer: { kind: 'parametric', module: () => import('./renderer'),