lainwired commited on
Commit
5fcc2e9
·
verified ·
1 Parent(s): ec924fd

Demo: per-step action log showing which policy made each move (hanabi/mini-hanabi)

Browse files
benchmark_ui/frontend/src/envs/hanabi/GameLog.tsx CHANGED
@@ -20,9 +20,12 @@ export type LogEntry = {
20
  type Props = {
21
  entries: LogEntry[]
22
  state: HanabiState
 
 
 
23
  }
24
 
25
- export function GameLog({ entries, state }: Props) {
26
  if (entries.length === 0) {
27
  return (
28
  <div className="text-xs text-ink-500 italic">
@@ -41,7 +44,7 @@ export function GameLog({ entries, state }: Props) {
41
  const absoluteIdx = total - 1 - revIdx
42
  return (
43
  <li key={absoluteIdx} className="text-sm animate-fade-in">
44
- <LogLine entry={entry} state={state} />
45
  </li>
46
  )
47
  })}
@@ -49,8 +52,9 @@ export function GameLog({ entries, state }: Props) {
49
  )
50
  }
51
 
52
- function LogLine({ entry, state }: { entry: LogEntry; state: HanabiState }) {
53
- const who = entry.player === 0 ? "You" : "Partner"
 
54
  const whoClass = entry.player === 0 ? "text-hanabi-blue" : "text-hanabi-yellow"
55
  const base = <span className={`font-medium ${whoClass}`}>{who}</span>
56
 
@@ -95,5 +99,5 @@ function LogLine({ entry, state }: { entry: LogEntry; state: HanabiState }) {
95
  )
96
  }
97
 
98
- return <div className="text-ink-500">{who} passed</div>
99
  }
 
20
  type Props = {
21
  entries: LogEntry[]
22
  state: HanabiState
23
+ // optional override for player labels. Defaults to ["You", "Partner"] for /play mode.
24
+ // /demo mode passes the actual policy names.
25
+ playerNames?: [string, string]
26
  }
27
 
28
+ export function GameLog({ entries, state, playerNames }: Props) {
29
  if (entries.length === 0) {
30
  return (
31
  <div className="text-xs text-ink-500 italic">
 
44
  const absoluteIdx = total - 1 - revIdx
45
  return (
46
  <li key={absoluteIdx} className="text-sm animate-fade-in">
47
+ <LogLine entry={entry} state={state} playerNames={playerNames} />
48
  </li>
49
  )
50
  })}
 
52
  )
53
  }
54
 
55
+ function LogLine({ entry, state, playerNames }: { entry: LogEntry; state: HanabiState; playerNames?: [string, string] }) {
56
+ const [name0, name1] = playerNames ?? ["You", "Partner"]
57
+ const who = entry.player === 0 ? name0 : name1
58
  const whoClass = entry.player === 0 ? "text-hanabi-blue" : "text-hanabi-yellow"
59
  const base = <span className={`font-medium ${whoClass}`}>{who}</span>
60
 
 
99
  )
100
  }
101
 
102
+ return <div className="text-ink-500">{base} passed</div>
103
  }
benchmark_ui/frontend/src/pages/Demo.tsx CHANGED
@@ -11,6 +11,7 @@ import {
11
  type PartnerInfo,
12
  } from "../api/client"
13
  import { getEnvComponents } from "../envs/registry"
 
14
 
15
  type AgentOption = {
16
  id: string
@@ -264,9 +265,20 @@ export function Demo() {
264
  />
265
  </div>
266
 
267
- {currentFrame.event && (
268
- <div className="text-xs font-mono text-ink-400">
269
- {JSON.stringify(currentFrame.event)}
 
 
 
 
 
 
 
 
 
 
 
270
  </div>
271
  )}
272
  </div>
@@ -282,6 +294,44 @@ export function Demo() {
282
  )
283
  }
284
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  function AgentSelect({
286
  label,
287
  options,
 
11
  type PartnerInfo,
12
  } from "../api/client"
13
  import { getEnvComponents } from "../envs/registry"
14
+ import { GameLog, type LogEntry } from "../envs/hanabi/GameLog"
15
 
16
  type AgentOption = {
17
  id: string
 
265
  />
266
  </div>
267
 
268
+ {(env === "hanabi" || env === "mini-hanabi") && parsedState && (
269
+ <HanabiDemoLog
270
+ frames={frames}
271
+ upToIdx={frameIdx}
272
+ state={parsedState as any}
273
+ playerNames={[labelA, labelB]}
274
+ />
275
+ )}
276
+ {!(env === "hanabi" || env === "mini-hanabi") && currentFrame.event && (
277
+ <div className="text-xs text-ink-400">
278
+ <span className="font-medium text-ink-200">
279
+ {currentFrame.actor === 0 ? labelA : labelB}
280
+ </span>
281
+ <span className="font-mono"> · {String((currentFrame.event as any).kind ?? JSON.stringify(currentFrame.event))}</span>
282
  </div>
283
  )}
284
  </div>
 
294
  )
295
  }
296
 
297
+ // Per-frame event narration for Hanabi/mini-hanabi demos so the user can see
298
+ // each move attributed to a specific policy.
299
+ function HanabiDemoLog({
300
+ frames, upToIdx, state, playerNames,
301
+ }: {
302
+ frames: { event: Record<string, unknown> | null; actor: number | null }[]
303
+ upToIdx: number
304
+ state: any
305
+ playerNames: [string, string]
306
+ }) {
307
+ const entries: LogEntry[] = []
308
+ for (let i = 0; i <= Math.min(upToIdx, frames.length - 1); i++) {
309
+ const f = frames[i]
310
+ if (!f.event || f.actor == null) continue
311
+ const ev = f.event as any
312
+ const player = (f.actor === 0 ? 0 : 1) as 0 | 1
313
+ if (ev.kind === "play" || ev.kind === "discard" || ev.kind === "hint_color" || ev.kind === "hint_rank" || ev.kind === "noop") {
314
+ entries.push({
315
+ player,
316
+ kind: ev.kind,
317
+ slot: ev.slot,
318
+ color: ev.color,
319
+ rank: ev.rank,
320
+ bombed: ev.bombed,
321
+ scored: ev.scored,
322
+ revealed: ev.revealed,
323
+ })
324
+ }
325
+ }
326
+ return (
327
+ <div className="rounded-lg border border-ink-800 bg-ink-950/40 p-4 max-h-72 overflow-y-auto">
328
+ <div className="text-xs uppercase tracking-wider text-ink-500 mb-2">Action log</div>
329
+ <GameLog entries={entries} state={state} playerNames={playerNames} />
330
+ </div>
331
+ )
332
+ }
333
+
334
+
335
  function AgentSelect({
336
  label,
337
  options,