From 1aaf75084cb1e2a4d052dcdb720ad3f5fd6c5a3d Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Wed, 19 Aug 2026 11:23:40 +0100 Subject: [PATCH 1/6] feat(vm): add fps_real builtin and fix fps builtin fixes the camera surface in Alphys's lab freezing in Undertale --- src/runner.c | 17 +++++++++++++++++ src/runner.h | 6 ++++++ src/vm_builtins.c | 9 ++++++--- src/vm_builtins.h | 1 + 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/runner.c b/src/runner.c index f0fd443b9..8eff98309 100644 --- a/src/runner.c +++ b/src/runner.c @@ -2296,6 +2296,11 @@ Runner* Runner_create(DataWin* dataWin, VMContext* vm, Renderer* renderer, FileS runner->fileSystem = fileSystem; runner->audioSystem = audioSystem; runner->frameCount = 0; + GMLReal initialFps = dataWin->gen8.gms2FPS; + runner->fps = initialFps; + runner->fpsReal = initialFps; + runner->fpsWindowStartFrame = 0; + runner->fpsWindowStartNanos = nowNanos(); runner->osType = OS_WINDOWS; runner->keyboard = RunnerKeyboard_create(); runner->gamepads = RunnerGamepad_create(); @@ -4094,6 +4099,18 @@ void Runner_step(Runner* runner) { Runner_cleanupDestroyedInstances(runner); Runner_sweepDeadStructs(runner); + // Measure fps builtin + if (nowNanos() - runner->fpsWindowStartNanos >= (uint64_t)1000000000) { + runner->fps = (GMLReal) (runner->frameCount - runner->fpsWindowStartFrame); + runner->fpsWindowStartFrame = runner->frameCount; + runner->fpsWindowStartNanos = nowNanos(); + } + + // Measure fps_real builtin + if (runner->deltaTime > 0.0) { + runner->fpsReal = (GMLReal) (1000000.0 / runner->deltaTime); + } + runner->frameCount++; } diff --git a/src/runner.h b/src/runner.h index 9efb1d4e4..21a3acade 100644 --- a/src/runner.h +++ b/src/runner.h @@ -550,6 +550,12 @@ struct Runner { int32_t forcedDepth; // The time between the last frame and the current frame, stored in microseconds. double deltaTime; + // Current frame rate (capped at room speed) + GMLReal fps; // last measured frames-per-second value returned to GML + uint64_t fpsWindowStartNanos; // nowNanos() at the start of the measurement window + int fpsWindowStartFrame; // runner->frameCount at the start of the measurement window + // Real-time measured frame rate for the "fps_real" builtin, computed from deltaTime. + GMLReal fpsReal; char* windowTitle; // ===[ Builtin function state ]=== diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 51241c919..246013437 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -323,6 +323,7 @@ static const BuiltinVarEntry BUILTIN_VAR_TABLE[] = { { "direction", BUILTIN_VAR_DIRECTION }, { "false", BUILTIN_VAR_FALSE }, { "fps", BUILTIN_VAR_FPS }, + { "fps_real", BUILTIN_VAR_FPS_REAL }, { "friction", BUILTIN_VAR_FRICTION }, { "gp_axislh", BUILTIN_VAR_GP_AXIS_LH }, { "gp_axislv", BUILTIN_VAR_GP_AXIS_LV }, @@ -1283,7 +1284,9 @@ RValue VMBuiltins_getVariable(VMContext* ctx, Instance* inst, int16_t builtinVar return RValue_makeReal((GMLReal) INSTANCE_NOONE); } case BUILTIN_VAR_FPS: - return RValue_makeReal(ctx->dataWin->gen8.gms2FPS); + return RValue_makeReal(runner->fps); + case BUILTIN_VAR_FPS_REAL: + return RValue_makeReal(runner->fpsReal); case BUILTIN_VAR_DEBUG_MODE: return RValue_makeBool(false); case BUILTIN_VAR_DELTA_TIME: @@ -5674,7 +5677,7 @@ static RValue builtin_ds_queue_read(VMContext* ctx, RValue* args, MAYBE_UNUSED i if (s.error || 0 > last) { free(bytes); return RValue_makeBool(false); } // Replace queue contents. - { + { repeat(arrlen(q->items), i) { RValue_free(&q->items[i]); } @@ -18221,7 +18224,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "tile_set_empty", builtin_tile_set_empty); VM_registerBuiltin(ctx, "tile_set_mirror", builtin_tile_set_mirror); VM_registerBuiltin(ctx, "tile_set_flip", builtin_tile_set_flip); - VM_registerBuiltin(ctx, "tile_set_rotate", builtin_tile_set_rotate); + VM_registerBuiltin(ctx, "tile_set_rotate", builtin_tile_set_rotate); VM_registerBuiltin(ctx, "tilemap_set", builtin_tilemap_set); VM_registerBuiltin(ctx, "tilemap_set_at_pixel", builtin_tilemap_set_at_pixel); #endif diff --git a/src/vm_builtins.h b/src/vm_builtins.h index b8cd35674..bdc57002f 100644 --- a/src/vm_builtins.h +++ b/src/vm_builtins.h @@ -238,6 +238,7 @@ typedef enum { BUILTIN_VAR_INSTANCE_COUNT, BUILTIN_VAR_INSTANCE_ID, BUILTIN_VAR_FPS, + BUILTIN_VAR_FPS_REAL, BUILTIN_VAR_DEBUG_MODE, BUILTIN_VAR_DELTA_TIME, From 26ec7a9f381e6764ec82c095a6531c2f1eacbd61 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Wed, 19 Aug 2026 11:28:51 +0100 Subject: [PATCH 2/6] fix ps2? --- src/runner.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runner.c b/src/runner.c index 8eff98309..699b31d3a 100644 --- a/src/runner.c +++ b/src/runner.c @@ -4107,8 +4107,8 @@ void Runner_step(Runner* runner) { } // Measure fps_real builtin - if (runner->deltaTime > 0.0) { - runner->fpsReal = (GMLReal) (1000000.0 / runner->deltaTime); + if (runner->deltaTime > 0.0f) { + runner->fpsReal = (GMLReal) (1000000.0f / runner->deltaTime); } runner->frameCount++; From 10ef84d1ef07f1d0d3292954bf1174ea715ee443 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Wed, 19 Aug 2026 11:33:58 +0100 Subject: [PATCH 3/6] cast it all to gmlreal --- src/runner.c | 4 ++-- src/runner.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/runner.c b/src/runner.c index 699b31d3a..8e9cd77e4 100644 --- a/src/runner.c +++ b/src/runner.c @@ -4107,8 +4107,8 @@ void Runner_step(Runner* runner) { } // Measure fps_real builtin - if (runner->deltaTime > 0.0f) { - runner->fpsReal = (GMLReal) (1000000.0f / runner->deltaTime); + if (runner->deltaTime > (GMLReal) 0.0) { + runner->fpsReal = (GMLReal) ((GMLReal) 1000000.0 / runner->deltaTime); } runner->frameCount++; diff --git a/src/runner.h b/src/runner.h index 21a3acade..66b824763 100644 --- a/src/runner.h +++ b/src/runner.h @@ -549,7 +549,7 @@ struct Runner { Instance** structInstances; int32_t forcedDepth; // The time between the last frame and the current frame, stored in microseconds. - double deltaTime; + GMLReal deltaTime; // Current frame rate (capped at room speed) GMLReal fps; // last measured frames-per-second value returned to GML uint64_t fpsWindowStartNanos; // nowNanos() at the start of the measurement window From 8b4f31582c8fc02ad46670f0d74b1652633cde8f Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Wed, 19 Aug 2026 11:35:38 +0100 Subject: [PATCH 4/6] fix --- src/runner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner.c b/src/runner.c index 8e9cd77e4..727c441e0 100644 --- a/src/runner.c +++ b/src/runner.c @@ -3838,7 +3838,7 @@ void Runner_step(Runner* runner) { if (entry->units == 1) { entry->elapsed += (double)1.0; } else { - entry->elapsed += runner->deltaTime / (double)1000000.0; + entry->elapsed += (double)runner->deltaTime / (double)1000000.0; } if (entry->elapsed >= entry->period) { From 957ccaf6a29c2f6deaef1e54a6adbbce5aa9c369 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 24 Aug 2026 08:51:43 +0100 Subject: [PATCH 5/6] double --- src/runner.c | 8 ++++---- src/runner.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/runner.c b/src/runner.c index 727c441e0..f1043cae0 100644 --- a/src/runner.c +++ b/src/runner.c @@ -2296,7 +2296,7 @@ Runner* Runner_create(DataWin* dataWin, VMContext* vm, Renderer* renderer, FileS runner->fileSystem = fileSystem; runner->audioSystem = audioSystem; runner->frameCount = 0; - GMLReal initialFps = dataWin->gen8.gms2FPS; + double initialFps = (double)dataWin->gen8.gms2FPS; runner->fps = initialFps; runner->fpsReal = initialFps; runner->fpsWindowStartFrame = 0; @@ -4101,14 +4101,14 @@ void Runner_step(Runner* runner) { // Measure fps builtin if (nowNanos() - runner->fpsWindowStartNanos >= (uint64_t)1000000000) { - runner->fps = (GMLReal) (runner->frameCount - runner->fpsWindowStartFrame); + runner->fps = (double)(runner->frameCount - runner->fpsWindowStartFrame); runner->fpsWindowStartFrame = runner->frameCount; runner->fpsWindowStartNanos = nowNanos(); } // Measure fps_real builtin - if (runner->deltaTime > (GMLReal) 0.0) { - runner->fpsReal = (GMLReal) ((GMLReal) 1000000.0 / runner->deltaTime); + if (runner->deltaTime > 0.0) { + runner->fpsReal = 1000000.0 / (double)runner->deltaTime; } runner->frameCount++; diff --git a/src/runner.h b/src/runner.h index 66b824763..81ff2a243 100644 --- a/src/runner.h +++ b/src/runner.h @@ -551,11 +551,11 @@ struct Runner { // The time between the last frame and the current frame, stored in microseconds. GMLReal deltaTime; // Current frame rate (capped at room speed) - GMLReal fps; // last measured frames-per-second value returned to GML + double fps; // last measured frames-per-second value returned to GML uint64_t fpsWindowStartNanos; // nowNanos() at the start of the measurement window int fpsWindowStartFrame; // runner->frameCount at the start of the measurement window // Real-time measured frame rate for the "fps_real" builtin, computed from deltaTime. - GMLReal fpsReal; + double fpsReal; char* windowTitle; // ===[ Builtin function state ]=== From 098861058d027e0400ac98e7c804a69a41b22881 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 24 Aug 2026 08:53:23 +0100 Subject: [PATCH 6/6] explicit --- src/runner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner.c b/src/runner.c index f1043cae0..7d3ac55f1 100644 --- a/src/runner.c +++ b/src/runner.c @@ -4108,7 +4108,7 @@ void Runner_step(Runner* runner) { // Measure fps_real builtin if (runner->deltaTime > 0.0) { - runner->fpsReal = 1000000.0 / (double)runner->deltaTime; + runner->fpsReal = (double)(1000000.0 / runner->deltaTime); } runner->frameCount++;