diff --git a/README.md b/README.md
index 18b413df..92330035 100644
--- a/README.md
+++ b/README.md
@@ -549,6 +549,10 @@ selected and switched back on.
## Stack
- **PixiJS v8** – rendering and pointer events. No game engine; runtime systems live in `src/runtime/`.
+ `main.ts` creates the WebGL context itself with `desynchronized`, which Pixi has no option for:
+ frames skip the compositor queue, so the UI keeps up with the pointer (click to picture measured
+ 72–77 ms before, 16–18 ms after). The price is possible tearing while things move fast;
+ `?vsync` falls back to Pixi's own context. A browser without the mode simply ignores it.
- **Own canvas widget toolkit** (`src/ui/`) – panels, buttons, lists, text/number/color fields,
popups. The only lasting DOM element is a hidden `` used as a keyboard bridge (IME,
mobile keyboard, clipboard) while a canvas text field is focused; *Upload…* adds a short-lived
@@ -640,7 +644,7 @@ npm run check # typecheck + lint + format:check + tests, what CI would ru
# code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html
export GM_REMOTE=user@host # optional: run the browser elsewhere; see below
-node tools/seed-e2e.mjs && node tools/e2e.mjs # 534 editor checks, screenshots in /tmp/gm-e2e-*.png
+node tools/seed-e2e.mjs && node tools/e2e.mjs # 535 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 – a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
diff --git a/src/editor/debugHook.ts b/src/editor/debugHook.ts
index b2884749..5e252458 100644
--- a/src/editor/debugHook.ts
+++ b/src/editor/debugHook.ts
@@ -281,6 +281,9 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
};
},
fps: () => editor.fps,
+ lowLatency: () =>
+ (app.renderer as unknown as { gl?: WebGL2RenderingContext }).gl?.getContextAttributes()
+ ?.desynchronized === true,
tab: () => editor.tab,
showTab: (tab: CentreTab) => editor.showTab(tab),
};
diff --git a/src/main.ts b/src/main.ts
index c879a774..f730558f 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -6,7 +6,22 @@ import { startEditor } from './editor/App';
import { mountPlayground } from './playground';
import { initUI, Theme, VStack } from './ui';
+// `desynchronized` bypasses the compositor queue (headless click-to-paint 72 → 16 ms) but may
+// tear; `?vsync` opts out. Pixi has no option for it, hence our own context.
+function lowLatencyContext(): { canvas?: HTMLCanvasElement; context?: WebGL2RenderingContext } {
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('webgl2', {
+ alpha: false,
+ antialias: true,
+ premultipliedAlpha: true,
+ stencil: true,
+ desynchronized: true,
+ });
+ return context ? { canvas, context } : {};
+}
+
async function main(): Promise {
+ const params = new URLSearchParams(location.search);
const app = new Application();
await app.init({
resizeTo: window,
@@ -15,6 +30,7 @@ async function main(): Promise {
resolution: Math.min(2, window.devicePixelRatio || 1),
autoDensity: true,
preference: 'webgl',
+ ...(params.has('vsync') ? {} : lowLatencyContext()),
});
document.body.appendChild(app.canvas);
@@ -25,7 +41,6 @@ async function main(): Promise {
app.stage.hitArea = app.screen;
initUI(app, root, overlay);
- const params = new URLSearchParams(location.search);
if (params.has('playground')) mountPlayground(app, root);
else await startEditor(app, root);
diff --git a/todo-notes/graph-node-collapse-width.md b/todo-notes/graph-node-collapse-width.md
new file mode 100644
index 00000000..19f83ab1
--- /dev/null
+++ b/todo-notes/graph-node-collapse-width.md
@@ -0,0 +1,2 @@
+man kann ja nun graph nodes zusammenklappen.
+hier soll auch die breite veringert werden auf die titeltext breite wenn möglich?
diff --git a/tools/e2e/06-graph.mjs b/tools/e2e/06-graph.mjs
index 89c582f1..8a54bfab 100644
--- a/tools/e2e/06-graph.mjs
+++ b/tools/e2e/06-graph.mjs
@@ -253,6 +253,10 @@ export async function graph(t) {
// Graph while playing shows the live highlight: hold ArrowRight → the exec edge is hot.
const fpsEdit = await evaluate('__gm.fps()');
check('fps readout is measured in edit mode', typeof fpsEdit === 'number' && fpsEdit > 0, `fps=${fpsEdit}`);
+ check(
+ 'the canvas bypasses the compositor queue by default',
+ (await evaluate('__gm.lowLatency()')) === true,
+ );
// Single evaluate: the readout refreshes twice a second, so read value and label together.
check(
'fps label is visible in the toolbar',