Skip to content

Fire Emblem Fortune's Weave: 60 fps through the game's own target frame rate - #1015

Closed
b1tdr3amer24 wants to merge 1 commit into
seedds:mainfrom
b1tdr3amer24:fe-fortunes-weave-60fps
Closed

b1tdr3amer24 wants to merge 1 commit into
seedds:mainfrom
b1tdr3amer24:fe-fortunes-weave-60fps

Conversation

@b1tdr3amer24

@b1tdr3amer24 b1tdr3amer24 commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Fire Emblem Fortune's Weave (04007EF01F8AE000, v131072) runs its gameplay at 30 fps and asks for present interval 2, with the GPU mostly idle on a Mac.

That is the game's own choice, and the game is built to run at either rate:

  • The target: its graphics object, a global at [main+0x21e60d8], holds a target frame rate at +0x1dc. The game sets it to 60 for its title and menus (main+0x652960, main+0x66af3c) and to 30 when gameplay starts (main+0x7bf1c0).
  • The interval: the present (main+0x648da0, through gxc::NvnDevice's present at main+0x5d170) asks for interval min([+0x1e0], [+0x1d8] / min([+0x1d8], [+0x1dc])), where +0x1d8 is the display rate (60) and +0x1e0 the longest interval (5).
  • The step: about 40 readers step the game by 60 / target (main+0x770d5c, main+0x11e6310, main+0x13a6c94): 2.0 per frame at 30, 1.0 at 60.

The change:

  • hle_fpslock.c: a build-gated k_locks row (FPS_I32) writes fps_target into that field. The game then asks for interval 1 itself and its step follows. No game code is cut.
  • game.tsv: fps_target 60, which also gives the settings window its Frame rate choice.
  • Docs: the game note's Frame rate section, a frame-rate.md entry ("A target frame rate the game steps by"), the binding entry in docs_patches.py, and the regenerated game-patches.md and symptoms.md.

Checked

  • Tests: make -C dm/host/tests GAME=04007EF01F8AE000 t_fpslock passes. dm/tools/tests/test_docs.py and test_profile_guess.py pass. make -C dm/host GAME=04007EF01F8AE000 dmhost builds. lint_c.py tidy dm/host/hle_fpslock.c: 0 findings.
  • Games run: Fire Emblem Fortune's Weave v131072 (main db93b0125f4850601d61c3edde012f8e), Mac app from this branch's parent plus The NSP script builds a Switch 2 game from a dump with only an update, and surveys a dump with no profile #973 for the update-only dump.
    • The log shows main: Intelligent Systems target frame rate 30 → 60 fps (game profile fps_target), then the game sets present interval 2 → 1 (from main+0x5d1c8).
    • Title screen: frame time p50 16.6 ms. Battle map: p50 16.7 / p95 17.1 ms, about 2 of 300 frames dropped, in a window.
    • DM_FPS_TARGET=30 holds 30 (p50 33.3 ms).
    • Played for a while at 60, battles included; it looked smooth.
  • Not checked: game speed at 60 against 30 over the same route. A scripted comparison did not reach the map. The readers above divide 60 by the target, and the game runs its own menus at 60.
  • Behaviour at 30: DM_FPS_TARGET=30 writes 30 everywhere, including the title and menus that the game itself runs at 60.
  • Devices and OS: MacBook Pro M4 Pro, macOS 27.2 (26B123).
  • regress.py: not run. The row is gated on this game's main build ID, so no other game reaches it.

Docs

  • The finding is in its game note and its docs/reference/frame-rate.md entry, and python3 dm/tools/docs_symptoms.py was run (the symptom index)
  • No game files, keys or data extracted from a game in the change or the attachments

…me rate

The game's graphics object (a global at [main+0x21e60d8]) holds a target frame rate at
+0x1dc. The game sets it to 60 for its title and menus (main+0x652960, main+0x66af3c) and
to 30 when gameplay starts (main+0x7bf1c0); its present asks for interval
min([+0x1e0], [+0x1d8] / min([+0x1d8], [+0x1dc])), so 2 in gameplay. About 40 readers
step the game by 60 / target, so the game is built to run at either rate.

hle_fpslock.c gets a build-gated row (main db93b012..., FPS_I32) that writes the
profile's fps_target there, and the profile sets fps_target 60, which also gives the
settings window its Frame rate choice. The game then asks for interval 1 itself.

Checked on an M4 Pro: the log shows "target frame rate 30 → 60" followed by the game's
own interval 2 → 1; title frame time p50 16.6 ms, battle map p50 16.7 / p95 17.1 ms,
about 2 of 300 frames dropped, in a window. DM_FPS_TARGET=30 holds 30 (p50 33.3 ms).
t_fpslock, test_docs and test_profile_guess pass. Game speed at 60 against 30 over the
same route was not measured.

Generated with Claude Code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added host The Horizon host: SVCs, services, threads, memory (dm/host) tools Tools and scripts (dm/tools, scripts) docs Documentation game: 04007EF01F8AE000 Fire Emblem Fortune's Weave labels Nov 3, 2026
@seedds

seedds commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Thanks, @b1tdr3amer24 — landed on main as 0a05fb1, with you as co-author. Your diagnosis holds and is in the game note: the gxc object's target frame rate at +0x1dc, the stores of 60 and 30, the present's interval maths, and fps_target 60 in the profile.

What changed from the PR: no build-gated fixed-address row in hle_fpslock.c. Under #635 a frame-rate field should be bound as loosely as the change depends on, so the rewriter now finds it by what makes the write safe, in any build and with no engine name (dm/tools/fps_target.py): an int32 in an object a global points to, which at least 8 readers convert and divide into 60.0, and which the game sets to an immediate 30. Exactly one field must qualify or nothing is written. It passes the place to the host through main's fixups (FPS_TARGET_PTR, FPS_TARGET_FIELD), and the host writes the profile's fps_target there. On v131072 it finds 55 of the 59 readers; of 100 mains on our drives no other has a single reader.

Played on an M4 Pro: the host writes 30 → 60, the game asks for interval 1 itself, frame time p50 16.7 ms. The playtest also hit a crash at the title when the game showed the controller strap guide through SDK 22's StrapGuideArg overload; that is fixed separately in b591037. Game speed at 60 against 30 over the same route is still unmeasured (noted in the game note).

@seedds seedds closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 04007EF01F8AE000 Fire Emblem Fortune's Weave host The Horizon host: SVCs, services, threads, memory (dm/host) tools Tools and scripts (dm/tools, scripts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants