File size: 54,767 Bytes
22974f7
 
 
 
 
 
1a929c7
 
22974f7
 
bcbbf3d
2f9ece5
 
 
1a929c7
 
22974f7
 
1a929c7
22974f7
 
1a929c7
22974f7
 
 
1a929c7
22974f7
1a929c7
 
 
22974f7
 
1a929c7
 
 
 
22974f7
 
 
 
2f9ece5
 
 
22974f7
 
 
 
2f9ece5
22974f7
2f9ece5
22974f7
 
 
 
bcbbf3d
22974f7
 
 
 
 
 
 
 
 
2f9ece5
 
22974f7
1a929c7
 
 
 
 
 
 
 
22974f7
 
 
 
 
bcbbf3d
22974f7
 
2f9ece5
 
22974f7
2f9ece5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22974f7
 
2f9ece5
 
22974f7
 
bcbbf3d
22974f7
 
 
 
 
 
 
 
 
 
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<meta name="description" content="Explore JEV-27B in live games, text and image decisions, and Doom. Follow the model’s choices and inference time.">
<title>JEV-27B · Decision playground — AutoTrust</title>
<link rel="icon" href="/autotrust-mark.svg" type="image/svg+xml">
<style>
:root{color-scheme:dark;--bg:#122E27;--surface:#18362F;--deep:#0E251F;--ivory:#FAF6EC;--gold:#E4C96A;--muted:#A9B9AF;--line:rgba(250,246,236,.14);--soft:rgba(250,246,236,.045);--red:#F3B4A1}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ivory);font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;line-height:1.55}button,input,textarea,select{font:inherit}button,a,input,textarea,select{-webkit-tap-highlight-color:transparent}a{color:inherit}button{cursor:pointer}button:disabled{cursor:default;opacity:.42}button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:2px solid var(--gold);outline-offset:4px}button:disabled:hover{transform:none}button{transition:background .15s,border-color .15s}main,.top,footer{max-width:1224px;width:calc(100% - 80px);margin:auto}[hidden]{display:none!important}.top{height:86px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);gap:25px}.brand{display:flex;gap:10px;align-items:center;text-decoration:none;flex:none}.brand img{height:33px;width:33px}.brand-name{font-family:Georgia,serif;font-size:21px;letter-spacing:-.3px}.nav{display:flex;align-items:center;gap:26px;font-size:12px;color:var(--muted)}.nav a{text-decoration:none}.nav a:hover{color:var(--gold)}.nav .nav-current{color:var(--ivory)}.nav-divider{height:18px;border-left:1px solid var(--line)}.connection{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted);white-space:nowrap}.dot{height:6px;width:6px;border-radius:50%;background:#90A197;flex:none}.connection[data-status=ok] .dot{background:var(--gold);box-shadow:0 0 0 4px rgba(228,201,106,.07)}.connection[data-status=unavailable] .dot{background:var(--red)}.connection[data-status=unavailable]{color:var(--red)}.hero{padding:48px 0 32px;display:flex;align-items:flex-end;justify-content:space-between;gap:42px}.eyebrow{font-size:10px;text-transform:uppercase;letter-spacing:.2em;color:var(--gold);margin:0 0 15px;display:flex;gap:12px;align-items:center}.eyebrow:before{content:"";height:1px;width:23px;background:var(--gold)}h1,h2,h3,p{margin-top:0}h1{font-family:Georgia,"Times New Roman",serif;font-weight:400;letter-spacing:-2.2px;line-height:1.05;font-size:clamp(38px,4.5vw,60px);margin:0}h1 em{color:var(--gold);font-weight:400}.hero-copy{max-width:320px;font-size:13px;color:var(--muted);margin:0 0 3px;line-height:1.8}.hero-copy strong{font-weight:500;color:var(--ivory)}.tabbar{display:flex;border-bottom:1px solid var(--line);gap:34px;margin:0 0 26px}.tab{display:flex;gap:10px;align-items:center;border:0;background:none;color:var(--muted);padding:17px 0;position:relative;font-size:13px}.tab .number{font-size:10px;letter-spacing:.04em;opacity:.65}.tab[aria-selected=true]{color:var(--gold)}.tab[aria-selected=true]:after{content:"";height:2px;background:var(--gold);position:absolute;bottom:-1px;left:0;right:0}.tab svg{width:16px;height:16px}.tab-tail{margin:auto 0 auto auto;color:var(--muted);font-size:10px;letter-spacing:.1em;text-transform:uppercase}.lab-grid{display:grid;grid-template-columns:1.07fr 1fr;gap:20px}.panel{border:1px solid var(--line);border-radius:12px;background:var(--surface);overflow:hidden}.panel-head{padding:21px 25px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;gap:12px}.section-label{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);display:flex;align-items:center;gap:9px}.section-label span{color:var(--gold);font-family:ui-monospace,monospace}.panel-head .small{font-size:10px;color:var(--muted)}.input-body{padding:22px 25px 24px}.preset-label{font-size:11px;color:var(--muted);margin-bottom:9px}.presets{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:24px}.preset{background:var(--soft);border:1px solid var(--line);border-radius:6px;color:var(--muted);padding:7px 10px;font-size:11px}.preset:hover,.preset[aria-pressed=true]{color:var(--gold);border-color:rgba(228,201,106,.45);background:rgba(228,201,106,.06)}label{display:block;font-size:11px;color:var(--ivory);margin:0 0 8px}.label-line{display:flex;align-items:baseline;justify-content:space-between;gap:10px}.label-line span{color:var(--muted);font-size:10px}.field{margin:0 0 19px}.field:last-of-type{margin-bottom:21px}input,textarea,select{width:100%;border:1px solid rgba(250,246,236,.2);background:var(--deep);color:var(--ivory);border-radius:6px;padding:11px 12px;font-size:12px;line-height:1.65}textarea{resize:vertical;min-height:85px}textarea[readonly]{color:var(--muted)}input::placeholder,textarea::placeholder{color:#91A298}select{padding:10px 12px;appearance:auto}.form-row{display:grid;grid-template-columns:1fr 1.16fr;gap:16px;align-items:start}.helper{font-size:10px;color:var(--muted);line-height:1.6;margin:7px 0 0}.primary,.secondary,.action-button{border-radius:6px;padding:11px 15px;font-size:12px;border:1px solid var(--line);background:var(--soft);color:var(--ivory);display:inline-flex;align-items:center;justify-content:center;gap:8px}.primary{background:var(--gold);color:var(--bg);border-color:var(--gold);font-weight:650}.primary:hover:not(:disabled){background:#EFDA8C}.secondary:hover:not(:disabled),.action-button:hover:not(:disabled){border-color:rgba(228,201,106,.55);background:rgba(228,201,106,.06);color:var(--gold)}.primary svg,.secondary svg{width:14px;height:14px}.run-row{display:flex;align-items:center;justify-content:space-between;gap:16px}.run-row .primary{min-width:146px}.run-caption{font-size:10px;color:var(--muted);text-align:right;line-height:1.6;max-width:150px}.error{padding:12px 14px;background:rgba(243,180,161,.08);border:1px solid rgba(243,180,161,.25);border-radius:6px;color:var(--red);font-size:12px;margin:15px 0 0}.output-panel{display:flex;flex-direction:column;min-width:0}.output-body{padding:28px 25px;flex:1;display:flex;flex-direction:column}.empty-output{display:flex;align-items:center;justify-content:center;flex:1;min-height:280px;flex-direction:column;text-align:center}.signal-mark{height:78px;width:78px;position:relative;display:flex;align-items:center;justify-content:center;margin-bottom:27px}.signal-mark:before,.signal-mark:after{position:absolute;content:"";inset:0;border:1px solid rgba(228,201,106,.25);transform:rotate(45deg);border-radius:12px}.signal-mark:after{inset:14px;border-color:rgba(228,201,106,.5);border-radius:6px}.signal-mark svg{height:25px;width:25px;color:var(--gold)}.empty-output h2{font-family:Georgia,serif;font-size:26px;font-weight:400;letter-spacing:-.5px;margin-bottom:10px}.empty-output p{max-width:275px;font-size:12px;color:var(--muted);line-height:1.8;margin:0}.output-foot{border-top:1px solid var(--line);padding:15px 25px;display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:10px;color:var(--muted)}.mono{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.result-label{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-bottom:10px}.picked{font-family:Georgia,serif;color:var(--gold);font-size:31px;font-weight:400;letter-spacing:-.6px;line-height:1.3;overflow-wrap:anywhere;margin-bottom:28px}.bar-list{display:flex;flex-direction:column;gap:19px}.bar-row{min-width:0}.bar-top{display:flex;justify-content:space-between;gap:16px;font-size:11px;margin-bottom:7px}.bar-name{overflow-wrap:anywhere;color:var(--muted)}.bar-row.selected .bar-name{color:var(--ivory)}.bar-value{font-family:ui-monospace,monospace;flex:none;color:var(--muted)}.bar-row.selected .bar-value{color:var(--gold)}.bar-track{height:5px;background:rgba(250,246,236,.08);border-radius:3px;overflow:hidden}.bar-fill{height:100%;width:0;background:#718D7F;border-radius:3px;transition:width .35s ease}.bar-row.selected .bar-fill{background:var(--gold)}.result-note{font-size:10px;color:var(--muted);line-height:1.7;margin:25px 0 0}.loading .signal-mark{animation:signal-breathe 1.4s ease-in-out infinite}.loading .signal-mark:after{background:rgba(228,201,106,.08)}.below-lab{padding:23px 3px 0;display:grid;grid-template-columns:repeat(3,1fr);gap:35px}.how-item{font-size:11px;color:var(--muted);line-height:1.75;display:flex;gap:12px}.how-item>span{font-family:Georgia,serif;color:var(--gold);font-size:17px;margin-top:-2px}.how-item p{margin:0}.how-item strong{color:var(--ivory);display:block;font-weight:500;margin-bottom:3px}.arena-toolbar{display:flex;justify-content:space-between;align-items:center;gap:20px;margin:0 0 17px}.scenario-wrap{display:flex;align-items:center;gap:13px}.scenario-wrap label{margin:0;font-size:11px;color:var(--muted)}.scenario-wrap select{min-width:180px;padding:8px 10px}.arena-toolbar .primary{padding:10px 17px}.arena-grid{display:grid;grid-template-columns:minmax(0,1.8fr) minmax(265px,1fr);gap:20px}.game-screen{position:relative;aspect-ratio:4/3;background:#0A1915;display:flex;align-items:center;justify-content:center;border-bottom:1px solid var(--line);overflow:hidden}.game-screen img{width:100%;height:100%;object-fit:contain;image-rendering:pixelated;display:block}.game-placeholder{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;text-align:center;padding:32px;background-image:linear-gradient(rgba(228,201,106,.035) 1px,transparent 1px),linear-gradient(90deg,rgba(228,201,106,.035) 1px,transparent 1px);background-size:36px 36px}.game-placeholder svg{width:43px;height:43px;color:var(--gold);opacity:.7;margin-bottom:21px}.game-placeholder h2{font-family:Georgia,serif;font-weight:400;font-size:27px;letter-spacing:-.5px;margin:0 0 9px}.game-placeholder p{font-size:12px;color:var(--muted);max-width:235px;margin:0;line-height:1.8}.frame-badge{position:absolute;top:14px;left:14px;border:1px solid rgba(250,246,236,.2);color:var(--ivory);background:rgba(10,25,21,.85);padding:5px 8px;border-radius:4px;font-size:9px;text-transform:uppercase;letter-spacing:.09em}.metrics{display:grid;grid-template-columns:repeat(5,1fr);padding:17px 10px}.metric{text-align:center;border-right:1px solid var(--line)}.metric:last-child{border:0}.metric span{display:block;font-size:9px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:5px}.metric b{font-weight:500;font-size:18px;font-family:ui-monospace,monospace}.arena-sidebar{display:flex;flex-direction:column;gap:17px}.controls-body{padding:19px 20px}.controls-label{font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);margin:0 0 10px}.manual-actions{display:grid;grid-template-columns:1fr 1fr;gap:7px}.action-button{font-size:11px;padding:10px 8px;min-height:39px}.no-actions{font-size:11px;color:var(--muted);margin:0;padding:5px 0 13px}.divider{border:0;border-top:1px solid var(--line);margin:21px 0}.agent-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}.agent-actions .primary,.agent-actions .secondary{padding:10px 8px;font-size:11px}.stop-button{width:100%;margin-top:9px;border-color:rgba(243,180,161,.4);color:var(--red)}.arena-note{font-size:10px;color:var(--muted);line-height:1.8;margin:13px 0 0}.game-status{font-size:11px;color:var(--muted);margin:16px 0 0;min-height:18px}.game-result{padding:19px 20px}.game-result .picked{font-size:23px;margin-bottom:19px}.game-result .bar-list{gap:12px}.game-result .bar-top{font-size:10px}.game-result .bar-track{height:4px}.game-decision-empty{font-size:12px;line-height:1.8;color:var(--muted);margin:0}.inference-timing{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 16px;margin:0 0 24px;border:1px solid rgba(228,201,106,.3);border-radius:6px;background:rgba(228,201,106,.055)}.inference-timing span{font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.inference-timing strong{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:23px;font-weight:500;white-space:nowrap;color:var(--gold)}.game-result .inference-timing{padding:12px;margin-bottom:20px;gap:10px}.game-result .inference-timing strong{font-size:21px}.game-decision-meta{font-size:10px;color:var(--muted);margin:19px 0 0}.arena-explainer{margin:16px 0 0;color:var(--muted);font-size:11px}.arena-explainer strong{font-weight:500;color:var(--ivory)}footer{display:flex;align-items:center;justify-content:space-between;gap:16px;border-top:1px solid var(--line);margin-top:43px;padding:22px 0 31px;color:var(--muted);font-size:10px}footer a{text-decoration:none;color:var(--gold)}.footer-links{display:flex;gap:20px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@keyframes signal-breathe{50%{opacity:.45;transform:scale(.93)}}@media(min-width:1400px){.hero{padding-top:55px}}@media(max-width:980px){main,.top,footer{width:calc(100% - 48px)}.hero{gap:24px}.hero-copy{max-width:260px;font-size:12px}.nav{gap:19px}.form-row{grid-template-columns:1fr}.lab-grid{grid-template-columns:1.05fr 1fr}.input-body,.output-body{padding:22px 20px}.panel-head{padding:20px}.output-foot{padding:15px 20px}.arena-grid{grid-template-columns:minmax(0,1.6fr) minmax(245px,1fr)}.game-screen{aspect-ratio:1/1}.agent-actions{grid-template-columns:1fr}.below-lab{gap:22px}.nav-divider{display:none}.top>.connection{display:none}}@media(max-width:720px){.hero{display:block;padding-top:36px}.hero-copy{max-width:430px;margin:19px 0 0;font-size:13px}.lab-grid,.arena-grid{grid-template-columns:1fr}.form-row{grid-template-columns:1fr 1.16fr}.empty-output{min-height:245px}.output-panel{min-height:350px}.arena-sidebar{display:grid;grid-template-columns:1fr 1fr;align-items:start}.game-screen{aspect-ratio:4/3}.agent-actions{grid-template-columns:1fr}.below-lab{gap:17px}.how-item{display:block}.how-item>span{display:block;margin:0 0 6px}.tab-tail{font-size:9px}.nav{gap:18px}}@media(max-width:480px){main,.top,footer{width:calc(100% - 32px)}.top{height:73px;gap:12px}.brand-name{font-size:19px}.brand img{width:29px;height:29px}.nav{font-size:11px;gap:15px}.nav .nav-current{display:none}h1{font-size:41px;letter-spacing:-1.7px}.hero{padding:31px 0 22px}.hero-copy{font-size:12px}.tabbar{gap:24px;margin-bottom:20px}.tab-tail{display:none}.tab{font-size:12px}.panel-head{padding:17px 18px}.input-body,.output-body{padding:20px 18px}.form-row{grid-template-columns:1fr}.presets{gap:6px}.preset{padding:7px 9px;font-size:10px}.below-lab{grid-template-columns:1fr;gap:16px;padding-top:22px}.how-item{display:flex;gap:12px}.how-item>span{flex:0 0 15px}.how-item strong{display:inline;margin-right:5px}.scenario-wrap{display:block}.scenario-wrap label{margin-bottom:5px;font-size:10px}.scenario-wrap select{min-width:0;font-size:11px;max-width:185px}.arena-toolbar{gap:10px;align-items:flex-end}.arena-toolbar .primary{font-size:11px;padding:11px 13px;white-space:nowrap}.arena-sidebar{display:flex}.agent-actions{grid-template-columns:1fr 1fr}.metrics{padding:14px 4px}.metric b{font-size:15px}.metric span{font-size:8px;letter-spacing:.03em}footer{align-items:flex-start;flex-direction:column;gap:10px;margin-top:32px}.run-caption{font-size:9px}.output-foot{padding:15px 18px}.picked{font-size:28px}}@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* One optional image, kept in page memory until a decision is submitted. */
.vision-details{margin:-12px 0 24px;padding:0 2px 14px;border-bottom:1px solid var(--line)}.vision-timing{display:flex;align-items:center;justify-content:space-between;gap:15px;font-size:11px;color:var(--muted)}.vision-timing strong{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;font-weight:500;color:var(--ivory);white-space:nowrap}.vision-model{font-size:9px;color:var(--muted);margin:5px 0 0;overflow-wrap:anywhere}.image-field{margin-top:15px}.image-drop{position:relative;border:1px dashed rgba(250,246,236,.28);border-radius:7px;background:var(--deep);transition:border-color .15s,background .15s}.image-drop:focus-within{outline:2px solid var(--gold);outline-offset:4px}.image-drop.is-dragging{border-color:var(--gold);background:rgba(228,201,106,.08)}.image-drop[aria-disabled=true]{opacity:.5}.image-picker{display:flex;align-items:center;justify-content:center;gap:11px;min-height:83px;padding:15px 17px;margin:0;cursor:pointer;color:var(--muted);font-size:11px;line-height:1.6;text-align:left}.image-picker svg{width:24px;height:24px;flex:none;color:var(--gold)}.image-picker strong{font-weight:500;color:var(--gold)}.image-picker small{display:block;font-size:10px;color:var(--muted);margin-top:2px}.image-drop[aria-disabled=true] .image-picker{cursor:default}.image-preview{display:flex;align-items:center;gap:13px;padding:13px;min-width:0}.image-thumbnail{display:block;flex:0 0 74px;width:74px;height:74px;object-fit:contain;border-radius:4px;background:rgba(250,246,236,.04);border:1px solid var(--line)}.image-details{flex:1;min-width:0}.image-name{color:var(--ivory);font-size:11px;line-height:1.5;overflow-wrap:anywhere;margin:0 0 5px}.image-meta{font-size:10px;color:var(--muted);line-height:1.5;margin:0}.image-remove{border:1px solid var(--line);border-radius:5px;background:transparent;color:var(--muted);font-size:10px;padding:6px 9px;margin-top:9px}.image-remove:hover:not(:disabled){color:var(--ivory);border-color:var(--ivory)}.image-status{min-height:0;font-size:10px;color:var(--gold);margin:7px 0 0;line-height:1.6}.image-status:empty{display:none}.image-error{margin-top:9px;padding:10px 12px;font-size:11px}@media(max-width:480px){.image-picker{padding:14px 12px;gap:10px}.image-preview{padding:11px;gap:11px}.image-thumbnail{width:64px;height:64px;flex-basis:64px}}

#live-games{background:#fff;border-radius:12px;overflow:hidden}.tabbar{overflow-x:auto}.tab{flex-shrink:0}.tab-tail{white-space:nowrap}@media(max-width:720px){.tabbar{gap:22px}.tab-tail{display:none}}@media(max-width:480px){.tabbar{gap:18px}.tab{gap:6px}.tab .number{display:none}}
</style>
</head>
<body>
<header class="top">
<a class="brand" href="/" aria-label="AutoTrust home"><img src="/autotrust-mark.svg" width="33" height="33" alt=""><span class="brand-name">AutoTrust</span></a>
<nav class="nav" aria-label="Main navigation"><a href="/">Demo reel</a><a class="nav-current" href="/play" aria-current="page">Decision playground</a><span class="nav-divider" aria-hidden="true"></span><a href="https://huggingface.co/autotrust/JEV-27B" target="_blank" rel="noopener noreferrer">Model ↗</a></nav>
<div class="connection" id="connection" data-status="checking" role="status"><span class="dot" aria-hidden="true"></span><span id="connection-text">Checking model…</span></div>
</header>
<main>
<section class="hero" aria-labelledby="page-title"><div><p class="eyebrow">JEV-27B · Interactive demos</p><h1 id="page-title">Decision playground</h1></div><p class="hero-copy">Explore live games and decisions with text or images.<br><strong>Follow each choice and its inference time.</strong></p></section>
<div class="tabbar" role="tablist" aria-label="Playground mode">
<button class="tab" id="games-tab" type="button" role="tab" aria-selected="true" aria-controls="live-games" tabindex="0"><span class="number">01</span><svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="m10 2 7 4v8l-7 4-7-4V6l7-4Zm0 8 7-4M10 10 3 6m7 4v8"/></svg>Live games</button>
<button class="tab" id="lab-tab" type="button" role="tab" aria-selected="false" aria-controls="lab" tabindex="-1"><span class="number">02</span><svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="M4 4h12v12H4zM7 7h6M7 10h6M7 13h3"/></svg>Decision lab</button>
<button class="tab" id="arena-tab" type="button" role="tab" aria-selected="false" aria-controls="arena" tabindex="-1"><span class="number">03</span><svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="M7 4h6l4 4v7l-3 1-3-3H9l-3 3-3-1V8zM7 7v5M4.5 9.5h5M13 8.5h.01M15 11h.01"/></svg>Doom arena</button>
<span class="tab-tail">Explore structured intelligence</span>
</div>
<section id="live-games" role="tabpanel" aria-labelledby="games-tab" tabindex="0">
<iframe id="live-games-frame" src="/arena?embed=1" title="Live game arena" style="display:block;width:100%;height:1000px;border:0;background:#fff;border-radius:12px" allow="autoplay; fullscreen"></iframe>
</section>
<section id="lab" role="tabpanel" aria-labelledby="lab-tab" tabindex="0" hidden>
<div class="lab-grid">
<div class="panel"><div class="panel-head"><div class="section-label"><span>01</span>Set the situation</div><span class="small">Your input</span></div>
<form id="decision-form" class="input-body">
<div class="preset-label">Start with an example</div><div class="presets" aria-label="Example situations"><button class="preset" type="button" data-preset="support" aria-pressed="true">Support ticket</button><button class="preset" type="button" data-preset="route" aria-pressed="false">Choose a route</button><button class="preset" type="button" data-preset="fact" aria-pressed="false">Factual check</button></div>
<div class="field"><div class="label-line"><label for="state">Situation</label><span id="state-hint">Text or image context</span></div><textarea id="state" rows="4" maxlength="6000" required spellcheck="false" placeholder="Describe the situation, or attach an image below." aria-describedby="state-hint"></textarea>
<div class="image-field"><div class="label-line"><label for="image-input">Image</label><span>Optional · one image</span></div><div class="image-drop" id="image-drop" role="group" aria-label="Image attachment" aria-disabled="false"><input id="image-input" class="sr-only" type="file" accept="image/jpeg,image/png,image/webp" aria-label="Choose an image" aria-describedby="image-help image-status image-error"><label class="image-picker" id="image-picker" for="image-input"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="3"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m4 17 5-5 4 4 3-3 5 5"/></svg><span><strong>Choose an image</strong> or drop it here<small>Add a photo, screenshot, or visual question.</small></span></label><div class="image-preview" id="image-preview" hidden><img class="image-thumbnail" id="image-thumbnail" alt="Preview of the attached image"><div class="image-details"><p class="image-name" id="image-name"></p><p class="image-meta" id="image-meta"></p><button class="image-remove" id="remove-image" type="button" aria-label="Remove attached image">Remove image</button></div></div></div><p class="helper" id="image-help">JPEG, PNG or WebP · up to 5 MiB. Text is optional with an image. Images are read by a vision model, then JEV makes the decision.</p><p class="image-status" id="image-status" role="status" aria-live="polite"></p><p class="error image-error" id="image-error" role="alert" hidden></p></div>
</div>
<div class="field"><label for="question">What should JEV decide?</label><input id="question" maxlength="1000" required autocomplete="off"></div>
<div class="form-row"><div class="field"><label for="kind">Decision type</label><select id="kind"><option value="choice">Choose an option</option><option value="noul">True or false</option><option value="score">Score from 0 to 5</option></select><p class="helper" id="kind-help">Compare the choices you provide.</p></div><div class="field"><div class="label-line"><label for="options">Options</label><span id="options-label">One per line</span></div><textarea id="options" rows="3" maxlength="8000" required spellcheck="false"></textarea></div></div>
<div class="run-row"><button class="primary" id="run-decision" type="submit">Run JEV <svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M3 9h11M10 5l4 4-4 4"/></svg></button><span class="run-caption">Each run calls the<br>connected JEV-27B model.</span></div><p class="error" id="decision-error" role="alert" hidden></p>
</form></div>
<div class="panel output-panel"><div class="panel-head"><div class="section-label"><span>02</span>Read the decision</div><span class="small" id="result-status">Ready when you are</span></div><div class="output-body"><div class="empty-output" id="empty-output"><div class="signal-mark" aria-hidden="true"><svg viewBox="0 0 28 28" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M7 14h14M14 7v14"/><circle cx="14" cy="14" r="10"/></svg></div><h2 id="empty-title">Give a choice some context.</h2><p id="empty-copy">Run an example or write your own situation. JEV’s answer and option probabilities will appear here.</p></div><div id="decision-result" aria-live="polite" hidden><p class="result-label">JEV’s choice</p><h2 class="picked" id="picked-answer"></h2><div class="inference-timing"><span>JEV inference time</span><strong id="result-latency">—</strong></div><div class="vision-details" id="vision-details" hidden><div class="vision-timing"><span>Image understanding</span><strong id="vision-latency">—</strong></div><p class="vision-model mono" id="vision-model"></p></div><div class="bar-list" id="probabilities" aria-label="Option probabilities"></div><p class="result-note">Probabilities compare the provided options. They are not a guarantee that an answer is correct.</p></div></div><div class="output-foot"><span class="mono" id="result-model">autotrust/JEV-27B</span><span>Timing reported by the model server</span></div></div>
</div>
<div class="below-lab"><div class="how-item"><span>1</span><p><strong>Describe the situation.</strong>Write the context or attach an image. Add the details that matter.</p></div><div class="how-item"><span>2</span><p><strong>Define the decision.</strong>Choose an option, check a statement, or assign a score.</p></div><div class="how-item"><span>3</span><p><strong>Inspect the result.</strong>Compare option probabilities. Edit the context and try again.</p></div></div>
</section>
<section id="arena" role="tabpanel" aria-labelledby="arena-tab" tabindex="0" hidden>
<div class="arena-toolbar"><div class="scenario-wrap"><label for="scenario">Choose an arena</label><select id="scenario"><option value="basic">Target practice</option><option value="defend_the_center">Center defense</option></select></div><button class="primary" type="button" id="new-game">Start arena <svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M5 3l9 6-9 6z"/></svg></button></div>
<div class="arena-grid"><div><div class="panel"><div class="game-screen"><img id="game-frame" alt="Current Doom arena frame" hidden><div class="game-placeholder" id="game-placeholder"><svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.3" aria-hidden="true"><circle cx="24" cy="24" r="14"/><circle cx="24" cy="24" r="5"/><path d="M24 2v12M24 34v12M2 24h12M34 24h12"/></svg><h2>Your move. Or JEV’s.</h2><p>Start an arena, try the controls, then hand the next decision to JEV.</p></div><span class="frame-badge" id="frame-badge" hidden>Simulation paused</span></div><div class="metrics" aria-label="Game metrics"><div class="metric"><span>Health</span><b id="metric-health">—</b></div><div class="metric"><span>Ammo</span><b id="metric-ammo">—</b></div><div class="metric"><span>Kills</span><b id="metric-kills">—</b></div><div class="metric"><span>Reward</span><b id="metric-reward">—</b></div><div class="metric"><span>Steps</span><b id="metric-steps">—</b></div></div></div><p class="arena-explainer"><strong>A turn-by-turn simulation.</strong> JEV reads game state; the simulation pauses between decisions.</p><p class="error" id="game-error" role="alert" hidden></p></div>
<div class="arena-sidebar"><div class="panel"><div class="panel-head"><div class="section-label"><span>01</span>Take control</div></div><div class="controls-body"><p class="controls-label">Your next move</p><div class="manual-actions" id="manual-actions"></div><p class="no-actions" id="no-actions">Start an arena to unlock its controls.</p><hr class="divider"><p class="controls-label">Let JEV decide</p><div class="agent-actions"><button class="primary" id="jev-move" type="button" disabled>JEV next move</button><button class="secondary" id="auto-play" type="button" disabled>Auto-play · 10 steps</button></div><button class="secondary stop-button" id="stop-auto" type="button" hidden>Stop after this move</button><p class="arena-note">Auto-play runs up to 10 model decisions. You can stop after the current move.</p><p class="game-status" id="game-status" role="status">No arena running.</p></div></div><div class="panel"><div class="panel-head"><div class="section-label"><span>02</span>JEV’s last decision</div></div><div class="game-result"><p class="game-decision-empty" id="game-decision-empty">Let JEV take a move to see its choice and action probabilities.</p><div id="game-decision" hidden><h3 class="picked" id="game-picked"></h3><div class="inference-timing"><span>Model inference time</span><strong id="game-latency">—</strong></div><div class="bar-list" id="game-probabilities" aria-label="Action probabilities"></div><p class="game-decision-meta" id="game-decision-meta"></p></div></div></div></div></div>
</section>
<noscript><p class="error">Enable JavaScript to use this live playground. You can still <a href="/">watch the recorded demos</a>.</p></noscript>
</main>
<footer><span><a href="https://autotrust.ai" target="_blank" rel="noopener noreferrer">autotrust.ai</a> <span aria-hidden="true"> / </span> Decisions into action.</span><div class="footer-links"><span id="footer-status">Connecting to model…</span><a href="/">Watch the demo reel ↗</a></div></footer>
<script>
'use strict';
const $=id=>document.getElementById(id);
const presets={support:{kind:'choice',state:'A customer was charged twice for a single subscription renewal. They included two transaction IDs for the same date and amount. Their account is active, and they need help correcting the duplicate charge.',question:'Which team should handle this ticket?',options:['Billing support','Technical support','Sales']},route:{kind:'choice',state:'A delivery must arrive within 25 minutes. Route A takes 18 minutes but crosses a bridge closed for repairs. Route B takes 23 minutes and is open. Route C takes 32 minutes and is open.',question:'Which route should the driver take?',options:['Route A','Route B','Route C']},fact:{kind:'noul',state:'The museum is open Tuesday through Sunday, from 10:00 to 18:00. It is closed every Monday. Today is Monday.',question:'Is the museum open today at 14:00?',options:['false','true']}};
let decisionBusy=false,customOptions='',modelName='autotrust/JEV-27B';
const MAX_IMAGE_BYTES=5*1024*1024,IMAGE_TYPES=new Set(['image/jpeg','image/png','image/webp']);
let attachedImage=null,imageLoading=false,imageLoadVersion=0,imageReader=null,imageDragDepth=0;
let sessionId=null,gameBusy=false,gameDone=false,autoPlaying=false,stopRequested=false,actions=[],gameModel=modelName;
const tabs=[$('games-tab'),$('lab-tab'),$('arena-tab')],tabHashes=['#games','#lab','#doom'];
const liveGamesFrame=$('live-games-frame');
function setTab(index,syncHash=true){if(index<0||index>=tabs.length)index=0;tabs.forEach((tab,i)=>{tab.setAttribute('aria-selected',String(i===index));tab.tabIndex=i===index?0:-1;$(tab.getAttribute('aria-controls')).hidden=i!==index});if(index!==2&&autoPlaying){stopRequested=true;$('game-status').textContent='Stopping after the current move…'}if(syncHash&&location.hash!==tabHashes[index])history.replaceState(null,'',location.pathname+location.search+tabHashes[index])}
tabs.forEach((tab,index)=>{tab.addEventListener('click',()=>setTab(index));tab.addEventListener('keydown',e=>{if(['ArrowLeft','ArrowRight','Home','End'].includes(e.key)){e.preventDefault();const next=e.key==='Home'?0:e.key==='End'?tabs.length-1:(index+(e.key==='ArrowRight'?1:-1)+tabs.length)%tabs.length;setTab(next);tabs[next].focus()}})});
function restoreTab(){setTab(Math.max(0,tabHashes.indexOf(location.hash)))}
window.addEventListener('hashchange',restoreTab);
window.addEventListener('message',event=>{if(event.origin!==location.origin||event.source!==liveGamesFrame.contentWindow)return;const data=event.data;if(!data||data.type!=='jev-arena-size'||typeof data.height!=='number'||!Number.isFinite(data.height))return;liveGamesFrame.style.height=Math.min(6000,Math.max(320,Math.ceil(data.height)))+'px'});
restoreTab();
function showError(id,message){$(id).textContent=message||'';$(id).hidden=!message}
function serviceMessage(status,detail){if(status===429)return 'The demo is busy. Please wait a moment, then try again.';if(status===404)return 'This session has expired. Start a new arena to continue.';if(status===409)return 'This episode has finished. Reset the arena to play again.';if(status===502||status===503||status===504)return 'The model service is temporarily unavailable. Please try again shortly.';if(status===400||status===422){if(typeof detail==='string'&&!/https?:|token|key|traceback/i.test(detail))return detail.slice(0,250);return 'The request could not be accepted. Check your inputs and try again.'}return 'The request did not complete. Please try again.'}
async function api(path,body,method){const controller=new AbortController(),timer=setTimeout(()=>controller.abort(),150000);try{const response=await fetch(path,{method:method||(body===undefined?'GET':'POST'),headers:body===undefined?{}:{'Content-Type':'application/json'},body:body===undefined?undefined:JSON.stringify(body),signal:controller.signal,cache:'no-store'});let data;try{data=await response.json()}catch{throw new Error('The service returned an unreadable response. Please try again.')}if(!response.ok){const error=new Error(serviceMessage(response.status,data.detail||data.error));error.status=response.status;throw error}return data}catch(error){if(error.name==='AbortError')throw new Error('This request took too long. Please try again shortly.');if(error instanceof TypeError)throw new Error('The demo could not reach the service. Check your connection and try again.');throw error}finally{clearTimeout(timer)}}
function connected(model){modelName=typeof model==='string'&&model?model:modelName;$('connection').dataset.status='ok';$('connection-text').textContent='Model connected';$('footer-status').textContent='Connected to JEV-27B'}
async function checkHealth(){try{const data=await api('/api/health');const online=data.status==='ok';$('connection').dataset.status=online?'ok':'unavailable';$('connection-text').textContent=online?'Model connected':'Model unavailable';$('footer-status').textContent=online?'Connected to JEV-27B':'Model temporarily unavailable';if(typeof data.model==='string'&&data.model){modelName=data.model;$('result-model').textContent=modelName}}catch{$('connection').dataset.status='unavailable';$('connection-text').textContent='Connection unavailable';$('footer-status').textContent='Connection unavailable'}}
function formatTime(seconds){const n=seconds;return typeof n==='number'&&Number.isFinite(n)&&n>=0?n<1?`${Math.round(n*1000)} ms`:`${n.toFixed(2)} s`:'—'}
function decisionData(data,fallbackOptions=[]){const optionList=Array.isArray(data.options)?data.options:fallbackOptions;const labels=optionList.map(option=>typeof option==='object'?String(option.label??option.id??''):String(option));let probabilities=data.probabilities;if(!Array.isArray(probabilities)&&probabilities&&typeof probabilities==='object')probabilities=optionList.map(option=>probabilities[typeof option==='object'?option.id:option]);if(!Array.isArray(probabilities))probabilities=[];const rawChoice=data.choice??data.answer??data.selected;let selectedIndex=-1;if(typeof rawChoice==='number'&&Number.isInteger(rawChoice)&&rawChoice>=0&&rawChoice<labels.length)selectedIndex=rawChoice;else selectedIndex=optionList.findIndex((option,index)=>String(typeof option==='object'?option.id:option)===String(rawChoice)||labels[index]===String(rawChoice));if(selectedIndex<0&&Number.isInteger(data.choice_index))selectedIndex=data.choice_index;const choiceLabel=selectedIndex>=0&&selectedIndex<labels.length?labels[selectedIndex]:rawChoice==null?'No decision returned':String(rawChoice);return {labels,probabilities,selectedIndex,choiceLabel}}
function renderBars(container,result){container.replaceChildren();result.labels.forEach((label,index)=>{const row=document.createElement('div');row.className='bar-row'+(index===result.selectedIndex?' selected':'');const top=document.createElement('div');top.className='bar-top';const name=document.createElement('span');name.className='bar-name';name.textContent=label;const value=document.createElement('span');value.className='bar-value';const p=Number(result.probabilities[index]),valid=Number.isFinite(p)&&p>=0&&p<=1;value.textContent=valid?`${(p*100).toFixed(1)}%`:'—';const track=document.createElement('div');track.className='bar-track';const fill=document.createElement('div');fill.className='bar-fill';fill.style.width=valid?`${p*100}%`:'0%';track.append(fill);top.append(name,value);row.append(top,track);container.append(row)})}
function renderVision(vision){const present=vision&&typeof vision==='object'&&!Array.isArray(vision);$('vision-details').hidden=!present;$('vision-latency').textContent=present?formatTime(vision.elapsed_seconds):'—';$('vision-model').textContent=present&&typeof vision.model==='string'?vision.model:''}
function resetDecision(){renderVision(null);showError('decision-error','');$('decision-result').hidden=true;$('empty-output').hidden=false;$('empty-output').classList.remove('loading');$('empty-title').textContent='Give a choice some context.';$('empty-copy').textContent='Run an example or write your own situation. JEV’s answer and option probabilities will appear here.';$('result-status').textContent='Ready when you are';$('result-latency').textContent='—'}
function updateKind(){const kind=$('kind').value;$('options').readOnly=kind!=='choice';$('options-label').textContent=kind==='choice'?'One per line':'Fixed choices';$('kind-help').textContent=kind==='choice'?'Compare the choices you provide.':kind==='noul'?'Judge whether the statement is true.':'Use your question to define what the score means.';if(kind==='noul')$('options').value='false\ntrue';else if(kind==='score')$('options').value='0\n1\n2\n3\n4\n5';else $('options').value=customOptions}

function syncImageControls(){
  $('image-drop').setAttribute('aria-disabled',String(decisionBusy));
  $('image-input').disabled=decisionBusy;
  $('image-input').hidden=Boolean(attachedImage);
  $('remove-image').disabled=decisionBusy;
  $('run-decision').disabled=decisionBusy||imageLoading;
  $('state').required=!attachedImage;
  $('state-hint').textContent=attachedImage?'Text context is optional':'Text or image context';
  $('decision-form').setAttribute('aria-busy',String(decisionBusy||imageLoading));
}
function clearImage(){
  imageLoadVersion++;
  if(imageReader?.readyState===1)imageReader.abort();
  imageReader=null;attachedImage=null;imageLoading=false;imageDragDepth=0;
  $('image-input').value='';
  $('image-thumbnail').removeAttribute('src');
  $('image-preview').hidden=true;$('image-picker').hidden=false;
  $('image-name').textContent='';$('image-meta').textContent='';$('image-status').textContent='';
  $('image-drop').classList.remove('is-dragging');showError('image-error','');syncImageControls();
}
function imageType(bytes){
  if(bytes.length>=8&&[137,80,78,71,13,10,26,10].every((n,i)=>bytes[i]===n))return 'image/png';
  if(bytes.length>=3&&bytes[0]===255&&bytes[1]===216&&bytes[2]===255)return 'image/jpeg';
  if(bytes.length>=12&&[82,73,70,70].every((n,i)=>bytes[i]===n)&&[87,69,66,80].every((n,i)=>bytes[i+8]===n))return 'image/webp';
  return '';
}
function readImageData(file){
  return new Promise((resolve,reject)=>{
    const reader=new FileReader();imageReader=reader;
    reader.onload=()=>typeof reader.result==='string'?resolve(reader.result):reject(new Error('This image could not be read. Choose the file again.'));
    reader.onerror=()=>reject(new Error('This image could not be read. Choose the file again.'));
    reader.onabort=()=>reject(new Error('Image selection was cancelled.'));
    reader.readAsDataURL(file);
  });
}
function decodeImage(dataUrl){
  return new Promise((resolve,reject)=>{
    const image=new Image();
    image.onload=()=>{
      if(image.naturalWidth>0&&image.naturalHeight>0)resolve({width:image.naturalWidth,height:image.naturalHeight});
      else reject(new Error('This image could not be decoded. Choose a valid JPEG, PNG, or WebP image.'));
    };
    image.onerror=()=>reject(new Error('This image could not be decoded. Choose a valid JPEG, PNG, or WebP image.'));
    image.src=dataUrl;
  });
}
async function selectImage(files){
  if(decisionBusy||!files||!files.length)return;
  clearImage();resetDecision();
  document.querySelectorAll('.preset').forEach(button=>button.setAttribute('aria-pressed','false'));
  const version=imageLoadVersion;
  imageLoading=true;$('image-status').textContent='Checking image…';syncImageControls();
  try{
    if(files.length!==1)throw new Error('Choose one image at a time. No image is attached.');
    const file=files[0];
    if(file.size>MAX_IMAGE_BYTES)throw new Error('This image is larger than 5 MiB. Choose a smaller image.');
    if(!file.size)throw new Error('This file is empty. Choose a valid JPEG, PNG, or WebP image.');
    if(file.type&&!IMAGE_TYPES.has(file.type))throw new Error('Unsupported image type. Choose a JPEG, PNG, or WebP image.');
    const mime=imageType(new Uint8Array(await file.slice(0,12).arrayBuffer()));
    if(version!==imageLoadVersion)return;
    if(!mime||(file.type&&file.type!==mime))throw new Error('The file content is not a matching JPEG, PNG, or WebP image. Choose another file.');
    const raw=await readImageData(file);
    if(version!==imageLoadVersion)return;
    const dataUrl=`data:${mime};base64,${raw.slice(raw.indexOf(',')+1)}`;
    const dimensions=await decodeImage(dataUrl);
    if(version!==imageLoadVersion)return;
    attachedImage={dataUrl,name:file.name,size:file.size,...dimensions};
    $('image-thumbnail').src=dataUrl;
    $('image-name').textContent=file.name;
    $('image-meta').textContent=`${dimensions.width} × ${dimensions.height} · ${file.size>=1024*1024?(file.size/(1024*1024)).toFixed(1)+' MiB':Math.max(1,Math.ceil(file.size/1024))+' KiB'}`;
    $('image-preview').hidden=false;$('image-picker').hidden=true;
    $('image-status').textContent='Image attached. You can add text for more context.';
  }catch(error){
    if(version!==imageLoadVersion)return;
    $('image-status').textContent='No image attached.';
    showError('image-error',error.message);
  }finally{
    if(version===imageLoadVersion){imageReader=null;imageLoading=false;syncImageControls()}
  }
}
$('image-input').addEventListener('change',event=>selectImage(Array.from(event.target.files||[])));
$('remove-image').addEventListener('click',()=>{if(decisionBusy)return;clearImage();resetDecision();$('image-status').textContent='Image removed.';$('image-input').focus()});
$('image-drop').addEventListener('dragenter',event=>{event.preventDefault();if(decisionBusy)return;imageDragDepth++;$('image-drop').classList.add('is-dragging')});
$('image-drop').addEventListener('dragover',event=>{event.preventDefault();if(event.dataTransfer)event.dataTransfer.dropEffect=decisionBusy?'none':'copy'});
$('image-drop').addEventListener('dragleave',event=>{event.preventDefault();imageDragDepth=Math.max(0,imageDragDepth-1);if(!imageDragDepth)$('image-drop').classList.remove('is-dragging')});
$('image-drop').addEventListener('drop',event=>{event.preventDefault();imageDragDepth=0;$('image-drop').classList.remove('is-dragging');if(decisionBusy)return;const files=Array.from(event.dataTransfer?.files||[]);if(files.length)selectImage(files);else{clearImage();resetDecision();$('image-status').textContent='No image attached.';showError('image-error','Drop an image file from your device, or use Choose an image.')}});
function applyPreset(name){if(decisionBusy)return;clearImage();const p=presets[name];$('state').value=p.state;$('question').value=p.question;$('kind').value=p.kind;if(p.kind==='choice')customOptions=p.options.join('\n');updateKind();document.querySelectorAll('.preset').forEach(button=>button.setAttribute('aria-pressed',String(button.dataset.preset===name)));resetDecision()}
document.querySelectorAll('.preset').forEach(button=>button.addEventListener('click',()=>applyPreset(button.dataset.preset)));
$('kind').addEventListener('change',()=>{updateKind();resetDecision()});$('options').addEventListener('input',()=>{if($('kind').value==='choice')customOptions=$('options').value});['state','question','options','kind'].forEach(id=>$(id).addEventListener('input',()=>document.querySelectorAll('.preset').forEach(button=>button.setAttribute('aria-pressed','false'))));
function setDecisionBusy(busy){decisionBusy=busy;document.querySelectorAll('#decision-form button,#decision-form input,#decision-form textarea,#decision-form select').forEach(element=>element.disabled=busy);$('run-decision').textContent=busy?'JEV is deciding…':'Run JEV →';syncImageControls()}
$('decision-form').addEventListener('submit',async event=>{event.preventDefault();if(decisionBusy||imageLoading)return;const kind=$('kind').value,state=$('state').value.trim(),question=$('question').value.trim(),options=$('options').value.split('\n').map(option=>option.trim()).filter(Boolean);if(!question){showError('decision-error','Add a question for JEV first.');return}if(!state&&!attachedImage){showError('decision-error','Describe a situation or attach an image first.');return}if(options.length<2||options.length>16){showError('decision-error','Provide between 2 and 16 options, one per line.');return}if(options.some(option=>option.length>180)){showError('decision-error','Keep each option to 180 characters or fewer.');return}if(new Set(options).size!==options.length){showError('decision-error','Give each option a different name.');return}showError('decision-error','');setDecisionBusy(true);$('decision-result').hidden=true;$('empty-output').hidden=false;$('empty-output').classList.add('loading');$('empty-title').textContent='Weighing the options.';$('empty-copy').textContent=attachedImage?'Reading your image, then JEV will compare the available choices.':'JEV is reading your situation and comparing the available choices.';$('result-status').textContent='Inference in progress';$('result-latency').textContent='—';try{const payload={kind,state,question,options};if(attachedImage)payload.image=attachedImage.dataUrl;const data=await api('/api/decide',payload);const result=decisionData(data,options);if(!result.labels.length||result.choiceLabel==='No decision returned')throw new Error('The model did not return a valid decision. Please try again.');connected(data.model);renderVision(data.vision);$('picked-answer').textContent=result.choiceLabel;renderBars($('probabilities'),result);$('empty-output').hidden=true;$('decision-result').hidden=false;$('result-status').textContent='Decision received';$('result-latency').textContent=formatTime(data.elapsed_seconds);$('result-model').textContent=typeof data.model==='string'?data.model:modelName}catch(error){showError('decision-error',error.message);$('empty-title').textContent='The decision did not arrive.';$('empty-copy').textContent='Your inputs are still here. Try again when the service is ready.';$('result-status').textContent='Request unsuccessful';checkHealth()}finally{$('empty-output').classList.remove('loading');setDecisionBusy(false)}});
function updateGameControls(){const disabled=gameBusy||autoPlaying||!sessionId||gameDone;$('new-game').disabled=gameBusy||autoPlaying;$('scenario').disabled=gameBusy||autoPlaying;$('jev-move').disabled=disabled;$('auto-play').disabled=disabled;document.querySelectorAll('#manual-actions button').forEach(button=>button.disabled=disabled);$('stop-auto').hidden=!autoPlaying;$('stop-auto').disabled=stopRequested;$('auto-play').textContent=autoPlaying?'Auto-play running…':'Auto-play · 10 steps';$('new-game').textContent=sessionId?'Reset arena ↻':'Start arena ▷'}
function renderActions(nextActions){if(!Array.isArray(nextActions))return;actions=nextActions;$('manual-actions').replaceChildren();for(const action of actions){if(!action||typeof action.id!=='string')continue;const button=document.createElement('button');button.type='button';button.className='action-button';button.textContent=String(action.label||action.id);button.title=String(action.description||action.label||action.id);button.addEventListener('click',()=>runGameStep('human',action.id));$('manual-actions').append(button)}$('no-actions').hidden=actions.length>0;updateGameControls()}
function updateSnapshot(data){if(typeof data.session_id==='string')sessionId=data.session_id;gameDone=Boolean(data.done);if(typeof data.frame==='string'&&data.frame){const frame=data.frame.startsWith('data:image/png;base64,')?data.frame:`data:image/png;base64,${data.frame}`;$('game-frame').src=frame;$('game-frame').hidden=false;$('game-placeholder').hidden=true;$('game-frame').alt=`Doom ${data.scenario==='defend_the_center'?'center defense':'target practice'}; step ${data.metrics?.steps??'unknown'}`}if(data.actions)renderActions(data.actions);for(const key of ['health','ammo','kills','reward','steps']){const value=data.metrics?.[key];$('metric-'+key).textContent=typeof value==='number'&&Number.isFinite(value)?Number.isInteger(value)?String(value):value.toFixed(1):'—'}$('frame-badge').hidden=!sessionId;$('frame-badge').textContent=gameDone?'Episode complete':'Simulation paused';if(data.decision){const decision=data.decision;connected(decision.model);const results=decisionData(decision,actions.map(action=>action.id));const labels=results.labels.map(label=>actions.find(action=>action.id===label||action.description===label)?.label||label);const selectedId=results.choiceLabel;results.labels=labels;results.choiceLabel=actions.find(action=>action.id===selectedId||action.description===selectedId)?.label||selectedId;$('game-picked').textContent=results.choiceLabel;renderBars($('game-probabilities'),results);$('game-decision-empty').hidden=true;$('game-decision').hidden=false;gameModel=typeof decision.model==='string'?decision.model:typeof data.model==='string'?data.model:modelName;$('game-latency').textContent=formatTime(decision.elapsed_seconds);$('game-decision-meta').textContent=`${gameModel} · Timing reported by the model server`;}updateGameControls()}
$('new-game').addEventListener('click',async()=>{if(gameBusy||autoPlaying)return;gameBusy=true;showError('game-error','');updateGameControls();$('game-status').textContent='Creating your arena…';try{if(sessionId){try{await api(`/api/games/${encodeURIComponent(sessionId)}`,undefined,'DELETE')}catch(error){if(error.status!==404)throw error}sessionId=null;gameDone=false;renderActions([]);$('game-frame').hidden=true;$('game-placeholder').hidden=false;$('frame-badge').hidden=true;for(const key of ['health','ammo','kills','reward','steps'])$('metric-'+key).textContent='—';$('game-decision').hidden=true;$('game-decision-empty').hidden=false}const data=await api('/api/games/new',{scenario:$('scenario').value});if(!data.session_id)throw new Error('The arena did not start. Please try again.');updateSnapshot(data);$('game-decision').hidden=true;$('game-decision-empty').hidden=false;$('game-status').textContent='Arena ready. Choose a move or let JEV decide.'}catch(error){showError('game-error',error.message);$('game-status').textContent='Could not create an arena.'}finally{gameBusy=false;updateGameControls()}});
async function runGameStep(controller,action,fromAuto=false){if(!sessionId||gameDone||gameBusy||(autoPlaying&&!fromAuto))return false;gameBusy=true;showError('game-error','');updateGameControls();if(!fromAuto)$('game-status').textContent=controller==='jev'?'JEV is choosing a move…':'Applying your move…';$('frame-badge').textContent=controller==='jev'?'JEV is deciding…':'Applying move…';try{const body={controller};if(action)body.action=action;const data=await api(`/api/games/${encodeURIComponent(sessionId)}/step`,body);updateSnapshot(data);if(gameDone)$('game-status').textContent='Episode complete. Reset the arena to play again.';else if(!fromAuto)$('game-status').textContent=controller==='jev'?'JEV’s move is complete. Your turn to choose.':'Move complete. The simulation is paused.';return true}catch(error){showError('game-error',error.message);$('game-status').textContent='Move unsuccessful. Try again or reset the arena.';if(error.status===404){sessionId=null;gameDone=true;$('frame-badge').textContent='Session expired'}else if(error.status===409){gameDone=true;$('frame-badge').textContent='Episode complete'}else $('frame-badge').textContent='Simulation paused';return false}finally{gameBusy=false;updateGameControls()}}
$('jev-move').addEventListener('click',()=>runGameStep('jev'));
$('auto-play').addEventListener('click',async()=>{if(autoPlaying||gameBusy||!sessionId||gameDone)return;autoPlaying=true;stopRequested=false;updateGameControls();let completed=0,failed=false;try{for(let i=0;i<10;i++){if(stopRequested||gameDone)break;$('game-status').textContent=`Auto-play: JEV is choosing move ${i+1} of 10…`;const success=await runGameStep('jev',undefined,true);if(!success){failed=true;break}completed++;await new Promise(resolve=>setTimeout(resolve,200))}}finally{autoPlaying=false;updateGameControls();if(gameDone)$('game-status').textContent=`Episode complete after ${completed} auto-play ${completed===1?'move':'moves'}.`;else if(!failed)$('game-status').textContent=`Auto-play ${stopRequested?'stopped':'complete'} · ${completed} ${completed===1?'move':'moves'}. The simulation is paused.`;stopRequested=false}});
$('stop-auto').addEventListener('click',()=>{stopRequested=true;$('game-status').textContent='Stopping after the current move…';updateGameControls()});
window.addEventListener('pagehide',()=>{if(sessionId)fetch(`/api/games/${encodeURIComponent(sessionId)}`,{method:'DELETE',keepalive:true}).catch(()=>{})});
applyPreset('support');checkHealth();
</script>
</body>
</html>