Gauntlet #340: shared HUD overlay + entity-swarm motion
Instrument-panel HUD: trial banner, 60-bar frame-time sparkline with 16.7 ms hairline, and FPS / p95 / draw-calls / load readout (budget-colored). Give swarm entities a nonzero move_speed so they spread visually instead of piling on target. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9f371bccd3
commit
b20903b9ed
2 changed files with 190 additions and 2 deletions
188
tests/benchmarks/gauntlet/hud.py
Normal file
188
tests/benchmarks/gauntlet/hud.py
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
"""Shared HUD overlay for The Gauntlet -- identical on every trial scene.
|
||||
|
||||
Top strip: trial name + description (left), a 60-bar frame-time sparkline
|
||||
(center), and an FPS / p95 / draw-calls / load readout (right). Bottom strip:
|
||||
key legend. Refresh on a 100 ms Timer; frame_time is milliseconds (see DESIGN.md).
|
||||
"""
|
||||
import mcrfpy
|
||||
|
||||
# -- palette (r, g, b) ----------------------------------------------------
|
||||
BG = (13, 15, 20)
|
||||
PANEL = (22, 26, 34)
|
||||
OUTLINE = (42, 49, 64)
|
||||
TEXT = (232, 234, 240)
|
||||
DIM = (138, 147, 166)
|
||||
OK = (56, 217, 150)
|
||||
WARN = (245, 184, 61)
|
||||
FAIL = (229, 72, 77)
|
||||
|
||||
BUDGET_MS = 16.67
|
||||
WARN_MS = 33.0
|
||||
|
||||
SCREEN_W = 1024
|
||||
SCREEN_H = 768
|
||||
PANEL_H = 100
|
||||
|
||||
# sparkline geometry
|
||||
SPARK_N = 60
|
||||
BAR_W = 4
|
||||
BAR_GAP = 2
|
||||
SPARK_X = 372
|
||||
SPARK_TOP = 18
|
||||
SPARK_H = 52
|
||||
SPARK_BASE = SPARK_TOP + SPARK_H # y of bar bottoms
|
||||
FT_CLAMP = 50.0
|
||||
|
||||
LEGEND = ("[SPACE] pause [LEFT/RIGHT] trial [-/+] load [A] auto-ramp "
|
||||
"[R] run gauntlet [S] shot [ESC] menu/quit")
|
||||
|
||||
|
||||
def color(rgb, a=255):
|
||||
return mcrfpy.Color(rgb[0], rgb[1], rgb[2], a)
|
||||
|
||||
|
||||
def budget_color(ft_ms):
|
||||
if ft_ms <= 0:
|
||||
return DIM
|
||||
if ft_ms < BUDGET_MS:
|
||||
return OK
|
||||
if ft_ms < WARN_MS:
|
||||
return WARN
|
||||
return FAIL
|
||||
|
||||
|
||||
def percentile(values, q):
|
||||
if not values:
|
||||
return 0.0
|
||||
s = sorted(values)
|
||||
idx = int(round(q * (len(s) - 1)))
|
||||
return s[max(0, min(len(s) - 1, idx))]
|
||||
|
||||
|
||||
class HUD:
|
||||
def __init__(self):
|
||||
self.ring = []
|
||||
self.bars = []
|
||||
self._built = False
|
||||
|
||||
def build(self, scene, trial):
|
||||
ui = scene.children
|
||||
acc = trial.accent
|
||||
|
||||
panel = mcrfpy.Frame(pos=(0, 0), size=(SCREEN_W, PANEL_H))
|
||||
panel.fill_color = color(PANEL)
|
||||
panel.outline = 1
|
||||
panel.outline_color = color(OUTLINE)
|
||||
panel.z_index = 100
|
||||
ui.append(panel)
|
||||
|
||||
self.name_cap = mcrfpy.Caption(text=trial.name, pos=(16, 10))
|
||||
self.name_cap.font_size = 28
|
||||
self.name_cap.fill_color = color(acc)
|
||||
self.name_cap.z_index = 102
|
||||
ui.append(self.name_cap)
|
||||
|
||||
self.desc_cap = mcrfpy.Caption(text=trial.description, pos=(16, 50))
|
||||
self.desc_cap.font_size = 14
|
||||
self.desc_cap.fill_color = color(DIM)
|
||||
self.desc_cap.z_index = 102
|
||||
ui.append(self.desc_cap)
|
||||
|
||||
# sparkline bars
|
||||
self.bars = []
|
||||
for i in range(SPARK_N):
|
||||
bx = SPARK_X + i * (BAR_W + BAR_GAP)
|
||||
bar = mcrfpy.Frame(pos=(bx, SPARK_BASE - 2), size=(BAR_W, 2))
|
||||
bar.fill_color = color(DIM)
|
||||
bar.z_index = 101
|
||||
ui.append(bar)
|
||||
self.bars.append(bar)
|
||||
|
||||
# 16.7 ms budget hairline
|
||||
budget_h = 2 + (BUDGET_MS / FT_CLAMP) * (SPARK_H - 2)
|
||||
by = SPARK_BASE - budget_h
|
||||
line = mcrfpy.Line(start=(SPARK_X, by),
|
||||
end=(SPARK_X + SPARK_N * (BAR_W + BAR_GAP), by),
|
||||
color=color(TEXT, 120), thickness=1.0)
|
||||
line.z_index = 103
|
||||
ui.append(line)
|
||||
self.spark_label = mcrfpy.Caption(text="16.7ms", pos=(SPARK_X, SPARK_TOP - 2))
|
||||
self.spark_label.font_size = 10
|
||||
self.spark_label.fill_color = color(DIM)
|
||||
self.spark_label.z_index = 103
|
||||
ui.append(self.spark_label)
|
||||
|
||||
# right readout block
|
||||
rx = 772
|
||||
self.fps_cap = mcrfpy.Caption(text="-- FPS", pos=(rx, 6))
|
||||
self.fps_cap.font_size = 34
|
||||
self.fps_cap.fill_color = color(TEXT)
|
||||
self.fps_cap.z_index = 102
|
||||
ui.append(self.fps_cap)
|
||||
|
||||
self.p95_cap = mcrfpy.Caption(text="frame p95: -- ms", pos=(rx, 48))
|
||||
self.p95_cap.font_size = 14
|
||||
self.p95_cap.fill_color = color(DIM)
|
||||
self.p95_cap.z_index = 102
|
||||
ui.append(self.p95_cap)
|
||||
|
||||
self.draw_cap = mcrfpy.Caption(text="draw calls: --", pos=(rx, 66))
|
||||
self.draw_cap.font_size = 14
|
||||
self.draw_cap.fill_color = color(DIM)
|
||||
self.draw_cap.z_index = 102
|
||||
ui.append(self.draw_cap)
|
||||
|
||||
self.load_cap = mcrfpy.Caption(text="LOAD: -- [MANUAL]", pos=(rx, 82))
|
||||
self.load_cap.font_size = 14
|
||||
self.load_cap.fill_color = color(acc)
|
||||
self.load_cap.z_index = 102
|
||||
ui.append(self.load_cap)
|
||||
|
||||
# bottom legend strip
|
||||
bottom = mcrfpy.Frame(pos=(0, SCREEN_H - 22), size=(SCREEN_W, 22))
|
||||
bottom.fill_color = color(PANEL)
|
||||
bottom.outline = 1
|
||||
bottom.outline_color = color(OUTLINE)
|
||||
bottom.z_index = 100
|
||||
ui.append(bottom)
|
||||
self.legend_cap = mcrfpy.Caption(text=LEGEND, pos=(12, SCREEN_H - 18))
|
||||
self.legend_cap.font_size = 12
|
||||
self.legend_cap.fill_color = color(DIM)
|
||||
self.legend_cap.z_index = 102
|
||||
ui.append(self.legend_cap)
|
||||
|
||||
self.ring = []
|
||||
self.unit = trial.unit
|
||||
self._built = True
|
||||
|
||||
def refresh(self, metrics, load_value, ramp_tag):
|
||||
if not self._built:
|
||||
return
|
||||
ft = float(metrics.get("frame_time", 0.0))
|
||||
self.ring.append(ft)
|
||||
if len(self.ring) > SPARK_N:
|
||||
self.ring = self.ring[-SPARK_N:]
|
||||
|
||||
# bars: oldest at left, newest at right
|
||||
n = len(self.ring)
|
||||
for i, bar in enumerate(self.bars):
|
||||
src = i - (SPARK_N - n)
|
||||
if src < 0:
|
||||
bar.h = 2
|
||||
bar.y = SPARK_BASE - 2
|
||||
bar.fill_color = color(OUTLINE)
|
||||
continue
|
||||
v = self.ring[src]
|
||||
clamped = max(0.0, min(FT_CLAMP, v))
|
||||
h = 2 + (clamped / FT_CLAMP) * (SPARK_H - 2)
|
||||
bar.h = h
|
||||
bar.y = SPARK_BASE - h
|
||||
bar.fill_color = color(budget_color(v))
|
||||
|
||||
bc = budget_color(ft)
|
||||
fps = int(round(1000.0 / ft)) if ft > 0 else 0
|
||||
self.fps_cap.text = "%d FPS" % fps
|
||||
self.fps_cap.fill_color = color(bc)
|
||||
self.p95_cap.text = "frame p95: %.1f ms" % percentile(self.ring, 0.95)
|
||||
self.draw_cap.text = "draw calls: %d" % int(metrics.get("draw_calls", 0))
|
||||
self.load_cap.text = "LOAD: %d %s %s" % (int(load_value), self.unit, ramp_tag)
|
||||
|
|
@ -55,7 +55,7 @@ class EntitySwarm(Trial):
|
|||
break
|
||||
e = mcrfpy.Entity((x, y), grid=self.grid)
|
||||
e.sprite_index = self.rng.choice((84, 85, 86, 100, 101))
|
||||
e.move_speed = 0.0
|
||||
e.move_speed = 0.3 # animate between cells so the swarm spreads visually
|
||||
e.set_behavior(SEEK, pathfinder=self.dmap)
|
||||
self.entities.append(e)
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ class EntitySwarm(Trial):
|
|||
|
||||
def tick(self, dt_ms):
|
||||
self.ticks += 1
|
||||
if self.ticks % 6 == 0:
|
||||
if self.ticks % 4 == 0:
|
||||
self._move_target()
|
||||
self.grid.step()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue