File size: 4,359 Bytes
0e23a69
 
 
 
 
 
 
 
 
 
 
 
 
 
312c390
0e23a69
 
 
312c390
0e23a69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312c390
0e23a69
 
312c390
0e23a69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { useEffect, useState } from 'react'
import { useGameStore } from './hooks/useGameStore.js'
import { useAgentLoop, greedyPick, buildPitch } from './hooks/useAgentLoop.js'

import TopBar from './components/TopBar.jsx'
import PlaybackControls from './components/PlaybackControls.jsx'
import MetricsPanel from './components/MetricsPanel.jsx'
import TrustPanel from './components/TrustPanel.jsx'
import EventBanner from './components/EventBanner.jsx'
import NPCGrid from './components/NPCGrid.jsx'
import AgentDecision from './components/AgentDecision.jsx'
import VoteTally from './components/VoteTally.jsx'
import HistoryTimeline from './components/HistoryTimeline.jsx'
import EndScreen from './components/EndScreen.jsx'
import RewardTrace from './components/RewardTrace.jsx'

export default function App() {
    const { state, resetGame, stepGame, setSpeed, setPaused } = useGameStore()
    const { obs, prevObs, done, loading, error, lastReward, lastInfo, speed, paused, rewardTrace, cumReward } = state

    const [toast, setToast] = useState(null)

    // Show error toast
    useEffect(() => {
        if (error) {
            setToast(error)
            const t = setTimeout(() => setToast(null), 5000)
            return () => clearTimeout(t)
        }
    }, [error])

    // Boot
    useEffect(() => { resetGame(42) }, [resetGame])

    // Wire agent loop
    useAgentLoop(state, stepGame)

    const handleRun = () => setPaused(false)
    const handlePause = () => setPaused(true)
    const handleReset = () => { resetGame(Math.floor(Math.random() * 9999)) }
    const handleReplay = () => { resetGame(Math.floor(Math.random() * 9999)) }

    const handleStep = async () => {
        if (!obs || loading || done) return
        const decision = greedyPick(obs)
        const pitch = buildPitch(obs, decision)
        if (decision) await stepGame(decision, pitch)
    }

    const round = obs?.round ?? 0
    const curState = obs?.state
    const prevState = prevObs?.state

    return (
        <div className="app-shell">
            <TopBar obs={obs} round={round} />
            <PlaybackControls
                paused={paused}
                loading={loading}
                done={done}
                obs={obs}
                speed={speed}
                onRun={handleRun}
                onPause={handlePause}
                onStep={handleStep}
                onReset={handleReset}
                onSpeedChange={setSpeed}
            />

            {/* Metrics strip at top — always visible */}
            {curState && (
                <MetricsPanel state={curState} prevState={prevState} />
            )}

            <div className="main-grid">
                {/* Left — Trust + Reward + History */}
                <div className="col-left">
                    <TrustPanel trust={curState?.trust} prevTrust={prevState?.trust} />
                    <RewardTrace trace={rewardTrace} cumReward={cumReward} lastReward={lastReward} />
                    <HistoryTimeline history={curState?.history} />
                </div>

                {/* Centre — Event + NPCs + Agent Decision */}
                <div className="col-center">
                    <EventBanner event={obs?.event} round={round} />
                    <NPCGrid npcStatements={obs?.npc_statements} />
                    <AgentDecision obs={obs} loading={loading} lastInfo={lastInfo} />
                </div>

                {/* Right — Vote Tally */}
                <div className="col-right">
                    {lastInfo?.winning_vote_tally && <VoteTally info={lastInfo} />}
                    {!lastInfo && (
                        <div className="card">
                            <div className="section-label">Vote Tally</div>
                            <div className="card-body" style={{ fontSize: '0.65rem', color: 'var(--text-muted)', textAlign: 'center', padding: '1.25rem 1rem' }}>
                                // vote tally appears after first decision.
                            </div>
                        </div>
                    )}
                </div>
            </div>

            {done && obs && <EndScreen obs={obs} onReplay={handleReplay} />}

            {toast && (
                <div className="toast">
                    ⚠ {toast}
                </div>
            )}
        </div>
    )
}