From 8dee7d8658a7443f6568d958cfb2371f66d332b3 Mon Sep 17 00:00:00 2001 From: kichikuou Date: Thu, 4 Jun 2026 16:45:30 +0900 Subject: [PATCH 1/5] PartsEngine: Implement PartsFunc 6 (SaveThumbnail) Save a reduced copy of the main surface to a file. The image is written as QNT under the save directory. To avoid aliasing at large reductions, the surface is downscaled by repeatedly halving before the final stretch. --- include/parts.h | 1 + src/hll/PartsEngine.c | 5 +++++ src/parts/parts.c | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/include/parts.h b/include/parts.h index e814436..7dab905 100644 --- a/include/parts.h +++ b/include/parts.h @@ -131,6 +131,7 @@ void PE_set_active_controller(int controller_no); int PE_get_active_controller(void); int PE_get_system_controller(void); void PE_parts_set_want_save(int parts_no, bool want_save); +bool PE_save_thumbnail(struct string *filename, int reduction_factor); bool PE_init_parts_movie(int parts_no, int width, int height, int bg_r, int bg_g, int bg_b, int state); int PE_get_movie_sprite(int parts_no, int state); float PE_parts_get_absolute_x(int parts_no); diff --git a/src/hll/PartsEngine.c b/src/hll/PartsEngine.c index 08d445c..4e74357 100644 --- a/src/hll/PartsEngine.c +++ b/src/hll/PartsEngine.c @@ -264,6 +264,11 @@ static int PartsEngine_PartsFunc(int func_id, struct page **array_int, REQUIRE_INTS(2); PE_parts_set_want_save(ints[0].i, !!ints[1].i); return 1; + case 6: // bool SaveThumbnail(string filename, int reduction_factor) + REQUIRE_INTS(2); + REQUIRE_STRINGS(1); + ints[1].i = PE_save_thumbnail(heap_get_string(strings[0].i), ints[0].i); + return 1; case 40: // float PARTS_GetAbsoluteX(int number) REQUIRE_INTS(1); REQUIRE_FLOATS(1); diff --git a/src/parts/parts.c b/src/parts/parts.c index fd8ec8f..02475e0 100644 --- a/src/parts/parts.c +++ b/src/parts/parts.c @@ -1955,6 +1955,44 @@ bool PE_SetThumbnailMode(bool mode) return true; } +bool PE_save_thumbnail(struct string *filename, int reduction_factor) +{ + if (reduction_factor < 1) + reduction_factor = 1; + + Texture *src = gfx_main_surface(); + int w = src->w / reduction_factor; + int h = src->h / reduction_factor; + + // Downscale by repeatedly halving until we are within a factor of two of + // the target size, then do the final stretch. This avoids the aliasing + // that a single bilinear minification would otherwise produce. + Texture tmp, *cur = src; + bool have_tmp = false; + while (cur->w / 2 > w && cur->h / 2 > h) { + Texture next; + gfx_init_texture_blank(&next, cur->w / 2, cur->h / 2); + gfx_copy_stretch_with_alpha_map(&next, 0, 0, next.w, next.h, cur, 0, 0, cur->w, cur->h); + if (have_tmp) + gfx_delete_texture(&tmp); + tmp = next; + cur = &tmp; + have_tmp = true; + } + + Texture dst; + gfx_init_texture_blank(&dst, w, h); + gfx_copy_stretch_with_alpha_map(&dst, 0, 0, w, h, cur, 0, 0, cur->w, cur->h); + if (have_tmp) + gfx_delete_texture(&tmp); + + char *path = savedir_path(filename->text); + int r = gfx_save_texture(&dst, path, ALCG_QNT); + free(path); + gfx_delete_texture(&dst); + return !!r; +} + void PE_SetInputState(int parts_no, int state) { if (!parts_state_valid(--state)) { From ec9bd95dc90876a01d8e03fa7bcca1a2dd3cf9e1 Mon Sep 17 00:00:00 2001 From: kichikuou Date: Fri, 5 Jun 2026 08:46:39 +0900 Subject: [PATCH 2/5] PartsEngine: Implement PartsFunc 3 (motion Pause/Resume) Wire up PartsFunc 3 (PauseMotion) to a new PE_PauseMotion(). While paused, PE_UpdateMotionTime stops advancing the motion clock. --- include/parts.h | 1 + src/hll/PartsEngine.c | 4 ++++ src/parts/motion.c | 23 +++++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/include/parts.h b/include/parts.h index 7dab905..db6ff6b 100644 --- a/include/parts.h +++ b/include/parts.h @@ -248,6 +248,7 @@ void PE_SeekEndMotion(void); void PE_UpdateMotionTime(int time, bool skip); bool PE_IsMotion(void); int PE_GetMotionEndTime(void); +void PE_PauseMotion(bool pause); // text.c bool PE_SetText(int parts_no, struct string *text, int state); diff --git a/src/hll/PartsEngine.c b/src/hll/PartsEngine.c index 4e74357..3d758e4 100644 --- a/src/hll/PartsEngine.c +++ b/src/hll/PartsEngine.c @@ -260,6 +260,10 @@ static int PartsEngine_PartsFunc(int func_id, struct page **array_int, REQUIRE_INTS(1); ints[0].i = PE_get_system_controller(); return 1; + case 3: // void PauseMotion(bool pause) + REQUIRE_INTS(1); + PE_PauseMotion(!!ints[0].i); + return 1; case 4: // void SetWantSave(int parts_no, bool want_save) REQUIRE_INTS(2); PE_parts_set_want_save(ints[0].i, !!ints[1].i); diff --git a/src/parts/motion.c b/src/parts/motion.c index 13a593f..2d29d0d 100644 --- a/src/parts/motion.c +++ b/src/parts/motion.c @@ -38,6 +38,8 @@ static struct parts_motion_list global_motion_list = // true after call to BeginMotion until the motion ends or EndMotion is called static bool is_motion = false; +// true while the motion is paused (time does not advance) +static bool is_motion_paused = false; // the elapsed time of the current motion static int motion_t = 0; // the end time of the current motion @@ -498,6 +500,7 @@ void PE_BeginMotion(void) // FIXME: starting a motion seems to clear non-default states motion_t = 0; is_motion = true; + is_motion_paused = false; parts_init_all_motion(); } @@ -508,6 +511,7 @@ void PE_EndMotion(void) motion_t = 0; motion_end_t = 0; is_motion = false; + is_motion_paused = false; parts_fini_all_motion(); } @@ -531,6 +535,8 @@ void PE_SeekEndMotion(void) void PE_UpdateMotionTime(int time, possibly_unused bool skip) { // TODO: use skip + if (is_motion_paused) + return; PE_SetMotionTime(motion_t + time); } @@ -543,3 +549,20 @@ int PE_GetMotionEndTime(void) { return motion_end_t; } + +void PE_PauseMotion(bool pause) +{ + if (parts_began_click) + return; + if (!is_motion) + return; + if (is_motion_paused == pause) + return; + is_motion_paused = pause; + + // While paused, the per-frame advance stops updating the whole-screen + // vibration offset. Reset it to the origin so the screen doesn't stay + // stuck at the last shake position. + if (pause) + parts_set_global_pos((Point){ 0, 0 }); +} From 32024bc8058b4badd9f2fac034b52473619dc478 Mon Sep 17 00:00:00 2001 From: kichikuou Date: Fri, 5 Jun 2026 09:25:27 +0900 Subject: [PATCH 3/5] PartsEngine: Fix "Invalid controller number" error after vm_reset PE_Reset() zeroed the controller stack, leaving it with no default controller. Since PE_Reset() does not clear parts_engine_initialized, the subsequent PE_Init() returns early so the stack stays empty. Reset the stack with ctrl_stack_init() instead, which re-creates the default controller. ctrl_stack_fini() is now unused and removed. --- src/parts/parts.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/parts/parts.c b/src/parts/parts.c index 02475e0..9279277 100644 --- a/src/parts/parts.c +++ b/src/parts/parts.c @@ -40,7 +40,6 @@ struct parts_controller_stack ctrl_stack; bool parts_multi_controller; static void ctrl_stack_init(void); -static void ctrl_stack_fini(void); #define PARTS_PARAMS_INITIALIZER (struct parts_params) { \ .z = 1, \ @@ -1062,7 +1061,7 @@ void PE_Reset(void) { PE_ReleaseAllParts(); PE_ReleaseMessage(); - ctrl_stack_fini(); + ctrl_stack_init(); sact_ModuleFini(); } @@ -2110,11 +2109,6 @@ static void ctrl_stack_init(void) PE_AddController(-1); } -static void ctrl_stack_fini(void) -{ - memset(&ctrl_stack, 0, sizeof(ctrl_stack)); -} - // Adds a new controller to the stack and makes it active. The `index` // parameter specifies the position in the stack at which to insert the new // controller; -1 means "insert directly after the currently active From 28554137cc052ec19bcc5df8c569da9e367af8fa Mon Sep 17 00:00:00 2001 From: kichikuou Date: Fri, 5 Jun 2026 10:52:56 +0900 Subject: [PATCH 4/5] Stoat/Chipmunk: Silence KeepPreviousView_Set/GetMode warnings KeepPreviousView and its mode are purely a rendering performance knob (incremental rendering optimization). Toggling the mode never affects rendering correctness, so leaving these unimplemented is harmless and warning about them is just noise. --- src/hll/ChipmunkSpriteEngine.c | 4 ++-- src/hll/StoatSpriteEngine.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hll/ChipmunkSpriteEngine.c b/src/hll/ChipmunkSpriteEngine.c index b44f96d..c656578 100644 --- a/src/hll/ChipmunkSpriteEngine.c +++ b/src/hll/ChipmunkSpriteEngine.c @@ -111,8 +111,8 @@ HLL_WARN_UNIMPLEMENTED(0, int, ChipmunkSpriteEngine, SP_SetBrightness, int sp_n HLL_WARN_UNIMPLEMENTED(0, int, ChipmunkSpriteEngine, SP_GetBrightness, int sp_no); static bool keep_previous_view = true; -HLL_WARN_UNIMPLEMENTED((keep_previous_view = on, true), bool, ChipmunkSpriteEngine, KeepPreviousView_SetMode, bool on); -HLL_WARN_UNIMPLEMENTED(keep_previous_view, bool, ChipmunkSpriteEngine, KeepPreviousView_GetMode); +HLL_QUIET_UNIMPLEMENTED((keep_previous_view = on, true), bool, ChipmunkSpriteEngine, KeepPreviousView_SetMode, bool on); +HLL_QUIET_UNIMPLEMENTED(keep_previous_view, bool, ChipmunkSpriteEngine, KeepPreviousView_GetMode); HLL_QUIET_UNIMPLEMENTED(false, bool, ChipmunkSpriteEngine, KeepPreviousView); static void ChipmunkSpriteEngine_Sleep(void) diff --git a/src/hll/StoatSpriteEngine.c b/src/hll/StoatSpriteEngine.c index cd9b1b9..11e92e6 100644 --- a/src/hll/StoatSpriteEngine.c +++ b/src/hll/StoatSpriteEngine.c @@ -188,8 +188,8 @@ float StoatSpriteEngine_FPS_Get(void) HLL_WARN_UNIMPLEMENTED( , void, StoatSpriteEngine, VIEW_SetOffsetPos, int x, int y); static bool keep_previous_view = true; -HLL_WARN_UNIMPLEMENTED((keep_previous_view = on, true), bool, StoatSpriteEngine, KeepPreviousView_SetMode, bool on); -HLL_WARN_UNIMPLEMENTED(keep_previous_view, bool, StoatSpriteEngine, KeepPreviousView_GetMode); +HLL_QUIET_UNIMPLEMENTED((keep_previous_view = on, true), bool, StoatSpriteEngine, KeepPreviousView_SetMode, bool on); +HLL_QUIET_UNIMPLEMENTED(keep_previous_view, bool, StoatSpriteEngine, KeepPreviousView_GetMode); /* * NOTE: The multisprite "type" alters the behavior of the multisprite functions. From 0e8bd36407a6886005dfc024a3442866dafe9a5e Mon Sep 17 00:00:00 2001 From: kichikuou Date: Mon, 8 Jun 2026 09:12:00 +0900 Subject: [PATCH 5/5] SACT2: Update draw plugins during TRANS_Update() This fixes the issue where the 3D plugin freezes during transitions. --- src/hll/SACT2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hll/SACT2.c b/src/hll/SACT2.c index 19ca021..94fb24d 100644 --- a/src/hll/SACT2.c +++ b/src/hll/SACT2.c @@ -1031,6 +1031,7 @@ int sact_TRANS_Begin(int type) int sact_TRANS_Update(float rate) { + sprite_call_plugins(); if (scene_is_dirty) scene_render(); return effect_update(rate);