P20.1: FPS counter — env-gated dev overlay (M3TE_FPS), off by default
Add a small top-left FPS readout for gauging frame cost while tuning the organic animations. Gated behind the M3TE_FPS startup env pin (read like the other M3TE_* hooks); unset/=0 renders nothing, so default play and every committed golden stay byte-identical. - main.sx: g_fps_on (from M3TE_FPS) + g_fps_avg_dt, an EMA of delta_time (FPS_DT_SMOOTH=0.9) advanced only on the gated path; build_ui passes the smoothed FPS + flag into BoardView. delta_time is real wall-clock even when M3TE_ANIM_TIME pins the scene, so the counter stays live while frozen. - board_view.sx: BoardView.fps_on/fps fields + render_fps_overlay — "FPS n" in the top-left safe-area corner (clear of notch/Dynamic Island + the HUD), dark grape text over a bright halo. Drawn last, only when fps_on. - README.md: document M3TE_FPS (sim SIMCTL_CHILD_ + device devicectl env). - goldens/p20_fps.png: FPS overlay over the resting board (M3TE_FPS=1, M3TE_ANIM_TIME=0); FPS digits are dynamic, rest pinned == p6_idle_t0 region. Verified: ios-sim build + 22 logic tests green. Unset capture's board+HUD region is byte-identical to goldens/p6_idle_t0.png; the only ON-vs-OFF delta is the top-left FPS text box.
This commit is contained in:
28
main.sx
28
main.sx
@@ -45,6 +45,15 @@ g_viewport_w : f32 = 800.0;
|
||||
g_viewport_h : f32 = 600.0;
|
||||
g_safe_insets : EdgeInsets = .{};
|
||||
|
||||
// FPS dev overlay (P20.1). OFF unless the M3TE_FPS env pin is set, so default
|
||||
// play and every committed golden stay byte-identical. `g_fps_avg_dt` is an
|
||||
// exponential moving average of the per-frame delta, smoothed so the readout
|
||||
// doesn't jitter wildly; the displayed FPS is its reciprocal. Both are only
|
||||
// touched on the gated path, so the unset path is unchanged.
|
||||
FPS_DT_SMOOTH :f32: 0.9; // weight on the running average vs. this frame's delta
|
||||
g_fps_on : bool = false;
|
||||
g_fps_avg_dt : f32 = 0.016;
|
||||
|
||||
// iOS-only concrete handles kept alongside the boxed `g_plat` so the frame loop
|
||||
// can reach the CAMetalLayer pointer / pixel dims without going through the
|
||||
// protocol box.
|
||||
@@ -96,7 +105,8 @@ g_banner_prev_up : bool = false;
|
||||
// Rebuilt each frame inside the pipeline's arena; carries the current safe-area
|
||||
// insets so the grid stays inside the notch / home-indicator region.
|
||||
build_ui :: () -> View {
|
||||
BoardView.{ board = g_board, assets = g_assets, sel = g_sel, drag = g_drag, anim = g_anim, fx = g_fx, fxassets = g_fxassets, motion = g_motion, safe = g_safe_insets, seed = BOARD_SEED }
|
||||
fps : f32 = if g_fps_avg_dt > 0.0 then 1.0 / g_fps_avg_dt else 0.0;
|
||||
BoardView.{ board = g_board, assets = g_assets, sel = g_sel, drag = g_drag, anim = g_anim, fx = g_fx, fxassets = g_fxassets, motion = g_motion, safe = g_safe_insets, seed = BOARD_SEED, fps_on = g_fps_on, fps = fps }
|
||||
}
|
||||
|
||||
// Deterministic capture (P6.3). The idle loop is always-on, so a live screenshot
|
||||
@@ -200,6 +210,14 @@ frame :: () {
|
||||
g_viewport_h = fc.viewport_h;
|
||||
g_safe_insets = g_plat.safe_insets();
|
||||
|
||||
// FPS dev overlay (P20.1): advance the smoothed frame-time average ONLY when
|
||||
// the env pin enabled it, so an unset run never touches this and renders
|
||||
// byte-identically. delta_time is real wall-clock even when M3TE_ANIM_TIME
|
||||
// pins the animation clock, so the readout is live while the scene is frozen.
|
||||
if g_fps_on and g_delta_time > 0.0 {
|
||||
g_fps_avg_dt = g_fps_avg_dt * FPS_DT_SMOOTH + g_delta_time * (1.0 - FPS_DT_SMOOTH);
|
||||
}
|
||||
|
||||
if fc.viewport_w != g_pipeline.screen_width or fc.viewport_h != g_pipeline.screen_height {
|
||||
g_pipeline.resize(fc.viewport_w, fc.viewport_h);
|
||||
}
|
||||
@@ -380,6 +398,14 @@ main :: () -> void {
|
||||
}
|
||||
}
|
||||
|
||||
// FPS dev-overlay hook (P20.1): a non-zero M3TE_FPS turns on the corner FPS
|
||||
// readout. Default (unset / =0) leaves it off, so normal play and every
|
||||
// committed golden stay byte-identical. Purely a render overlay — no board /
|
||||
// score / move / animation state changes and it never gates input.
|
||||
if fp := read_env("M3TE_FPS") {
|
||||
if parse_s64(fp) != 0 { g_fps_on = true; }
|
||||
}
|
||||
|
||||
// Match-FX capture hook (P11.1). The bursts/popups spawn off a committed move,
|
||||
// which the sim can't script (no public touch injection), so M3TE_FX forces a
|
||||
// representative match at startup the same way a swipe would: it commits the
|
||||
|
||||
Reference in New Issue
Block a user