Eine Aufnahme per Node Start recording, die nicht in Echtzeit läuft: Die Spieluhr rückt pro aufgenommenem Frame genau 1 / fps weiter, egal wie lange der Rechner für das Frame braucht. So geht kein Frame verloren, und 4K60 wird auch auf einem Rechner möglich, der in Echtzeit nur ~39 fps schafft. Wunsch des Users vom 2026-10-14, für den Trailer.
Warum
Gemessen am 2026-10-13/14:
Aufnahme 4K60, Rechner des Users (RTX, NVENC)
echte fps
bisheriger Weg (GPU → YUV → getBufferSubData), ?vsync
39
bisheriger Weg, desynchronized
38 (517 von 827 Frames, 37 % übersprungen)
Versuch: Kacheln über den sichtbaren Canvas, VideoFrame(canvas)
30
Der Engpass ist das Zurücklesen von der GPU bzw. der GPU-Prozess von Chrome, nicht das Zeichnen (drawPicture ~1,5 ms) und nicht der Encoder (~0,05 ms pro encode).
Früher oder später zurücklesen (am Tick-Anfang, 20/40 ms nach dem Zeichnen) brachte nichts. Die Game View am Bildschirm wegzulassen spart unter 1 ms pro Frame.
In Echtzeit gibt es auf dem Rechner also keinen Weg zu 4K60. Die Warnzeile can't keep up: about N of 60 fps aus 77365f1 macht das sichtbar, behebt es aber nicht.
Idee
Feste Zeitschritte: Während einer solchen Aufnahme bekommt die Laufzeit pro Tick genau 1 / fps als dt (statt der Wanduhr, gekappt bei MAX_FRAME in src/core/time.ts). Der nächste Tick kommt erst, wenn das Frame zurückgelesen und an den Encoder übergeben ist. FrameGate überspringt dann nie etwas.
Zeitstempel aus dem Frame-Zähler, nicht aus performance.now(). Die Datei läuft danach in normaler Geschwindigkeit ab, auch wenn die Aufnahme selbst länger gedauert hat.
Einschalten: als Option am Node Start recording (z. B. Real time an/aus). Standard bleibt Echtzeit.
Die harten Teile
Ton: Er läuft heute in Echtzeit auf der Audio-Uhr mit und wird live mitgeschnitten. Bei einer Aufnahme nicht in Echtzeit passt das nicht mehr. Möglichkeiten:
den Mix getrennt in einer OfflineAudioContext rendern (Sounds, Patterns, Songs, Busse, Effekte, Fades der Composition-Items), oder
die Ereignisse mitschreiben (was startet wann, mit welchem Pegel) und den Ton nach der Aufnahme in einem Durchgang rendern.
Video-Ton müsste dabei aus der Datei kommen, nicht aus dem <video>.
Das Nachsynchronisieren in CompositionSound (src/runtime/compositionSound.ts) darf in diesem Modus nicht eingreifen.
Videos: Pro Frame auf die richtige Zeit suchen und warten, bis das Bild da ist. Das Warten auf ein Video hat die Composition schon, aber nicht in diesem Takt.
Eingaben: Ein Spieler lässt sich so nicht sinnvoll aufnehmen (das Spiel läuft langsamer als die Hand). Der Modus passt für Compositions und Szenen ohne Spieler. Eingaben in dieser Zeit ignorieren oder die Aufnahme ablehnen, sobald eine Eingabe kommt.
Anzeige: Die Game View läuft während der Aufnahme sichtbar langsamer. Fortschritt anzeigen (z. B. Recording "Trailer": 4,2 s of video, 1,6x slower than real time).
Partikel, Physik, Animationen, Shader-Zeit müssen alle an der Spieluhr hängen, nicht an der Wanduhr. Vorher prüfen, ob irgendwo performance.now() oder Pixis Ticker direkt verwendet wird.
Offene Fragen
Ton offline rendern oder Ereignisse mitschreiben? Das ist die größte Entscheidung und der größte Teil der Arbeit.
Nur für die Game-View-Aufnahme per Node, oder auch für die Aufnahme des ganzen Editors? Vorschlag: nur per Node.
Tests
Unit: feste Zeitschritte, Zeitstempel aus dem Zähler, FrameGate überspringt nichts.
e2e 67: Aufnahme mit künstlich gebremstem Tick (wie 67b-ii). Die Datei hat trotzdem alle Frames ohne Lücke in den Zeitstempeln.
Ton: ein Sound an einer bekannten Stelle, im Ergebnis auf das Frame genau gegen das Bild prüfen (wie 68h).
Eine Aufnahme per Node *Start recording*, die **nicht in Echtzeit** läuft: Die Spieluhr rückt pro aufgenommenem Frame genau `1 / fps` weiter, egal wie lange der Rechner für das Frame braucht. So geht kein Frame verloren, und 4K60 wird auch auf einem Rechner möglich, der in Echtzeit nur ~39 fps schafft. Wunsch des Users vom 2026-10-14, für den Trailer.
## Warum
Gemessen am 2026-10-13/14:
| Aufnahme 4K60, Rechner des Users (RTX, NVENC) | echte fps |
|---|---|
| bisheriger Weg (GPU → YUV → `getBufferSubData`), `?vsync` | 39 |
| bisheriger Weg, `desynchronized` | 38 (517 von 827 Frames, 37 % übersprungen) |
| Versuch: Kacheln über den sichtbaren Canvas, `VideoFrame(canvas)` | 30 |
- Der Engpass ist das Zurücklesen von der GPU bzw. der GPU-Prozess von Chrome, nicht das Zeichnen (`drawPicture` ~1,5 ms) und nicht der Encoder (~0,05 ms pro `encode`).
- Früher oder später zurücklesen (am Tick-Anfang, 20/40 ms nach dem Zeichnen) brachte nichts. Die Game View am Bildschirm wegzulassen spart unter 1 ms pro Frame.
- In Echtzeit gibt es auf dem Rechner also keinen Weg zu 4K60. Die Warnzeile `can't keep up: about N of 60 fps` aus `77365f1` macht das sichtbar, behebt es aber nicht.
## Idee
- **Feste Zeitschritte:** Während einer solchen Aufnahme bekommt die Laufzeit pro Tick genau `1 / fps` als `dt` (statt der Wanduhr, gekappt bei `MAX_FRAME` in `src/core/time.ts`). Der nächste Tick kommt erst, wenn das Frame zurückgelesen und an den Encoder übergeben ist. `FrameGate` überspringt dann nie etwas.
- **Zeitstempel aus dem Frame-Zähler**, nicht aus `performance.now()`. Die Datei läuft danach in normaler Geschwindigkeit ab, auch wenn die Aufnahme selbst länger gedauert hat.
- **Einschalten:** als Option am Node *Start recording* (z. B. *Real time* an/aus). Standard bleibt Echtzeit.
## Die harten Teile
- **Ton:** Er läuft heute in Echtzeit auf der Audio-Uhr mit und wird live mitgeschnitten. Bei einer Aufnahme nicht in Echtzeit passt das nicht mehr. Möglichkeiten:
- den Mix getrennt in einer `OfflineAudioContext` rendern (Sounds, Patterns, Songs, Busse, Effekte, Fades der Composition-Items), oder
- die Ereignisse mitschreiben (was startet wann, mit welchem Pegel) und den Ton nach der Aufnahme in einem Durchgang rendern.
- Video-Ton müsste dabei aus der Datei kommen, nicht aus dem `<video>`.
- Das Nachsynchronisieren in `CompositionSound` (`src/runtime/compositionSound.ts`) darf in diesem Modus nicht eingreifen.
- **Videos:** Pro Frame auf die richtige Zeit suchen und warten, bis das Bild da ist. Das Warten auf ein Video hat die Composition schon, aber nicht in diesem Takt.
- **Eingaben:** Ein Spieler lässt sich so nicht sinnvoll aufnehmen (das Spiel läuft langsamer als die Hand). Der Modus passt für Compositions und Szenen ohne Spieler. Eingaben in dieser Zeit ignorieren oder die Aufnahme ablehnen, sobald eine Eingabe kommt.
- **Anzeige:** Die Game View läuft während der Aufnahme sichtbar langsamer. Fortschritt anzeigen (z. B. `Recording "Trailer": 4,2 s of video, 1,6x slower than real time`).
- **Partikel, Physik, Animationen, Shader-Zeit** müssen alle an der Spieluhr hängen, nicht an der Wanduhr. Vorher prüfen, ob irgendwo `performance.now()` oder Pixis Ticker direkt verwendet wird.
## Offene Fragen
- Ton offline rendern oder Ereignisse mitschreiben? Das ist die größte Entscheidung und der größte Teil der Arbeit.
- Nur für die Game-View-Aufnahme per Node, oder auch für die Aufnahme des ganzen Editors? Vorschlag: nur per Node.
## Tests
- Unit: feste Zeitschritte, Zeitstempel aus dem Zähler, `FrameGate` überspringt nichts.
- e2e 67: Aufnahme mit künstlich gebremstem Tick (wie 67b-ii). Die Datei hat trotzdem alle Frames ohne Lücke in den Zeitstempeln.
- Ton: ein Sound an einer bekannten Stelle, im Ergebnis auf das Frame genau gegen das Bild prüfen (wie 68h).
Gebaut in 87b2ee9, Wiki cbc0a0f (Videos → Not in real time). Umgesetzt wie entschieden: Ton offline gerendert, nur per Node.
Bedienung
Start recording hat die neue Option Real time (Standard an). Aus heißt: Das Spiel rückt pro Frame der Aufnahme genau 1/fps weiter, egal wie lange der Rechner braucht. Die Datei hat jedes Frame, auch bei 4K60.
Die Notice zeigt den Fortschritt (Recording "Trailer" not in real time: 4.2 s of video, 2.6x real time), die Zeile beim Speichern die Dauer.
Start recording… im Hauptmenü (ganzer Editor) bleibt in Echtzeit.
Bausteine
SteadyPace (src/core/recording/recording.ts): der Takt als reine Klasse. Ein Schritt ist 1/fps, sobald das vorige Frame genommen und der Ton bis dahin gerendert ist, sonst 0 (das Spiel hält).
AudioSwitch (src/runtime/audioSwitch.ts) lenkt den Ton der Partie während der Aufnahme in die Offline-SoundLibrary eines SteadySound (src/viewport/SteadySound.ts): OfflineAudioContext, gleicher Mix, gleiche Busse, FX und Limiter, gerendert bis zum jeweils nächsten Frame.
Gesteuerte Videos (also jedes Video-Item einer Composition) springen auf jedes verlangte Bild, das Frame wartet darauf. Ihr Ton wird aus der MP4 dekodiert und im Takt über ihren Bus gespielt.
Zwei gemessene Chrome-Eigenheiten umgangen: Ein angehaltenes Video liefert das Bild eines Sprungs oft schon vor seeked, und ein Sprung genau auf einen Bildanfang zeigt je nach Rundung das neue oder das vorige Bild. Zusammen von 30 s auf 10,7 s für 2 s Video (Editor absichtlich auf ~20 fps gebremst).
Abweichungen und Grenzen
Eingaben werden nicht ignoriert, sie wirken weiter, das Spiel läuft eben langsamer.
Höchstens 10 Minuten pro Aufnahme: Chrome belegt beim Rendern den ganzen Puffer der OfflineAudioContext (230 MB bei 10 min). Danach stoppt die Aufnahme mit einer Log-Zeile.
Freie Videos (Video clock: Free) laufen weiter in Echtzeit und passen nicht dazu.
Ein Sound, der vor dem Start schon spielt, bleibt an den Lautsprechern und ist nicht in der Aufnahme. Eine laufende Composition wandert mit.
Läuft der Browser-Tab im Hintergrund, steht die Aufnahme (Fund vom User, gilt auch in Echtzeit). Das wird getrennt behandelt.
Tests
Gate auf melektaus-atx grün: 310 Dateien, 4818 Tests (erster Lauf rot: eine Erwartung in node-catalog.test.ts ohne realTime, nachgezogen).
e2e 66 + 68: ALL PASSED, 50 Checks, 68j neu (3 Checks). ffmpeg liest alle 120 Frames im Abstand 1/60 s, Farbwechsel des Videos exakt bei Frame 30/60/90, Video-Ton ab dem ersten Bild, der Blip 8 ms neben seinem Farbwechsel.
e2e 60 und 67 grün. In e2e 60 war ein Check seit 77365f1 rot (Startzeile der Aufnahme im Log), Erwartung nachgezogen.
Gegenprobe ohne Takt: alle drei 68j-Checks rot (32 von 119 Frames, Farbwechsel bei 33/62/96, kein Ton). Der Unit-Test zu AudioSwitch wird rot, wenn die Nummern falsch zugeordnet werden.
Nicht geprüft: 4K60 auf deinem Rechner und eine Aufnahme nicht in Echtzeit im exportierten Spiel (Code da, kein Browser-Test).
Gebaut in `87b2ee9`, Wiki `cbc0a0f` (*Videos → Not in real time*). Umgesetzt wie entschieden: Ton offline gerendert, nur per Node.
## Bedienung
- *Start recording* hat die neue Option **Real time** (Standard an). Aus heißt: Das Spiel rückt pro Frame der Aufnahme genau `1/fps` weiter, egal wie lange der Rechner braucht. Die Datei hat jedes Frame, auch bei 4K60.
- Die Notice zeigt den Fortschritt (`Recording "Trailer" not in real time: 4.2 s of video, 2.6x real time`), die Zeile beim Speichern die Dauer.
- *Start recording…* im Hauptmenü (ganzer Editor) bleibt in Echtzeit.
## Bausteine
- `SteadyPace` (`src/core/recording/recording.ts`): der Takt als reine Klasse. Ein Schritt ist `1/fps`, sobald das vorige Frame genommen und der Ton bis dahin gerendert ist, sonst 0 (das Spiel hält).
- `AudioSwitch` (`src/runtime/audioSwitch.ts`) lenkt den Ton der Partie während der Aufnahme in die Offline-SoundLibrary eines `SteadySound` (`src/viewport/SteadySound.ts`): `OfflineAudioContext`, gleicher Mix, gleiche Busse, FX und Limiter, gerendert bis zum jeweils nächsten Frame.
- Gesteuerte Videos (also jedes Video-Item einer Composition) springen auf jedes verlangte Bild, das Frame wartet darauf. Ihr Ton wird aus der MP4 dekodiert und im Takt über ihren Bus gespielt.
- Zwei gemessene Chrome-Eigenheiten umgangen: Ein angehaltenes Video liefert das Bild eines Sprungs oft schon vor `seeked`, und ein Sprung genau auf einen Bildanfang zeigt je nach Rundung das neue oder das vorige Bild. Zusammen von 30 s auf 10,7 s für 2 s Video (Editor absichtlich auf ~20 fps gebremst).
## Abweichungen und Grenzen
- **Eingaben werden nicht ignoriert**, sie wirken weiter, das Spiel läuft eben langsamer.
- Höchstens 10 Minuten pro Aufnahme: Chrome belegt beim Rendern den ganzen Puffer der `OfflineAudioContext` (230 MB bei 10 min). Danach stoppt die Aufnahme mit einer Log-Zeile.
- Freie Videos (*Video clock: Free*) laufen weiter in Echtzeit und passen nicht dazu.
- Ein Sound, der vor dem Start schon spielt, bleibt an den Lautsprechern und ist nicht in der Aufnahme. Eine laufende Composition wandert mit.
- Läuft der Browser-Tab im Hintergrund, steht die Aufnahme (Fund vom User, gilt auch in Echtzeit). Das wird getrennt behandelt.
## Tests
- Gate auf `melektaus-atx` grün: 310 Dateien, 4818 Tests (erster Lauf rot: eine Erwartung in `node-catalog.test.ts` ohne `realTime`, nachgezogen).
- e2e 66 + 68: ALL PASSED, 50 Checks, **68j** neu (3 Checks). ffmpeg liest alle 120 Frames im Abstand 1/60 s, Farbwechsel des Videos exakt bei Frame 30/60/90, Video-Ton ab dem ersten Bild, der Blip 8 ms neben seinem Farbwechsel.
- e2e 60 und 67 grün. In e2e 60 war ein Check seit `77365f1` rot (Startzeile der Aufnahme im Log), Erwartung nachgezogen.
- Gegenprobe ohne Takt: alle drei 68j-Checks rot (32 von 119 Frames, Farbwechsel bei 33/62/96, kein Ton). Der Unit-Test zu `AudioSwitch` wird rot, wenn die Nummern falsch zugeordnet werden.
**Nicht geprüft:** 4K60 auf deinem Rechner und eine Aufnahme nicht in Echtzeit im exportierten Spiel (Code da, kein Browser-Test).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Eine Aufnahme per Node Start recording, die nicht in Echtzeit läuft: Die Spieluhr rückt pro aufgenommenem Frame genau
1 / fpsweiter, egal wie lange der Rechner für das Frame braucht. So geht kein Frame verloren, und 4K60 wird auch auf einem Rechner möglich, der in Echtzeit nur ~39 fps schafft. Wunsch des Users vom 2026-10-14, für den Trailer.Warum
Gemessen am 2026-10-13/14:
getBufferSubData),?vsyncdesynchronizedVideoFrame(canvas)drawPicture~1,5 ms) und nicht der Encoder (~0,05 ms proencode).can't keep up: about N of 60 fpsaus77365f1macht das sichtbar, behebt es aber nicht.Idee
1 / fpsalsdt(statt der Wanduhr, gekappt beiMAX_FRAMEinsrc/core/time.ts). Der nächste Tick kommt erst, wenn das Frame zurückgelesen und an den Encoder übergeben ist.FrameGateüberspringt dann nie etwas.performance.now(). Die Datei läuft danach in normaler Geschwindigkeit ab, auch wenn die Aufnahme selbst länger gedauert hat.Die harten Teile
OfflineAudioContextrendern (Sounds, Patterns, Songs, Busse, Effekte, Fades der Composition-Items), oder<video>.CompositionSound(src/runtime/compositionSound.ts) darf in diesem Modus nicht eingreifen.Recording "Trailer": 4,2 s of video, 1,6x slower than real time).performance.now()oder Pixis Ticker direkt verwendet wird.Offene Fragen
Tests
FrameGateüberspringt nichts.Gebaut in
87b2ee9, Wikicbc0a0f(Videos → Not in real time). Umgesetzt wie entschieden: Ton offline gerendert, nur per Node.Bedienung
1/fpsweiter, egal wie lange der Rechner braucht. Die Datei hat jedes Frame, auch bei 4K60.Recording "Trailer" not in real time: 4.2 s of video, 2.6x real time), die Zeile beim Speichern die Dauer.Bausteine
SteadyPace(src/core/recording/recording.ts): der Takt als reine Klasse. Ein Schritt ist1/fps, sobald das vorige Frame genommen und der Ton bis dahin gerendert ist, sonst 0 (das Spiel hält).AudioSwitch(src/runtime/audioSwitch.ts) lenkt den Ton der Partie während der Aufnahme in die Offline-SoundLibrary einesSteadySound(src/viewport/SteadySound.ts):OfflineAudioContext, gleicher Mix, gleiche Busse, FX und Limiter, gerendert bis zum jeweils nächsten Frame.seeked, und ein Sprung genau auf einen Bildanfang zeigt je nach Rundung das neue oder das vorige Bild. Zusammen von 30 s auf 10,7 s für 2 s Video (Editor absichtlich auf ~20 fps gebremst).Abweichungen und Grenzen
OfflineAudioContext(230 MB bei 10 min). Danach stoppt die Aufnahme mit einer Log-Zeile.Tests
melektaus-atxgrün: 310 Dateien, 4818 Tests (erster Lauf rot: eine Erwartung innode-catalog.test.tsohnerealTime, nachgezogen).77365f1rot (Startzeile der Aufnahme im Log), Erwartung nachgezogen.AudioSwitchwird rot, wenn die Nummern falsch zugeordnet werden.Nicht geprüft: 4K60 auf deinem Rechner und eine Aufnahme nicht in Echtzeit im exportierten Spiel (Code da, kein Browser-Test).