From f03ce8a868572617e9a8d9cf009f9b8306d480df Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 14 Apr 2026 12:22:08 -0400 Subject: [PATCH 01/11] Frame interp: Title logo presentation sync & minor sync refactor --- src/d/actor/d_a_title.cpp | 11 +++++++++++ src/dusk/frame_interpolation.cpp | 7 +------ src/m_Do/m_Do_graphic.cpp | 23 ++++++++++++----------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/d/actor/d_a_title.cpp b/src/d/actor/d_a_title.cpp index fc727a1248..6df52abc5b 100644 --- a/src/d/actor/d_a_title.cpp +++ b/src/d/actor/d_a_title.cpp @@ -18,6 +18,11 @@ #include "JSystem/J2DGraph/J2DTextBox.h" #include "m_Do/m_Do_graphic.h" +#ifdef TARGET_PC +#include "dusk/frame_interpolation.h" +#include "dusk/settings.h" +#endif + class daTit_HIO_c : public JORReflexible { public: daTit_HIO_c(); @@ -343,6 +348,12 @@ void daTitle_c::fastLogoDispInit() { field_0x604 = 0; mWaitTimer = 30; mProcID = 5; + +#ifdef TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + dusk::frame_interp::request_presentation_sync(); + } +#endif } void daTitle_c::fastLogoDisp() { diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index 8c45c034c6..ced68b6be5 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -65,7 +65,6 @@ bool g_recording = false; bool g_interpolating = false; bool g_sync_presentation = false; uint32_t g_presentation_counter = 0; -uint32_t g_presentation_sync_end = 0; float g_step = 0.0f; uint32_t g_pending_presentation_ui_ticks = 0; @@ -280,10 +279,7 @@ void begin_record() { return; } - if (g_sync_presentation && g_presentation_counter > g_presentation_sync_end) { - g_sync_presentation = false; - } - + g_sync_presentation = false; g_previous_recording = std::move(g_current_recording); g_current_recording = {}; g_current_path.clear(); @@ -320,7 +316,6 @@ void request_presentation_sync() { return; } g_sync_presentation = true; - g_presentation_sync_end = g_presentation_counter + 1; } bool presentation_sync_active() { diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 51733dad8c..f3672fdc8c 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -28,10 +28,6 @@ #include "d/d_menu_collect.h" #include "d/d_meter2_info.h" #include "d/d_s_play.h" -#include "dusk/endian.h" -#include "dusk/frame_interpolation.h" -#include "dusk/gx_helper.h" -#include "dusk/logging.h" #include "f_ap/f_ap_game.h" #include "f_op/f_op_actor_mng.h" #include "f_op/f_op_camera_mng.h" @@ -52,8 +48,12 @@ #if TARGET_PC #include "d/actor/d_a_horse.h" -#include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/dusk.h" +#include "dusk/endian.h" +#include "dusk/frame_interpolation.h" +#include "dusk/gx_helper.h" +#include "dusk/imgui/ImGuiConsole.hpp" +#include "dusk/logging.h" #endif class mDoGph_HIO_c : public JORReflexible { @@ -1989,12 +1989,13 @@ static void captureScreenPerspDrawInfo(JPADrawInfo& info) { static void drawItem3D() { ZoneScoped; #ifdef TARGET_PC - // Frame interpolation: Title screen needs 0.0f while everything else that runs through this is -100.0f. - // Running presentation faster than logic revealed the problem. Thanks, Nintendo. - if (fopAcM_SearchByName(fpcNm_TITLE_e) != nullptr) { - dMenu_Collect3D_c::setViewPortOffsetY(0.0f); - } else { - dMenu_Collect3D_c::setViewPortOffsetY(-100.0f); + if (dusk::getSettings().game.enableFrameInterpolation) { + // FRAME INTERP NOTE: Title screen needs 0.0f while everything else that runs through this is -100.0f. + if (fopAcM_SearchByName(fpcNm_TITLE_e) != nullptr) { + dMenu_Collect3D_c::setViewPortOffsetY(0.0f); + } else { + dMenu_Collect3D_c::setViewPortOffsetY(-100.0f); + } } #endif Mtx item_mtx; From 5c9af2ea64aab03f163db15187ae55d66389d77c Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 14 Apr 2026 11:33:14 -0600 Subject: [PATCH 02/11] Patch Twilight Hack --- include/d/d_save.h | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/include/d/d_save.h b/include/d/d_save.h index b0682e7ed6..0e9a4b37cd 100644 --- a/include/d/d_save.h +++ b/include/d/d_save.h @@ -487,19 +487,31 @@ public: } } char* getPlayerName() const { return const_cast(mPlayerName); } - void setPlayerName(const char* i_name) { strcpy(mPlayerName, i_name); } + void setPlayerName(const char* i_name) { +#if AVOID_UB + strncpy(mPlayerName, i_name, sizeof(mPlayerName) - 1); + mPlayerName[sizeof(mPlayerName) - 1] = '\0'; +#else + strcpy(mPlayerName, i_name); +#endif + } char* getHorseName() const { return const_cast(mHorseName); } - void setHorseName(const char* i_name) { strcpy(mHorseName, i_name); } + void setHorseName(const char* i_name) { +#if AVOID_UB + strncpy(mHorseName, i_name, sizeof(mHorseName) - 1); + mHorseName[sizeof(mHorseName) - 1] = '\0'; +#else + strcpy(mHorseName, i_name); +#endif + } u8 getClearCount() const { return mClearCount; } /* 0x00 */ BE(u64) unk0; /* 0x08 */ BE(s64) mTotalTime; /* 0x10 */ BE(u16) unk16; /* 0x12 */ BE(u16) mDeathCount; - /* 0x14 */ char mPlayerName[16]; - /* 0x24 */ u8 unk36; - /* 0x25 */ char mHorseName[16]; - /* 0x35 */ u8 unk53; + /* 0x14 */ char mPlayerName[17]; + /* 0x25 */ char mHorseName[17]; /* 0x36 */ u8 mClearCount; /* 0x37 */ u8 unk55[5]; }; // Size: 0x40 From 925df48e7229653c35cad781c2793f6059bfc438 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 14 Apr 2026 15:10:28 -0400 Subject: [PATCH 03/11] Widescreen minigame counters --- include/d/actor/d_a_balloon_2D.h | 3 +++ src/d/actor/d_a_balloon_2D.cpp | 14 ++++++++++++++ src/d/d_meter_HIO.cpp | 7 ++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/d/actor/d_a_balloon_2D.h b/include/d/actor/d_a_balloon_2D.h index f2dfa5e99f..193ecadbaa 100644 --- a/include/d/actor/d_a_balloon_2D.h +++ b/include/d/actor/d_a_balloon_2D.h @@ -44,6 +44,9 @@ public: int draw(); int execute(); void drawMeter(); + #if TARGET_PC + void updateOnWide(); + #endif void setComboCount(u8, u8); void setScoreCount(u32); void addScoreCount(cXyz*, u32, u8); diff --git a/src/d/actor/d_a_balloon_2D.cpp b/src/d/actor/d_a_balloon_2D.cpp index 957e7df281..6d9c66b464 100644 --- a/src/d/actor/d_a_balloon_2D.cpp +++ b/src/d/actor/d_a_balloon_2D.cpp @@ -268,6 +268,11 @@ int daBalloon2D_c::draw() { int daBalloon2D_c::execute() { setAllAlpha(); setComboAlpha(); + + #if TARGET_PC + updateOnWide(); + #endif + setHIO(false); return 1; } @@ -278,6 +283,15 @@ void daBalloon2D_c::drawMeter() { drawAddScore(); } +#if TARGET_PC +void daBalloon2D_c::updateOnWide() { + // Combo HUD + l_HOSTIO = {}; + + l_HOSTIO.m.m2DNumberCombo2PosX = mDoGph_gInf_c::ScaleHUDXRight(l_HOSTIO.m.m2DNumberCombo2PosX); + l_HOSTIO.m.mAdjustmentToggle = 1; +} +#endif void daBalloon2D_c::setComboCount(u8 size, u8 comboNum) { if (mComboNum != comboNum) { diff --git a/src/d/d_meter_HIO.cpp b/src/d/d_meter_HIO.cpp index 0566494863..2df28b2655 100644 --- a/src/d/d_meter_HIO.cpp +++ b/src/d/d_meter_HIO.cpp @@ -2289,7 +2289,8 @@ dMeter_drawHIO_c::dMeter_drawHIO_c() { #if WIDESCREEN_SUPPORT void dMeter_drawHIO_c::updateOnWide() { #if TARGET_PC - g_drawHIO = {}; // this might be a bad idea + // Main HUD + g_drawHIO = {}; g_drawHIO.mMainHUDButtonsPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMainHUDButtonsPosX); g_drawHIO.mRingHUDButtonsPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRingHUDButtonsPosX); @@ -2301,6 +2302,10 @@ void dMeter_drawHIO_c::updateOnWide() { g_drawHIO.mButtonCrossONPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mButtonCrossONPosX); g_drawHIO.mLifeGaugePosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mLifeGaugePosX); g_drawHIO.mLanternMeterPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mLanternMeterPosX); + + // River Canoe Minigame + g_drawHIO.mMiniGame.mCounterPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mCounterPosX[1]); + g_drawHIO.mMiniGame.mIconPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mIconPosX[1]); #endif } From 08855a5ad8cc616e071fed84dff9c29f44f7a7af Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 14 Apr 2026 15:27:29 -0400 Subject: [PATCH 04/11] Frame interp: Howl UI scrolling dot --- src/d/d_msg_scrn_howl.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/d/d_msg_scrn_howl.cpp b/src/d/d_msg_scrn_howl.cpp index 965bd25075..19150520cf 100644 --- a/src/d/d_msg_scrn_howl.cpp +++ b/src/d/d_msg_scrn_howl.cpp @@ -19,6 +19,10 @@ #include "JSystem/J2DGraph/J2DScreen.h" #include "Z2AudioLib/Z2WolfHowlMgr.h" +#if TARGET_PC +#include "dusk/settings.h" +#endif + // POSIX already defines a macro with this name, but we know that this specific name is // used in TP based on assertion messages. This redefinition is scoped to this TU which // is unlikely to ever actually need the POSIX define, so we can just redefine it. @@ -495,7 +499,12 @@ void dMsgScrnHowl_c::drawWave() { if (mPlotTime != field_0x212c) { field_0x212c = mPlotTime; } else { - bVar5 = false; +#if TARGET_PC + if (!dusk::getSettings().game.enableFrameInterpolation) +#endif + { + bVar5 = false; + } } } if (bVar5) { From 0f32b444a1dbeb7affde30d798591fbf8195d37e Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 14 Apr 2026 15:43:52 -0400 Subject: [PATCH 05/11] Disable Main HUD Feature --- include/dusk/settings.h | 1 + src/d/d_meter2.cpp | 7 +++++++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 6 ++++++ src/dusk/settings.cpp | 2 ++ 4 files changed, 16 insertions(+) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 7381ccafe5..67dce3c067 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -66,6 +66,7 @@ struct UserSettings { // Preferences ConfigVar enableMirrorMode; ConfigVar invertCameraXAxis; + ConfigVar disableMainHUD; // Graphics ConfigVar bloomMode; diff --git a/src/d/d_meter2.cpp b/src/d/d_meter2.cpp index aa5496dbba..5bd321e7a8 100644 --- a/src/d/d_meter2.cpp +++ b/src/d/d_meter2.cpp @@ -423,6 +423,13 @@ void dMeter2_c::setLifeZero() { void dMeter2_c::checkStatus() { mStatus = 0; + + #if TARGET_PC + if (dusk::getSettings().game.disableMainHUD) { + mStatus |= 0xF0000000; + } + #endif + field_0x12c = field_0x128; field_0x128 = daPy_py_c::checkNowWolf(); diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 8c65c97912..b287399341 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -83,6 +83,12 @@ namespace dusk { config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); + config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Disables the main HUD of the game.\n" + "Useful for recording or a more immersive experience!"); + } + ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 0c2f02c129..99ff183e79 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -40,6 +40,7 @@ UserSettings g_userSettings = { // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, .invertCameraXAxis {"game.invertCameraXAxis", false}, + .disableMainHUD {"game.disableMainHUD", false}, // Graphics .bloomMode {"game.bloomMode", BloomMode::Classic}, @@ -117,6 +118,7 @@ void registerSettings() { Register(g_userSettings.game.sunsSong); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); + Register(g_userSettings.game.disableMainHUD); Register(g_userSettings.game.bloomMode); Register(g_userSettings.game.bloomMultiplier); Register(g_userSettings.game.enableWaterRefraction); From 34de4ea3925b028537209efec276ee83b6458329 Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 14 Apr 2026 16:11:15 -0400 Subject: [PATCH 06/11] Frame interp: Stable keying for simple shadows --- include/dusk/frame_interpolation.h | 2 + src/d/d_drawlist.cpp | 31 +++++++++------ src/dusk/frame_interpolation.cpp | 64 ++++++++++++++++++++++++++---- 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 423ac9af37..3aadfe8520 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -34,6 +34,7 @@ void open_child(const void* key, int32_t id); void close_child(); void record_camera(::camera_process_class* cam, int camera_id); void record_final_mtx_raw(const Mtx* dest, const Mtx src); +void record_final_mtx_raw_tagged(const Mtx* dest, const Mtx src, uint64_t stable_tag); bool lookup_replacement(const void* source, Mtx out); bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); @@ -41,6 +42,7 @@ bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye); bool build_star_view(Mtx o_view, Mtx o_cam_billboard_base, cXyz* o_anchor_eye, float* o_fovy); +uint64_t alloc_simple_shadow_pair_base(); } // namespace frame_interp } // namespace dusk #endif diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 24798f4482..e903e39ce4 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1,28 +1,29 @@ #include "d/dolzel.h" // IWYU pragma: keep -#include #include "JSystem/J2DGraph/J2DAnimation.h" #include "JSystem/J2DGraph/J2DGrafContext.h" #include "JSystem/J2DGraph/J2DScreen.h" #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" -#include "JSystem/JKernel/JKRHeap.h" #include "SSystem/SComponent/c_bg_s_shdw_draw.h" #include "SSystem/SComponent/c_math.h" #include "d/d_com_inf_game.h" #include "d/d_drawlist.h" - -#include - -#include "absl/container/flat_hash_map.h" -#include "client/TracyScoped.hpp" #include "d/d_s_play.h" -#include "dusk/frame_interpolation.h" -#include "dusk/gx_helper.h" -#include "dusk/logging.h" #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_lib.h" #include "m_Do/m_Do_mtx.h" +#if TARGET_PC +#include +#include +#include "JSystem/JKernel/JKRHeap.h" +#include "absl/container/flat_hash_map.h" +#include "client/TracyScoped.hpp" +#include "dusk/frame_interpolation.h" +#include "dusk/gx_helper.h" +#include "dusk/logging.h" +#endif + class dDlst_2Dm_c { public: virtual void draw(); @@ -1432,8 +1433,14 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa mDoMtx_stack_c::scaleM(param_2, 1.0f, param_2 * param_5); cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), mMtx); #ifdef TARGET_PC - dusk::frame_interp::record_final_mtx_raw(&mVolumeMtx, mVolumeMtx); - dusk::frame_interp::record_final_mtx_raw(&mMtx, mMtx); + const uint64_t shadow_tag_base = dusk::frame_interp::alloc_simple_shadow_pair_base(); + if (shadow_tag_base != 0) { + dusk::frame_interp::record_final_mtx_raw_tagged(&mVolumeMtx, mVolumeMtx, shadow_tag_base); + dusk::frame_interp::record_final_mtx_raw_tagged(&mMtx, mMtx, shadow_tag_base + 1u); + } else { + dusk::frame_interp::record_final_mtx_raw(&mVolumeMtx, mVolumeMtx); + dusk::frame_interp::record_final_mtx_raw(&mMtx, mMtx); + } #endif mpTexObj = param_6; } diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index ced68b6be5..0fa7d036e0 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -28,6 +28,7 @@ struct Data { size_t child_index = 0; Mtx matrix{}; const Mtx* dest = nullptr; + uint64_t stable_tag = 0; }; struct Path; @@ -46,6 +47,8 @@ struct Path { std::vector children; std::vector ops; std::vector> items; + Label draw_scope{}; + uint32_t simple_shadow_pair_seq = 0; }; struct Recording { @@ -57,6 +60,7 @@ struct MatrixValue { }; using FinalMtxLookup = std::unordered_map; +using FinalMtxLookupTagged = std::unordered_map; bool s_initialized = false; @@ -142,8 +146,9 @@ const OpBucket* find_op_bucket(const Path& path, Op op) { return &*it; } -void build_final_mtx_lookup(const Path& path, FinalMtxLookup& lookup) { - lookup.clear(); +void build_final_mtx_lookups(const Path& path, FinalMtxLookup& dest_lookup, FinalMtxLookupTagged& tag_lookup) { + dest_lookup.clear(); + tag_lookup.clear(); const OpBucket* bucket = find_op_bucket(path, Op::FinalMtx); if (bucket == nullptr) { @@ -151,10 +156,12 @@ void build_final_mtx_lookup(const Path& path, FinalMtxLookup& lookup) { } for (const Data& data : bucket->values) { - if (data.dest == nullptr) { - continue; + if (data.dest != nullptr) { + dest_lookup[data.dest] = &data; + } + if (data.stable_tag != 0) { + tag_lookup[data.stable_tag] = &data; } - lookup[data.dest] = &data; } } @@ -201,7 +208,8 @@ void store_replacement(const Data& old_data, const Data& new_data, float step) { void interpolate_branch(const Path& old_path, const Path& new_path, float step) { FinalMtxLookup old_final_mtx_lookup; - build_final_mtx_lookup(old_path, old_final_mtx_lookup); + FinalMtxLookupTagged old_final_mtx_lookup_tagged; + build_final_mtx_lookups(old_path, old_final_mtx_lookup, old_final_mtx_lookup_tagged); for (const auto& item : new_path.items) { const Op op = item.first; @@ -230,7 +238,17 @@ void interpolate_branch(const Path& old_path, const Path& new_path, float step) } const Data* indexed_old_data = find_matching_data(old_path, op, index); - const Data* old_data = op == Op::FinalMtx ? find_matching_final_mtx(old_final_mtx_lookup, *new_data) : indexed_old_data; + const Data* old_data = nullptr; + if (op == Op::FinalMtx) { + if (new_data->stable_tag != 0) { + const auto it = old_final_mtx_lookup_tagged.find(new_data->stable_tag); + old_data = it != old_final_mtx_lookup_tagged.end() ? it->second : nullptr; + } else { + old_data = find_matching_final_mtx(old_final_mtx_lookup, *new_data); + } + } else { + old_data = indexed_old_data; + } if (op == Op::FinalMtx) { store_replacement(old_data != nullptr ? *old_data : *new_data, *new_data, step); } @@ -370,7 +388,9 @@ void open_child(const void* key, int32_t id) { data.child_label = label; data.child_index = siblings.size(); siblings.emplace_back(std::make_unique()); - g_current_path.push_back(siblings.back().get()); + Path* const child = siblings.back().get(); + child->draw_scope = label; + g_current_path.push_back(child); } void close_child() { @@ -388,6 +408,18 @@ void record_final_mtx_raw(const Mtx* dest, const Mtx src) { Data& data = append_op(Op::FinalMtx); data.dest = dest; + data.stable_tag = 0; + copy_matrix(src, data.matrix); +} + +void record_final_mtx_raw_tagged(const Mtx* dest, const Mtx src, uint64_t stable_tag) { + if (!s_initialized || !g_recording || dest == nullptr) { + return; + } + + Data& data = append_op(Op::FinalMtx); + data.dest = dest; + data.stable_tag = stable_tag; copy_matrix(src, data.matrix); } @@ -501,4 +533,20 @@ bool build_star_view(Mtx o_view, Mtx o_cam_billboard_base, cXyz* o_anchor_eye, f *o_fovy = s_star_prev.fovy + (s_star_curr.fovy - s_star_prev.fovy) * step; return true; } + +uint64_t alloc_simple_shadow_pair_base() { + if (!s_initialized || !g_recording || g_current_path.size() <= 1) { + return 0; + } + + Path* const scope = g_current_path.back(); + const uint64_t h = static_cast(reinterpret_cast(scope->draw_scope.key)); + const uint32_t lo = scope->simple_shadow_pair_seq; + scope->simple_shadow_pair_seq += 2; + uint64_t tag0 = (h << 17) ^ (static_cast(lo) << 1u); + if (tag0 == 0) { + tag0 = 2; + } + return tag0; +} } // namespace dusk::frame_interp From 1bed728d08c1c851811986e5222db952379dfde5 Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 14 Apr 2026 17:17:29 -0400 Subject: [PATCH 07/11] Frame interp: Howl 2 Electric Boogaloo --- include/d/d_msg_scrn_howl.h | 4 ++++ src/d/d_msg_scrn_howl.cpp | 28 +++++++++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/include/d/d_msg_scrn_howl.h b/include/d/d_msg_scrn_howl.h index bf28682d42..5db5db0232 100644 --- a/include/d/d_msg_scrn_howl.h +++ b/include/d/d_msg_scrn_howl.h @@ -110,6 +110,10 @@ struct dMsgScrnHowl_c : public dMsgScrnBase_c { /* 0x27A0 */ f32 field_0x27a0; /* 0x27A4 */ f32 field_0x27a4; /* 0x27A8 */ f32 field_0x27a8; + +#if TARGET_PC + u8 showCursor; +#endif }; #endif /* MSG_SCRN_D_MSG_SCRN_HOWL_H */ diff --git a/src/d/d_msg_scrn_howl.cpp b/src/d/d_msg_scrn_howl.cpp index 19150520cf..03729377a5 100644 --- a/src/d/d_msg_scrn_howl.cpp +++ b/src/d/d_msg_scrn_howl.cpp @@ -311,6 +311,17 @@ void dMsgScrnHowl_c::exec() { mpButtonIcon[1]->setAlphaRate(field_0x1994 * alphaRate); mpButtonText[0]->setAlphaRate(field_0x1998 * alphaRate); mpButtonText[1]->setAlphaRate(field_0x1998 * alphaRate); + +#if TARGET_PC + showCursor = true; + if (field_0x2798 == 0) { + if (mPlotTime != field_0x212c) { + field_0x212c = mPlotTime; + } else { + showCursor = false; + } + } +#endif } void dMsgScrnHowl_c::drawSelf() { @@ -494,20 +505,23 @@ void dMsgScrnHowl_c::drawWave() { grafContext->setScissor(); #endif +#if !TARGET_PC bool bVar5 = true; if (field_0x2798 == 0) { if (mPlotTime != field_0x212c) { field_0x212c = mPlotTime; } else { -#if TARGET_PC - if (!dusk::getSettings().game.enableFrameInterpolation) -#endif - { - bVar5 = false; - } + bVar5 = false; } } - if (bVar5) { +#endif + +#if TARGET_PC + if (showCursor) +#else + if (bVar5) +#endif + { for (int iVar10 = 0; iVar10 < field_0x2128 - 1; iVar10++) { f32 local_54 = local_e0; f32 local_c8 = field_0x180[sVar14]; From bc4145be90395021c7a1582734c71f940329f46a Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 14 Apr 2026 17:51:00 -0400 Subject: [PATCH 08/11] Frame interp: GOAT IN! --- src/d/d_timer.cpp | 38 ++++++++++++++++++++++++++++---------- src/m_Do/m_Do_graphic.cpp | 12 +++++------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/d/d_timer.cpp b/src/d/d_timer.cpp index 41826c3029..552874b552 100644 --- a/src/d/d_timer.cpp +++ b/src/d/d_timer.cpp @@ -23,6 +23,10 @@ #include "m_Do/m_Do_lib.h" #include +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif + static int dTimer_createStart2D(s32 param_0, u16 param_1); int dTimer_c::_create() { @@ -1336,29 +1340,44 @@ void dDlst_TimerScrnDraw_c::draw() { f32 temp = (f32)g_drawHIO.mMiniGame.mGetInTextAlphaFrames + ((f32)g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f); +#if TARGET_PC + const u32 pending_ui_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); +#else + const u32 pending_ui_ticks = 1u; +#endif + for (int i = 0; i < 51; i++) { - if (m_getin_info[i].bck_frame > 0.0f && m_getin_info[i].bck_frame < temp) { - f32 var_f29 = 1.0f; + for (u32 tick = 0; tick < pending_ui_ticks; tick++) { + if (!(m_getin_info[i].bck_frame > 0.0f && m_getin_info[i].bck_frame < temp)) { + break; + } if (m_getin_info[i].bck_frame < 60.0f) { m_getin_info[i].bck_frame += g_drawHIO.mMiniGame.mGetInTextAnimSpeed; if (m_getin_info[i].bck_frame > 60.0f) { m_getin_info[i].bck_frame = 60.0f; } - - playBckAnimation(m_getin_info[i].bck_frame); - } else if (m_getin_info[i].bck_frame < g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f) - { + } else if (m_getin_info[i].bck_frame < g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f) { m_getin_info[i].bck_frame++; - playBckAnimation(60.0f); } else if (m_getin_info[i].bck_frame < temp) { m_getin_info[i].bck_frame++; - playBckAnimation(60.0f); + } + } + if (m_getin_info[i].bck_frame > 0.0f && m_getin_info[i].bck_frame < temp) { + f32 var_f29 = 1.0f; + if (m_getin_info[i].bck_frame >= g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f && + m_getin_info[i].bck_frame < temp) { var_f29 = acc(g_drawHIO.mMiniGame.mGetInTextAlphaFrames, temp - m_getin_info[i].bck_frame, 0); } + if (m_getin_info[i].bck_frame < 60.0f) { + playBckAnimation(m_getin_info[i].bck_frame); + } else { + playBckAnimation(60.0f); + } + mpGetInParent->setAlphaRate(var_f29); if (g_drawHIO.mMiniGame.mGetInTextLocation == 1) { @@ -1388,8 +1407,7 @@ void dDlst_TimerScrnDraw_c::draw() { m_getin_info[i].pikari_frame = 18.0f - g_drawHIO.mMiniGame.mGetInPikariAnimSpeed; } - } else if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mStartPikariAppearFrames) - { + } else if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mStartPikariAppearFrames) { m_getin_info[i].pikari_frame = 18.0f - g_drawHIO.mMiniGame.mStartPikariAnimSpeed; } diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index f3672fdc8c..92c8707782 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -2034,12 +2034,11 @@ int mDoGph_Painter() { #endif #ifdef TARGET_PC - for (u32 i = 0; i < pending_ui_ticks; ++i) { + for (u32 i = 0; i < pending_ui_ticks; ++i) #endif + { dComIfGp_particle_calcMenu(); -#ifdef TARGET_PC } -#endif JFWDisplay::getManager()->setFader(mDoGph_gInf_c::getFader()); mDoGph_gInf_c::setClearColor(mDoGph_gInf_c::getBackColor()); @@ -2599,13 +2598,12 @@ int mDoGph_Painter() { #endif GXSetClipMode(GX_CLIP_ENABLE); -#ifdef TARGET_PC - for (u32 i = 0; i < pending_ui_ticks; ++i) { +#if TARGET_PC + for (u32 i = 0; i < pending_ui_ticks; ++i) #endif + { dDlst_list_c::calcWipe(); -#ifdef TARGET_PC } -#endif j3dSys.reinitGX(); ortho.setOrtho(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), From 60ac9c28ffd5ab20821e2e42e2a8b6a0c20b112b Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 14 Apr 2026 15:04:24 -0600 Subject: [PATCH 09/11] Update CI for self-hosted runners --- .github/workflows/build.yml | 67 +++++++++++++++++++------------------ CMakeLists.txt | 10 ++++-- CMakePresets.json | 46 +++++++++++++++---------- ci/build-appimage.sh | 2 +- 4 files changed, 71 insertions(+), 54 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1412aec639..0607841922 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,8 +8,9 @@ on: pull_request: env: - SCCACHE_GHA_ENABLED: "true" + # SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} jobs: build-linux: @@ -21,13 +22,13 @@ jobs: matrix: include: - name: GCC x86_64 - runner: ubuntu-latest + runner: [self-hosted, Linux] preset: gcc artifact_arch: x86_64 - - name: GCC aarch64 - runner: ubuntu-24.04-arm - preset: gcc - artifact_arch: aarch64 + # - name: GCC aarch64 + # runner: ubuntu-24.04-arm + # preset: gcc + # artifact_arch: aarch64 # - name: Clang x86_64 # runner: ubuntu-latest # preset: clang @@ -40,17 +41,22 @@ jobs: submodules: recursive - name: Install dependencies + if: 'false' # disabled for self-hosted run: | sudo apt-get update sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \ zlib1g-dev libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev \ libpulse-dev libudev-dev libpng-dev libncurses5-dev libx11-xcb-dev libfreetype-dev \ libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev libssl-dev \ - libxss-dev libfuse2 + libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev - name: Setup sccache + if: 'false' # disabled for self-hosted uses: mozilla-actions/sccache-action@v0.0.9 + - name: Print sccache stats + run: sccache --show-stats + - name: Configure CMake run: cmake --preset x-linux-ci-${{matrix.preset}} @@ -68,9 +74,9 @@ jobs: build/install/Dusk-*.AppImage build/install/debug.tar.* - build-apple: name: Build Apple (${{matrix.name}}) + if: 'false' # TODO enable when CI is free runs-on: macos-latest strategy: fail-fast: false @@ -80,14 +86,14 @@ jobs: platform: macos preset: x-macos-ci artifact_name: macos-appleclang-universal - # - name: AppleClang iOS arm64 # TODO enable when CI is free - # platform: ios - # preset: x-ios-ci - # artifact_name: ios-appleclang-arm64 - # - name: AppleClang tvOS arm64 # TODO enable when CI is free - # platform: tvos - # preset: x-tvos-ci - # artifact_name: tvos-appleclang-arm64 + - name: AppleClang iOS arm64 + platform: ios + preset: x-ios-ci + artifact_name: ios-appleclang-arm64 + - name: AppleClang tvOS arm64 + platform: tvos + preset: x-tvos-ci + artifact_name: tvos-appleclang-arm64 steps: - uses: actions/checkout@v6 @@ -139,26 +145,22 @@ jobs: name: Build Windows (${{matrix.name}}) runs-on: ${{matrix.runner}} - env: - BUILD_DIR: C:\build - SCCACHE_DIR: C:\sccache - strategy: fail-fast: false matrix: include: - name: MSVC x86_64 - runner: windows-latest + runner: [self-hosted, Windows] preset: msvc msvc_arch: amd64 vcpkg_arch: x64 artifact_arch: x86_64 - - name: MSVC arm64 - runner: windows-11-arm - preset: arm64-msvc - msvc_arch: arm64 - vcpkg_arch: arm64 - artifact_arch: arm64 + # - name: MSVC arm64 + # runner: windows-11-arm + # preset: arm64-msvc + # msvc_arch: arm64 + # vcpkg_arch: arm64 + # artifact_arch: arm64 # - name: Clang x86_64 # runner: windows-latest # preset: clang @@ -185,11 +187,10 @@ jobs: uses: mozilla-actions/sccache-action@v0.0.9 - name: Install dependencies + if: 'false' # disabled for self-hosted run: | choco install ninja - vcpkg install zlib:${{matrix.vcpkg_arch}}-windows-static bzip2:${{matrix.vcpkg_arch}}-windows-static ` - zstd:${{matrix.vcpkg_arch}}-windows-static liblzma:${{matrix.vcpkg_arch}}-windows-static ` - freetype:${{matrix.vcpkg_arch}}-windows-static + vcpkg install freetype:${{matrix.vcpkg_arch}}-windows-static zstd:${{matrix.vcpkg_arch}}-windows-static - name: Configure CMake run: cmake --preset x-windows-ci-${{matrix.preset}} @@ -202,5 +203,7 @@ jobs: with: name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}} path: | - ${{env.BUILD_DIR}}/install/*.exe - ${{env.BUILD_DIR}}/install/debug.7z + build/install/*.exe + build/install/*.dll + build/install/res/ + build/install/debug.7z diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ca90b3e5d..25d5d386f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,7 +113,7 @@ set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN") set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment") if (DUSK_MOVIE_SUPPORT) - find_package(libjpeg-turbo QUIET) + find_package(libjpeg-turbo 3.0 CONFIG QUIET) if (libjpeg-turbo_FOUND) message(STATUS "dusk: Using system libjpeg-turbo") else () @@ -297,8 +297,12 @@ endif () # Edit & Continue if (MSVC) - add_compile_options("/ZI") - add_link_options("/INCREMENTAL") + if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL Debug) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue") + endif () + if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue") + add_link_options("/INCREMENTAL") + endif () endif () if(ANDROID) diff --git a/CMakePresets.json b/CMakePresets.json index c3c7463be3..44e75973b9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -22,6 +22,20 @@ "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded" } }, + { + "name": "ci", + "hidden": true, + "cacheVariables": { + "CMAKE_C_COMPILER_LAUNCHER": "sccache", + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", + "DUSK_ENABLE_SENTRY_NATIVE": { + "type": "BOOL", + "value": true + }, + "DUSK_SENTRY_DSN": "$env{SENTRY_DSN}", + "DUSK_SENTRY_ENVIRONMENT": "production" + } + }, { "name": "linux-default", "displayName": "Linux (default)", @@ -343,12 +357,9 @@ "name": "x-linux-ci", "hidden": true, "inherits": [ - "relwithdebinfo" - ], - "cacheVariables": { - "CMAKE_C_COMPILER_LAUNCHER": "sccache", - "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" - } + "relwithdebinfo", + "ci" + ] }, { "name": "x-linux-ci-gcc", @@ -367,12 +378,9 @@ { "name": "x-macos-ci", "inherits": [ - "macos-default-relwithdebinfo" - ], - "cacheVariables": { - "CMAKE_C_COMPILER_LAUNCHER": "sccache", - "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" - } + "macos-default-relwithdebinfo", + "ci" + ] }, { "name": "x-ios-ci", @@ -398,14 +406,16 @@ "name": "x-windows-ci", "hidden": true, "inherits": [ - "relwithdebinfo" + "relwithdebinfo", + "ci" ], - "binaryDir": "$env{BUILD_DIR}", "cacheVariables": { - "CMAKE_INSTALL_PREFIX": "$env{BUILD_DIR}/install", - "CMAKE_C_COMPILER_LAUNCHER": "sccache", - "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", - "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "Embedded" + "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "Embedded", + "CMAKE_TOOLCHAIN_FILE": { + "type": "FILEPATH", + "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + }, + "VCPKG_TARGET_TRIPLET": "x64-windows" } }, { diff --git a/ci/build-appimage.sh b/ci/build-appimage.sh index 82c4cffaeb..3ce3021b97 100755 --- a/ci/build-appimage.sh +++ b/ci/build-appimage.sh @@ -9,7 +9,7 @@ chmod +x linuxdeploy-$(uname -m).AppImage # Build AppImage cd "$GITHUB_WORKSPACE" mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}} -cp build/install/!(*.*) build/appdir/usr/bin +cp -r build/install/!(*.*) build/appdir/usr/bin cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor cp platforms/freedesktop/dusk.desktop build/appdir/usr/share/applications From 75032296f412a735fbe09344c4dd6d963c93052d Mon Sep 17 00:00:00 2001 From: Phillip Stephens Date: Tue, 14 Apr 2026 16:49:08 -0700 Subject: [PATCH 10/11] Fix missing libusb in Linux AppImage (#384) --- ci/build-appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/build-appimage.sh b/ci/build-appimage.sh index 3ce3021b97..078d6cabf0 100755 --- a/ci/build-appimage.sh +++ b/ci/build-appimage.sh @@ -15,4 +15,4 @@ cp platforms/freedesktop/dusk.desktop build/appdir/usr/share/applications cd build/install VERSION="$DUSK_VERSION" NO_STRIP=1 "$RUNNER_WORKSPACE"/linuxdeploy-$(uname -m).AppImage \ - --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage + -l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage From d97fbb778bff34f6590b5f45ff6bc0b8abc8483f Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 14 Apr 2026 21:58:44 -0400 Subject: [PATCH 11/11] Frame interp: Dreamworks' Turbo (2013) --- libs/JSystem/src/JFramework/JFWDisplay.cpp | 2 +- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 5 ++--- src/f_pc/f_pc_manager.cpp | 7 +++---- src/m_Do/m_Do_main.cpp | 14 +++++--------- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index ce4c870064..38dbc7eab1 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -393,7 +393,7 @@ static void waitPrecise(Limiter& limiter, Uint64 targetNs) { static void waitForTick(u32 p1, u16 p2) { #if TARGET_PC - if (dusk::getSettings().game.enableFrameInterpolation) { + if (dusk::getSettings().game.enableFrameInterpolation && !dusk::getTransientSettings().skipFrameRateLimit) { return; } if (dusk::getTransientSettings().skipFrameRateLimit) { diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 8c65c97912..29f2045a72 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -185,10 +185,9 @@ namespace dusk { } if (ImGui::BeginMenu("Tools")) { - config::ImGuiCheckbox("Enable Turbo Key", getSettings().game.enableTurboKeybind); + config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Holding TAB will speed up the game.\n" - "This will not work with the \"Unlock Framerate\" enhancement."); + ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x."); } ImGui::EndMenu(); diff --git a/src/f_pc/f_pc_manager.cpp b/src/f_pc/f_pc_manager.cpp index 03319db3d8..4d424ad75e 100644 --- a/src/f_pc/f_pc_manager.cpp +++ b/src/f_pc/f_pc_manager.cpp @@ -64,13 +64,12 @@ void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_p } #ifdef TARGET_PC - // Frame interpolation: call moved to m_Do_main - if (!dusk::getSettings().game.enableFrameInterpolation) { + // FRAME INTERP NOTE: Called in m_Do_main when interp is enabled + if (!dusk::getSettings().game.enableFrameInterpolation || dusk::getTransientSettings().skipFrameRateLimit) #endif + { cAPIGph_Painter(); -#ifdef TARGET_PC } -#endif if (!dPa_control_c::isStatus(1)) { fpcDt_Handler(); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index fbe0bce3cc..3568e044e1 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -202,9 +202,8 @@ void main01(void) { if (preLaunchUIWindowSize.width != 0) mDoGph_gInf_c::setWindowSize(preLaunchUIWindowSize); - using clock = std::chrono::steady_clock; constexpr double kSimStepSeconds = 1.0 / 30.0; - auto previous_time = clock::now(); + auto previous_time = std::chrono::steady_clock::now(); double accumulator = kSimStepSeconds; do { @@ -229,7 +228,7 @@ void main01(void) { eventsDone:; - auto current_time = clock::now(); + auto current_time = std::chrono::steady_clock::now(); double frame_seconds = std::chrono::duration(current_time - previous_time).count(); previous_time = current_time; accumulator += frame_seconds; @@ -242,9 +241,8 @@ void main01(void) { continue; } - if (dusk::getSettings().game.enableFrameInterpolation) { + if (dusk::getSettings().game.enableFrameInterpolation && !dusk::getTransientSettings().skipFrameRateLimit) { dusk::frame_interp::notify_presentation_frame(); - while (accumulator >= kSimStepSeconds) { mDoCPd_c::read(); if (dusk::getSettings().game.enableGyroAim) { @@ -254,13 +252,11 @@ void main01(void) { mDoAud_Execute(); accumulator -= kSimStepSeconds; } - - float interp_alpha = static_cast(accumulator / kSimStepSeconds); - dusk::frame_interp::interpolate(interp_alpha); + dusk::frame_interp::interpolate(static_cast(accumulator / kSimStepSeconds)); cAPIGph_Painter(); } else { accumulator = 0.0; - + // Game Inputs mDoCPd_c::read(); if (dusk::getSettings().game.enableGyroAim) {