pokertools-arena 0.17.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2 -0
- package/README.md +8 -1
- package/bin/pokertools-arena.mjs +1 -23
- package/dist/app.js +233 -240
- package/dist/index.html +10 -10
- package/dist/pokertools-arena.html +242 -249
- package/dist/styles.css +1 -1
- package/docs/diagnostics/SUMMARY.md +3 -5
- package/docs/diagnostics/summary.json +1 -1
- package/docs/releases/RELEASE_NOTES.md +15 -0
- package/package.json +1 -1
- package/src/app.js +15 -18
- package/src/benchmark/scenarios.js +4 -10
- package/src/index.html +10 -10
- package/src/styles.css +1 -1
package/.env.example
CHANGED
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| **Package** | `npx pokertools-arena` |
|
|
18
18
|
| **Runtime** | Node.js ≥ 24 for tooling; any modern browser for the app |
|
|
19
19
|
|
|
20
|
-
> **Current release: 0.
|
|
20
|
+
> **Current release: 0.18.0.** This maintenance release centralizes environment parsing and benchmark constants, makes release artifacts reproducible, fixes archive and cache-version drift, and improves keyboard and screen-reader behavior.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -83,6 +83,9 @@ The CLI starts a tiny local static server and opens the browser. It exists only
|
|
|
83
83
|
| `--chrome` | off | Open Google Chrome specifically. |
|
|
84
84
|
| `--no-open` | off | Do not launch a browser. |
|
|
85
85
|
| `--no-env` | off | Ignore `.env` bootstrap. |
|
|
86
|
+
| `--autostart` | off | Start the configured tournament after launch. |
|
|
87
|
+
| `--starting-stack <n>` | `3000` | Override the chip stack for `.env` seats. |
|
|
88
|
+
| `--max-decisions <n>` | unlimited | Stop after this many model decisions. |
|
|
86
89
|
| `--help`, `-h` | — | Print usage. |
|
|
87
90
|
|
|
88
91
|
### Option B — single HTML file
|
|
@@ -120,6 +123,8 @@ OPENAI_PLAYER1=google/gemma-4-26b-a4b-it
|
|
|
120
123
|
OPENAI_PLAYER2=qwen/qwen3.8-flash
|
|
121
124
|
OPENAI_PLAYER3=typesafe/jev-1.13
|
|
122
125
|
OPENAI_STARTING_STACK=3000
|
|
126
|
+
OPENAI_AUTOSTART=0
|
|
127
|
+
OPENAI_MAX_DECISIONS=0
|
|
123
128
|
```
|
|
124
129
|
|
|
125
130
|
| Variable | Notes |
|
|
@@ -128,6 +133,8 @@ OPENAI_STARTING_STACK=3000
|
|
|
128
133
|
| `OPENAI_API_KEY` | Optional for local/unauthenticated endpoints. |
|
|
129
134
|
| `OPENAI_PLAYER1` … `OPENAI_PLAYER10` | Each value seats one model; Jev models auto-route to Decisions on OpenRouter. |
|
|
130
135
|
| `OPENAI_STARTING_STACK` | Chip stack per seat (minimum 100, default 3000). |
|
|
136
|
+
| `OPENAI_AUTOSTART` | Set to `1`, `true`, `yes`, or `on` to start automatically. |
|
|
137
|
+
| `OPENAI_MAX_DECISIONS` | Optional tournament decision limit (`0` means unlimited). |
|
|
131
138
|
|
|
132
139
|
See [`.env.example`](./.env.example).
|
|
133
140
|
|
package/bin/pokertools-arena.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { readFile, stat } from 'node:fs/promises';
|
|
|
4
4
|
import { extname, dirname, join, normalize, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
|
+
import { parseDotEnv } from '../tools/shared/dotenv.mjs';
|
|
7
8
|
|
|
8
9
|
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
10
|
const root = join(packageRoot, 'dist');
|
|
@@ -25,29 +26,6 @@ const envOverrides = {
|
|
|
25
26
|
OPENAI_AUTOSTART: args.includes('--autostart') ? '1' : null,
|
|
26
27
|
};
|
|
27
28
|
|
|
28
|
-
function parseDotEnv(text) {
|
|
29
|
-
const out = {};
|
|
30
|
-
for (const rawLine of String(text || '').split(/\r?\n/)) {
|
|
31
|
-
let line = rawLine.trim();
|
|
32
|
-
if (!line || line.startsWith('#')) continue;
|
|
33
|
-
if (line.startsWith('export ')) line = line.slice(7).trim();
|
|
34
|
-
const eq = line.indexOf('=');
|
|
35
|
-
if (eq <= 0) continue;
|
|
36
|
-
const key = line.slice(0, eq).trim();
|
|
37
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
|
|
38
|
-
let value = line.slice(eq + 1).trim();
|
|
39
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
40
|
-
const quote = value[0];
|
|
41
|
-
value = value.slice(1, -1);
|
|
42
|
-
if (quote === '"') value = value.replace(/\n/g, '\n').replace(/\r/g, '\r').replace(/\t/g, '\t').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
43
|
-
} else {
|
|
44
|
-
value = value.replace(/\s+#.*$/, '').trim();
|
|
45
|
-
}
|
|
46
|
-
out[key] = value;
|
|
47
|
-
}
|
|
48
|
-
return out;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
29
|
async function loadArenaEnv() {
|
|
52
30
|
if (!useEnv) return { source: null, values: {} };
|
|
53
31
|
const candidates = [resolve(process.cwd(), '.env'), join(packageRoot, '.env')];
|
package/dist/app.js
CHANGED
|
@@ -114000,237 +114000,6 @@ var require_browser = __commonJS({
|
|
|
114000
114000
|
// src/app.js
|
|
114001
114001
|
var import_browser = __toESM(require_browser(), 1);
|
|
114002
114002
|
|
|
114003
|
-
// src/benchmark/scenarios.js
|
|
114004
|
-
var POLICY = Object.freeze({
|
|
114005
|
-
private: "hero hole cards only",
|
|
114006
|
-
public: "board, pot, blinds, stacks, positions, current-hand actions, recent public hand history, public player statistics, and legal actions",
|
|
114007
|
-
excluded: "opponent hole cards, other agents reasoning, model outputs, API/provider metadata, hidden deck state, and future cards"
|
|
114008
|
-
});
|
|
114009
|
-
var OBJECTIVE = "Choose exactly one legal action that best maximizes tournament chip EV from the supplied state. Use only the information in this state and only an actionId present in legalActions.";
|
|
114010
|
-
function action(id2, type, description, amount = null) {
|
|
114011
|
-
return { id: id2, type, amount, description };
|
|
114012
|
-
}
|
|
114013
|
-
function withBetting(state) {
|
|
114014
|
-
const heroBet = Number(state.hero?.currentBet) || 0;
|
|
114015
|
-
const highestBet = Math.max(heroBet, ...(state.opponents ?? []).map((o) => Number(o.currentBet) || 0));
|
|
114016
|
-
const toCall = Math.max(0, highestBet - heroBet);
|
|
114017
|
-
const stack = Number(state.hero?.stack) || 0;
|
|
114018
|
-
return {
|
|
114019
|
-
...state,
|
|
114020
|
-
betting: {
|
|
114021
|
-
highestBet,
|
|
114022
|
-
heroCurrentBet: heroBet,
|
|
114023
|
-
toCall,
|
|
114024
|
-
effectiveCall: Math.min(stack, toCall),
|
|
114025
|
-
stackBehind: stack,
|
|
114026
|
-
facingAllInCall: stack > 0 && stack <= toCall
|
|
114027
|
-
}
|
|
114028
|
-
};
|
|
114029
|
-
}
|
|
114030
|
-
function baseState(overrides = {}) {
|
|
114031
|
-
return withBetting({
|
|
114032
|
-
contextVersion: 3,
|
|
114033
|
-
informationPolicy: POLICY,
|
|
114034
|
-
objective: OBJECTIVE,
|
|
114035
|
-
game: "No-Limit Texas Holdem tournament",
|
|
114036
|
-
memoryPolicy: {
|
|
114037
|
-
currentHand: "all public model actions in the current hand before this decision",
|
|
114038
|
-
recentHands: "last 8 completed public hands",
|
|
114039
|
-
publicPlayerStats: "deterministic aggregates from completed hands before the current hand; identical public dataset for every seat"
|
|
114040
|
-
},
|
|
114041
|
-
tournament: { handNumber: 50, blindLevel: 4, playersRemaining: 2, startingPlayers: 2 },
|
|
114042
|
-
blinds: { smallBlind: 50, bigBlind: 100, ante: 0 },
|
|
114043
|
-
hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BTN/SB", stack: 1e4, stackBB: 100, cards: ["As", "Kd"], currentBet: 0 },
|
|
114044
|
-
board: [],
|
|
114045
|
-
street: "PREFLOP",
|
|
114046
|
-
pot: 150,
|
|
114047
|
-
buttonSeat: 1,
|
|
114048
|
-
actionHistory: [],
|
|
114049
|
-
recentHands: [],
|
|
114050
|
-
publicPlayerStats: [
|
|
114051
|
-
{ playerId: "benchmark-hero", playerName: "Benchmark Hero", sampleHands: 40, vpipPct: 0.3, pfrPct: 0.22, aggressionPct: 0.41, foldPct: 0.28, callPct: 0.24, checkPct: 0.18, wins: 10 },
|
|
114052
|
-
{ playerId: "benchmark-villain", playerName: "Benchmark Villain", sampleHands: 40, vpipPct: 0.31, pfrPct: 0.21, aggressionPct: 0.4, foldPct: 0.29, callPct: 0.24, checkPct: 0.17, wins: 9 }
|
|
114053
|
-
],
|
|
114054
|
-
opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 1e4, stackBB: 100, currentBet: 100, status: "ACTIVE", cards: [] }],
|
|
114055
|
-
legalActions: [],
|
|
114056
|
-
...overrides
|
|
114057
|
-
});
|
|
114058
|
-
}
|
|
114059
|
-
var DECISION_SANITY_SCENARIOS = Object.freeze([
|
|
114060
|
-
{
|
|
114061
|
-
id: "free-check-dominates-fold",
|
|
114062
|
-
title: "Free check dominates folding",
|
|
114063
|
-
category: "Dominance",
|
|
114064
|
-
expectedTypes: ["CHECK"],
|
|
114065
|
-
note: "With no bet to call, checking preserves every future outcome while folding gives up immediately.",
|
|
114066
|
-
state: baseState({ street: "FLOP", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 5e3, stackBB: 50, cards: ["7c", "2d"], currentBet: 0 }, board: ["As", "Kd", "Qh"], pot: 300, opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 5e3, stackBB: 50, currentBet: 0, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CHECK", "Check")] })
|
|
114067
|
-
},
|
|
114068
|
-
{
|
|
114069
|
-
id: "board-royal-call",
|
|
114070
|
-
title: "Board royal flush: call a small bet",
|
|
114071
|
-
category: "Guaranteed tie",
|
|
114072
|
-
expectedTypes: ["CALL"],
|
|
114073
|
-
note: "The board itself is a royal flush, so folding gives up a guaranteed share of the pot.",
|
|
114074
|
-
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 4900, stackBB: 49, cards: ["2c", "3d"], currentBet: 0 }, board: ["As", "Ks", "Qs", "Js", "Ts"], pot: 1100, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 100, description: "Bet 100 (1 BB)" }, potBefore: 1e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 4900, stackBB: 49, currentBet: 100, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 100")] })
|
|
114075
|
-
},
|
|
114076
|
-
{
|
|
114077
|
-
id: "royal-nuts-call",
|
|
114078
|
-
title: "Royal flush facing a shove",
|
|
114079
|
-
category: "Nuts",
|
|
114080
|
-
expectedTypes: ["CALL"],
|
|
114081
|
-
note: "Hero holds the unbeatable royal flush and must not fold to an all-in bet.",
|
|
114082
|
-
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 4e3, stackBB: 40, cards: ["As", "Ks"], currentBet: 0 }, board: ["Qs", "Js", "Ts", "2d", "3c"], pot: 6e3, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 4e3, description: "All-in 4000 (40 BB)" }, potBefore: 2e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 0, stackBB: 0, currentBet: 4e3, status: "ALL_IN", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 4000")] })
|
|
114083
|
-
},
|
|
114084
|
-
{
|
|
114085
|
-
id: "straight-flush-call",
|
|
114086
|
-
title: "Straight flush facing a river bet",
|
|
114087
|
-
category: "Nuts",
|
|
114088
|
-
expectedTypes: ["CALL"],
|
|
114089
|
-
note: "Hero has a nine-high straight flush; folding to the available call is an obvious sanity failure.",
|
|
114090
|
-
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 7e3, stackBB: 70, cards: ["9s", "8s"], currentBet: 0 }, board: ["7s", "6s", "5s", "Kd", "2c"], pot: 3500, opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 5500, stackBB: 55, currentBet: 1500, status: "ACTIVE", cards: [] }], actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 1500, description: "Bet 1500 (15 BB)" }, potBefore: 2e3 }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 1500")] })
|
|
114091
|
-
},
|
|
114092
|
-
{
|
|
114093
|
-
id: "aces-vs-short-shove",
|
|
114094
|
-
title: "Pocket aces versus a short-stack shove",
|
|
114095
|
-
category: "Preflop",
|
|
114096
|
-
expectedTypes: ["CALL"],
|
|
114097
|
-
note: "With only fold or call available, folding AA to a short all-in is a basic strategic failure.",
|
|
114098
|
-
state: baseState({ hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9600, stackBB: 96, cards: ["As", "Ah"], currentBet: 100 }, pot: 2550, actionHistory: [{ street: "PREFLOP", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "RAISE", amount: 2500, description: "All-in to 2500 (25 BB)" }, potBefore: 150 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 0, stackBB: 0, currentBet: 2500, status: "ALL_IN", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 2400")] })
|
|
114099
|
-
},
|
|
114100
|
-
{
|
|
114101
|
-
id: "seven-deuce-vs-shove",
|
|
114102
|
-
title: "Seven-deuce offsuit versus a 100 BB shove",
|
|
114103
|
-
category: "Preflop",
|
|
114104
|
-
expectedTypes: ["FOLD"],
|
|
114105
|
-
note: "The weakest starting hand facing an enormous shove with little dead money should fold.",
|
|
114106
|
-
state: baseState({ hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9900, stackBB: 99, cards: ["7c", "2d"], currentBet: 100 }, pot: 10150, actionHistory: [{ street: "PREFLOP", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "RAISE", amount: 1e4, description: "All-in to 10000 (100 BB)" }, potBefore: 150 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 0, stackBB: 0, currentBet: 1e4, status: "ALL_IN", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 9900")] })
|
|
114107
|
-
},
|
|
114108
|
-
{
|
|
114109
|
-
id: "river-air-overbet",
|
|
114110
|
-
title: "Seven-high versus a 10\xD7 pot river shove",
|
|
114111
|
-
category: "River discipline",
|
|
114112
|
-
expectedTypes: ["FOLD"],
|
|
114113
|
-
note: "Hero has seven-high with no meaningful showdown value and faces an extreme overbet.",
|
|
114114
|
-
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 1e4, stackBB: 100, cards: ["7d", "2d"], currentBet: 0 }, board: ["Ac", "Kh", "Qh", "9s", "3c"], pot: 1100, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 1e4, description: "All-in 10000 (100 BB)" }, potBefore: 1e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 0, stackBB: 0, currentBet: 1e4, status: "ALL_IN", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 10000")] })
|
|
114115
|
-
},
|
|
114116
|
-
{
|
|
114117
|
-
id: "nut-flush-tiny-bet",
|
|
114118
|
-
title: "Nut flush versus a tiny river bet",
|
|
114119
|
-
category: "Pot odds",
|
|
114120
|
-
expectedTypes: ["CALL"],
|
|
114121
|
-
note: "Hero has the nut flush and faces a tiny bet; folding is an obvious sanity failure.",
|
|
114122
|
-
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9900, stackBB: 99, cards: ["As", "8s"], currentBet: 0 }, board: ["Ks", "Qs", "4s", "2h", "3d"], pot: 1100, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 100, description: "Bet 100 (1 BB)" }, potBefore: 1e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 9900, stackBB: 99, currentBet: 100, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 100")] })
|
|
114123
|
-
},
|
|
114124
|
-
{
|
|
114125
|
-
id: "kings-vs-open",
|
|
114126
|
-
title: "Pocket kings versus a normal open",
|
|
114127
|
-
category: "Preflop",
|
|
114128
|
-
expectedTypes: ["CALL", "RAISE"],
|
|
114129
|
-
note: "Continuing with KK is expected; the suite accepts either calling or raising and rejects folding.",
|
|
114130
|
-
state: baseState({ hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9900, stackBB: 99, cards: ["Kc", "Kd"], currentBet: 100 }, pot: 450, actionHistory: [{ street: "PREFLOP", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "RAISE", amount: 300, description: "Raise to 300 (3 BB)" }, potBefore: 150 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 9700, stackBB: 97, currentBet: 300, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 200"), action("A2", "RAISE", "Raise to 900 (9 BB)", 900), action("A3", "RAISE", "Raise to 1200 (12 BB)", 1200)] })
|
|
114131
|
-
},
|
|
114132
|
-
{
|
|
114133
|
-
id: "top-pair-tiny-river-bet",
|
|
114134
|
-
title: "Top pair versus a 5% pot river bet",
|
|
114135
|
-
category: "Pot odds",
|
|
114136
|
-
expectedTypes: ["CALL"],
|
|
114137
|
-
note: "Top pair facing a tiny river bet gets overwhelming pot odds; folding is a basic sanity failure.",
|
|
114138
|
-
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9950, stackBB: 99.5, cards: ["Ah", "Jd"], currentBet: 0 }, board: ["As", "8c", "6d", "4h", "2c"], pot: 1050, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 50, description: "Bet 50 (0.5 BB)" }, potBefore: 1e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 9950, stackBB: 99.5, currentBet: 50, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 50")] })
|
|
114139
|
-
}
|
|
114140
|
-
]);
|
|
114141
|
-
|
|
114142
|
-
// src/config/arena-config.js
|
|
114143
|
-
var BASE_CONFIG = {
|
|
114144
|
-
table: {
|
|
114145
|
-
minPlayers: 2,
|
|
114146
|
-
maxPlayers: 10,
|
|
114147
|
-
startingStack: 3e3,
|
|
114148
|
-
smallBlind: 25,
|
|
114149
|
-
bigBlind: 50,
|
|
114150
|
-
ante: 5,
|
|
114151
|
-
handsPerLevel: 8,
|
|
114152
|
-
blindMultiplier: 2
|
|
114153
|
-
},
|
|
114154
|
-
timing: {
|
|
114155
|
-
actionSeconds: 30,
|
|
114156
|
-
timeBankSeconds: 30,
|
|
114157
|
-
lowTimeSeconds: 5,
|
|
114158
|
-
lowTimeFraction: 0.25,
|
|
114159
|
-
betweenActionsMs: 250,
|
|
114160
|
-
betweenHandsMs: 2e3
|
|
114161
|
-
},
|
|
114162
|
-
limits: {
|
|
114163
|
-
startingStack: { min: 100 },
|
|
114164
|
-
smallBlind: { min: 1 },
|
|
114165
|
-
bigBlind: { min: 2 },
|
|
114166
|
-
ante: { min: 0 },
|
|
114167
|
-
handsPerLevel: { min: 1 },
|
|
114168
|
-
blindMultiplier: { min: 1.1, max: 3, step: 0.1 },
|
|
114169
|
-
actionSeconds: { min: 1, max: 120 },
|
|
114170
|
-
timeBankSeconds: { min: 0, max: 600 },
|
|
114171
|
-
lowTimeSeconds: { min: 0, max: 600 },
|
|
114172
|
-
lowTimeFraction: { min: 0, max: 1, step: 0.05 },
|
|
114173
|
-
betweenActionsMs: { min: 0, max: 5e3 },
|
|
114174
|
-
betweenHandsMs: { min: 0, max: 1e4 }
|
|
114175
|
-
},
|
|
114176
|
-
connections: {
|
|
114177
|
-
defaultKind: "openai",
|
|
114178
|
-
siteUrl: "https://pokertools-arena.github.io/",
|
|
114179
|
-
siteName: "pokertools-arena",
|
|
114180
|
-
presets: {
|
|
114181
|
-
openai: "https://api.openai.com/v1",
|
|
114182
|
-
openrouter: "https://openrouter.ai/api/v1",
|
|
114183
|
-
typesafe: "https://api.typesafe.ai/v1/systemone"
|
|
114184
|
-
}
|
|
114185
|
-
},
|
|
114186
|
-
history: {
|
|
114187
|
-
recentDecisions: 12,
|
|
114188
|
-
snapshotEvents: 300,
|
|
114189
|
-
persistedEvents: 100
|
|
114190
|
-
}
|
|
114191
|
-
};
|
|
114192
|
-
function mergeConfig(base, override) {
|
|
114193
|
-
if (!override || typeof override !== "object" || Array.isArray(override)) return { ...base };
|
|
114194
|
-
const merged = { ...base };
|
|
114195
|
-
for (const [key, value] of Object.entries(override)) {
|
|
114196
|
-
if (value && typeof value === "object" && !Array.isArray(value) && base[key] && typeof base[key] === "object") {
|
|
114197
|
-
merged[key] = mergeConfig(base[key], value);
|
|
114198
|
-
} else if (value !== void 0) {
|
|
114199
|
-
merged[key] = value;
|
|
114200
|
-
}
|
|
114201
|
-
}
|
|
114202
|
-
return merged;
|
|
114203
|
-
}
|
|
114204
|
-
var ARENA_CONFIG = Object.freeze(mergeConfig(BASE_CONFIG, globalThis.__POKERTOOLS_ARENA_CONFIG__));
|
|
114205
|
-
var TABLE_DEFAULTS = Object.freeze(ARENA_CONFIG.table);
|
|
114206
|
-
var TIMING_DEFAULTS = Object.freeze(ARENA_CONFIG.timing);
|
|
114207
|
-
var CONFIG_LIMITS = Object.freeze(ARENA_CONFIG.limits);
|
|
114208
|
-
var CONNECTION_PRESETS = Object.freeze(ARENA_CONFIG.connections.presets);
|
|
114209
|
-
var HISTORY_CONFIG = Object.freeze(ARENA_CONFIG.history);
|
|
114210
|
-
var FORM_DEFAULTS = Object.freeze({ ...TABLE_DEFAULTS, ...TIMING_DEFAULTS });
|
|
114211
|
-
function applyConfiguredFormDefaults(form) {
|
|
114212
|
-
for (const [name, value] of Object.entries(FORM_DEFAULTS)) {
|
|
114213
|
-
const input = form?.elements?.namedItem(name);
|
|
114214
|
-
if (!input) continue;
|
|
114215
|
-
input.value = String(value);
|
|
114216
|
-
const limits = CONFIG_LIMITS[name] || {};
|
|
114217
|
-
for (const attribute of ["min", "max", "step"]) {
|
|
114218
|
-
if (limits[attribute] != null) input.setAttribute(attribute, String(limits[attribute]));
|
|
114219
|
-
}
|
|
114220
|
-
}
|
|
114221
|
-
}
|
|
114222
|
-
function defaultConnections() {
|
|
114223
|
-
const kind = ARENA_CONFIG.connections.defaultKind;
|
|
114224
|
-
return [{ name: "API", kind, baseUrl: CONNECTION_PRESETS[kind], apiKey: "", headers: defaultExtraHeaders(kind) }];
|
|
114225
|
-
}
|
|
114226
|
-
function defaultExtraHeaders(kind) {
|
|
114227
|
-
if (kind !== "openrouter") return "";
|
|
114228
|
-
return JSON.stringify({
|
|
114229
|
-
"HTTP-Referer": ARENA_CONFIG.connections.siteUrl,
|
|
114230
|
-
"X-OpenRouter-Title": ARENA_CONFIG.connections.siteName
|
|
114231
|
-
}, null, 2);
|
|
114232
|
-
}
|
|
114233
|
-
|
|
114234
114003
|
// src/lib/decision-core.js
|
|
114235
114004
|
var import_evaluator = __toESM(require_dist2(), 1);
|
|
114236
114005
|
var ACTION = Object.freeze({
|
|
@@ -115794,6 +115563,231 @@ async function decideHierarchical({
|
|
|
115794
115563
|
}
|
|
115795
115564
|
}
|
|
115796
115565
|
|
|
115566
|
+
// src/benchmark/scenarios.js
|
|
115567
|
+
function action(id2, type, description, amount = null) {
|
|
115568
|
+
return { id: id2, type, amount, description };
|
|
115569
|
+
}
|
|
115570
|
+
function withBetting(state) {
|
|
115571
|
+
const heroBet = Number(state.hero?.currentBet) || 0;
|
|
115572
|
+
const highestBet = Math.max(heroBet, ...(state.opponents ?? []).map((o) => Number(o.currentBet) || 0));
|
|
115573
|
+
const toCall = Math.max(0, highestBet - heroBet);
|
|
115574
|
+
const stack = Number(state.hero?.stack) || 0;
|
|
115575
|
+
return {
|
|
115576
|
+
...state,
|
|
115577
|
+
betting: {
|
|
115578
|
+
highestBet,
|
|
115579
|
+
heroCurrentBet: heroBet,
|
|
115580
|
+
toCall,
|
|
115581
|
+
effectiveCall: Math.min(stack, toCall),
|
|
115582
|
+
stackBehind: stack,
|
|
115583
|
+
facingAllInCall: stack > 0 && stack <= toCall
|
|
115584
|
+
}
|
|
115585
|
+
};
|
|
115586
|
+
}
|
|
115587
|
+
function baseState(overrides = {}) {
|
|
115588
|
+
return withBetting({
|
|
115589
|
+
contextVersion: DECISION_CONTEXT_VERSION,
|
|
115590
|
+
informationPolicy: INFORMATION_POLICY,
|
|
115591
|
+
objective: DECISION_OBJECTIVE,
|
|
115592
|
+
game: "No-Limit Texas Holdem tournament",
|
|
115593
|
+
memoryPolicy: {
|
|
115594
|
+
currentHand: "all public model actions in the current hand before this decision",
|
|
115595
|
+
recentHands: "last 8 completed public hands",
|
|
115596
|
+
publicPlayerStats: "deterministic aggregates from completed hands before the current hand; identical public dataset for every seat"
|
|
115597
|
+
},
|
|
115598
|
+
tournament: { handNumber: 50, blindLevel: 4, playersRemaining: 2, startingPlayers: 2 },
|
|
115599
|
+
blinds: { smallBlind: 50, bigBlind: 100, ante: 0 },
|
|
115600
|
+
hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BTN/SB", stack: 1e4, stackBB: 100, cards: ["As", "Kd"], currentBet: 0 },
|
|
115601
|
+
board: [],
|
|
115602
|
+
street: "PREFLOP",
|
|
115603
|
+
pot: 150,
|
|
115604
|
+
buttonSeat: 1,
|
|
115605
|
+
actionHistory: [],
|
|
115606
|
+
recentHands: [],
|
|
115607
|
+
publicPlayerStats: [
|
|
115608
|
+
{ playerId: "benchmark-hero", playerName: "Benchmark Hero", sampleHands: 40, vpipPct: 0.3, pfrPct: 0.22, aggressionPct: 0.41, foldPct: 0.28, callPct: 0.24, checkPct: 0.18, wins: 10 },
|
|
115609
|
+
{ playerId: "benchmark-villain", playerName: "Benchmark Villain", sampleHands: 40, vpipPct: 0.31, pfrPct: 0.21, aggressionPct: 0.4, foldPct: 0.29, callPct: 0.24, checkPct: 0.17, wins: 9 }
|
|
115610
|
+
],
|
|
115611
|
+
opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 1e4, stackBB: 100, currentBet: 100, status: "ACTIVE", cards: [] }],
|
|
115612
|
+
legalActions: [],
|
|
115613
|
+
...overrides
|
|
115614
|
+
});
|
|
115615
|
+
}
|
|
115616
|
+
var DECISION_SANITY_SCENARIOS = Object.freeze([
|
|
115617
|
+
{
|
|
115618
|
+
id: "free-check-dominates-fold",
|
|
115619
|
+
title: "Free check dominates folding",
|
|
115620
|
+
category: "Dominance",
|
|
115621
|
+
expectedTypes: ["CHECK"],
|
|
115622
|
+
note: "With no bet to call, checking preserves every future outcome while folding gives up immediately.",
|
|
115623
|
+
state: baseState({ street: "FLOP", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 5e3, stackBB: 50, cards: ["7c", "2d"], currentBet: 0 }, board: ["As", "Kd", "Qh"], pot: 300, opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 5e3, stackBB: 50, currentBet: 0, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CHECK", "Check")] })
|
|
115624
|
+
},
|
|
115625
|
+
{
|
|
115626
|
+
id: "board-royal-call",
|
|
115627
|
+
title: "Board royal flush: call a small bet",
|
|
115628
|
+
category: "Guaranteed tie",
|
|
115629
|
+
expectedTypes: ["CALL"],
|
|
115630
|
+
note: "The board itself is a royal flush, so folding gives up a guaranteed share of the pot.",
|
|
115631
|
+
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 4900, stackBB: 49, cards: ["2c", "3d"], currentBet: 0 }, board: ["As", "Ks", "Qs", "Js", "Ts"], pot: 1100, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 100, description: "Bet 100 (1 BB)" }, potBefore: 1e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 4900, stackBB: 49, currentBet: 100, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 100")] })
|
|
115632
|
+
},
|
|
115633
|
+
{
|
|
115634
|
+
id: "royal-nuts-call",
|
|
115635
|
+
title: "Royal flush facing a shove",
|
|
115636
|
+
category: "Nuts",
|
|
115637
|
+
expectedTypes: ["CALL"],
|
|
115638
|
+
note: "Hero holds the unbeatable royal flush and must not fold to an all-in bet.",
|
|
115639
|
+
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 4e3, stackBB: 40, cards: ["As", "Ks"], currentBet: 0 }, board: ["Qs", "Js", "Ts", "2d", "3c"], pot: 6e3, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 4e3, description: "All-in 4000 (40 BB)" }, potBefore: 2e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 0, stackBB: 0, currentBet: 4e3, status: "ALL_IN", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 4000")] })
|
|
115640
|
+
},
|
|
115641
|
+
{
|
|
115642
|
+
id: "straight-flush-call",
|
|
115643
|
+
title: "Straight flush facing a river bet",
|
|
115644
|
+
category: "Nuts",
|
|
115645
|
+
expectedTypes: ["CALL"],
|
|
115646
|
+
note: "Hero has a nine-high straight flush; folding to the available call is an obvious sanity failure.",
|
|
115647
|
+
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 7e3, stackBB: 70, cards: ["9s", "8s"], currentBet: 0 }, board: ["7s", "6s", "5s", "Kd", "2c"], pot: 3500, opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 5500, stackBB: 55, currentBet: 1500, status: "ACTIVE", cards: [] }], actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 1500, description: "Bet 1500 (15 BB)" }, potBefore: 2e3 }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 1500")] })
|
|
115648
|
+
},
|
|
115649
|
+
{
|
|
115650
|
+
id: "aces-vs-short-shove",
|
|
115651
|
+
title: "Pocket aces versus a short-stack shove",
|
|
115652
|
+
category: "Preflop",
|
|
115653
|
+
expectedTypes: ["CALL"],
|
|
115654
|
+
note: "With only fold or call available, folding AA to a short all-in is a basic strategic failure.",
|
|
115655
|
+
state: baseState({ hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9600, stackBB: 96, cards: ["As", "Ah"], currentBet: 100 }, pot: 2550, actionHistory: [{ street: "PREFLOP", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "RAISE", amount: 2500, description: "All-in to 2500 (25 BB)" }, potBefore: 150 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 0, stackBB: 0, currentBet: 2500, status: "ALL_IN", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 2400")] })
|
|
115656
|
+
},
|
|
115657
|
+
{
|
|
115658
|
+
id: "seven-deuce-vs-shove",
|
|
115659
|
+
title: "Seven-deuce offsuit versus a 100 BB shove",
|
|
115660
|
+
category: "Preflop",
|
|
115661
|
+
expectedTypes: ["FOLD"],
|
|
115662
|
+
note: "The weakest starting hand facing an enormous shove with little dead money should fold.",
|
|
115663
|
+
state: baseState({ hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9900, stackBB: 99, cards: ["7c", "2d"], currentBet: 100 }, pot: 10150, actionHistory: [{ street: "PREFLOP", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "RAISE", amount: 1e4, description: "All-in to 10000 (100 BB)" }, potBefore: 150 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 0, stackBB: 0, currentBet: 1e4, status: "ALL_IN", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 9900")] })
|
|
115664
|
+
},
|
|
115665
|
+
{
|
|
115666
|
+
id: "river-air-overbet",
|
|
115667
|
+
title: "Seven-high versus a 10\xD7 pot river shove",
|
|
115668
|
+
category: "River discipline",
|
|
115669
|
+
expectedTypes: ["FOLD"],
|
|
115670
|
+
note: "Hero has seven-high with no meaningful showdown value and faces an extreme overbet.",
|
|
115671
|
+
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 1e4, stackBB: 100, cards: ["7d", "2d"], currentBet: 0 }, board: ["Ac", "Kh", "Qh", "9s", "3c"], pot: 1100, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 1e4, description: "All-in 10000 (100 BB)" }, potBefore: 1e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 0, stackBB: 0, currentBet: 1e4, status: "ALL_IN", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 10000")] })
|
|
115672
|
+
},
|
|
115673
|
+
{
|
|
115674
|
+
id: "nut-flush-tiny-bet",
|
|
115675
|
+
title: "Nut flush versus a tiny river bet",
|
|
115676
|
+
category: "Pot odds",
|
|
115677
|
+
expectedTypes: ["CALL"],
|
|
115678
|
+
note: "Hero has the nut flush and faces a tiny bet; folding is an obvious sanity failure.",
|
|
115679
|
+
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9900, stackBB: 99, cards: ["As", "8s"], currentBet: 0 }, board: ["Ks", "Qs", "4s", "2h", "3d"], pot: 1100, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 100, description: "Bet 100 (1 BB)" }, potBefore: 1e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 9900, stackBB: 99, currentBet: 100, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 100")] })
|
|
115680
|
+
},
|
|
115681
|
+
{
|
|
115682
|
+
id: "kings-vs-open",
|
|
115683
|
+
title: "Pocket kings versus a normal open",
|
|
115684
|
+
category: "Preflop",
|
|
115685
|
+
expectedTypes: ["CALL", "RAISE"],
|
|
115686
|
+
note: "Continuing with KK is expected; the suite accepts either calling or raising and rejects folding.",
|
|
115687
|
+
state: baseState({ hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9900, stackBB: 99, cards: ["Kc", "Kd"], currentBet: 100 }, pot: 450, actionHistory: [{ street: "PREFLOP", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "RAISE", amount: 300, description: "Raise to 300 (3 BB)" }, potBefore: 150 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 9700, stackBB: 97, currentBet: 300, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 200"), action("A2", "RAISE", "Raise to 900 (9 BB)", 900), action("A3", "RAISE", "Raise to 1200 (12 BB)", 1200)] })
|
|
115688
|
+
},
|
|
115689
|
+
{
|
|
115690
|
+
id: "top-pair-tiny-river-bet",
|
|
115691
|
+
title: "Top pair versus a 5% pot river bet",
|
|
115692
|
+
category: "Pot odds",
|
|
115693
|
+
expectedTypes: ["CALL"],
|
|
115694
|
+
note: "Top pair facing a tiny river bet gets overwhelming pot odds; folding is a basic sanity failure.",
|
|
115695
|
+
state: baseState({ street: "RIVER", hero: { id: "benchmark-hero", name: "Benchmark Hero", seat: 1, position: "BB", stack: 9950, stackBB: 99.5, cards: ["Ah", "Jd"], currentBet: 0 }, board: ["As", "8c", "6d", "4h", "2c"], pot: 1050, actionHistory: [{ street: "RIVER", playerName: "Benchmark Villain", position: "BTN/SB", action: { type: "BET", amount: 50, description: "Bet 50 (0.5 BB)" }, potBefore: 1e3 }], opponents: [{ seat: 2, id: "benchmark-villain", name: "Benchmark Villain", stack: 9950, stackBB: 99.5, currentBet: 50, status: "ACTIVE", cards: [] }], legalActions: [action("A0", "FOLD", "Fold"), action("A1", "CALL", "Call 50")] })
|
|
115696
|
+
}
|
|
115697
|
+
]);
|
|
115698
|
+
|
|
115699
|
+
// src/config/arena-config.js
|
|
115700
|
+
var BASE_CONFIG = {
|
|
115701
|
+
table: {
|
|
115702
|
+
minPlayers: 2,
|
|
115703
|
+
maxPlayers: 10,
|
|
115704
|
+
startingStack: 3e3,
|
|
115705
|
+
smallBlind: 25,
|
|
115706
|
+
bigBlind: 50,
|
|
115707
|
+
ante: 5,
|
|
115708
|
+
handsPerLevel: 8,
|
|
115709
|
+
blindMultiplier: 2
|
|
115710
|
+
},
|
|
115711
|
+
timing: {
|
|
115712
|
+
actionSeconds: 30,
|
|
115713
|
+
timeBankSeconds: 30,
|
|
115714
|
+
lowTimeSeconds: 5,
|
|
115715
|
+
lowTimeFraction: 0.25,
|
|
115716
|
+
betweenActionsMs: 250,
|
|
115717
|
+
betweenHandsMs: 2e3
|
|
115718
|
+
},
|
|
115719
|
+
limits: {
|
|
115720
|
+
startingStack: { min: 100 },
|
|
115721
|
+
smallBlind: { min: 1 },
|
|
115722
|
+
bigBlind: { min: 2 },
|
|
115723
|
+
ante: { min: 0 },
|
|
115724
|
+
handsPerLevel: { min: 1 },
|
|
115725
|
+
blindMultiplier: { min: 1.1, max: 3, step: 0.1 },
|
|
115726
|
+
actionSeconds: { min: 1, max: 120 },
|
|
115727
|
+
timeBankSeconds: { min: 0, max: 600 },
|
|
115728
|
+
lowTimeSeconds: { min: 0, max: 600 },
|
|
115729
|
+
lowTimeFraction: { min: 0, max: 1, step: 0.05 },
|
|
115730
|
+
betweenActionsMs: { min: 0, max: 5e3 },
|
|
115731
|
+
betweenHandsMs: { min: 0, max: 1e4 }
|
|
115732
|
+
},
|
|
115733
|
+
connections: {
|
|
115734
|
+
defaultKind: "openai",
|
|
115735
|
+
siteUrl: "https://pokertools-arena.github.io/",
|
|
115736
|
+
siteName: "pokertools-arena",
|
|
115737
|
+
presets: {
|
|
115738
|
+
openai: "https://api.openai.com/v1",
|
|
115739
|
+
openrouter: "https://openrouter.ai/api/v1",
|
|
115740
|
+
typesafe: "https://api.typesafe.ai/v1/systemone"
|
|
115741
|
+
}
|
|
115742
|
+
},
|
|
115743
|
+
history: {
|
|
115744
|
+
recentDecisions: 12,
|
|
115745
|
+
snapshotEvents: 300,
|
|
115746
|
+
persistedEvents: 100
|
|
115747
|
+
}
|
|
115748
|
+
};
|
|
115749
|
+
function mergeConfig(base, override) {
|
|
115750
|
+
if (!override || typeof override !== "object" || Array.isArray(override)) return { ...base };
|
|
115751
|
+
const merged = { ...base };
|
|
115752
|
+
for (const [key, value] of Object.entries(override)) {
|
|
115753
|
+
if (value && typeof value === "object" && !Array.isArray(value) && base[key] && typeof base[key] === "object") {
|
|
115754
|
+
merged[key] = mergeConfig(base[key], value);
|
|
115755
|
+
} else if (value !== void 0) {
|
|
115756
|
+
merged[key] = value;
|
|
115757
|
+
}
|
|
115758
|
+
}
|
|
115759
|
+
return merged;
|
|
115760
|
+
}
|
|
115761
|
+
var ARENA_CONFIG = Object.freeze(mergeConfig(BASE_CONFIG, globalThis.__POKERTOOLS_ARENA_CONFIG__));
|
|
115762
|
+
var TABLE_DEFAULTS = Object.freeze(ARENA_CONFIG.table);
|
|
115763
|
+
var TIMING_DEFAULTS = Object.freeze(ARENA_CONFIG.timing);
|
|
115764
|
+
var CONFIG_LIMITS = Object.freeze(ARENA_CONFIG.limits);
|
|
115765
|
+
var CONNECTION_PRESETS = Object.freeze(ARENA_CONFIG.connections.presets);
|
|
115766
|
+
var HISTORY_CONFIG = Object.freeze(ARENA_CONFIG.history);
|
|
115767
|
+
var FORM_DEFAULTS = Object.freeze({ ...TABLE_DEFAULTS, ...TIMING_DEFAULTS });
|
|
115768
|
+
function applyConfiguredFormDefaults(form) {
|
|
115769
|
+
for (const [name, value] of Object.entries(FORM_DEFAULTS)) {
|
|
115770
|
+
const input = form?.elements?.namedItem(name);
|
|
115771
|
+
if (!input) continue;
|
|
115772
|
+
input.value = String(value);
|
|
115773
|
+
const limits = CONFIG_LIMITS[name] || {};
|
|
115774
|
+
for (const attribute of ["min", "max", "step"]) {
|
|
115775
|
+
if (limits[attribute] != null) input.setAttribute(attribute, String(limits[attribute]));
|
|
115776
|
+
}
|
|
115777
|
+
}
|
|
115778
|
+
}
|
|
115779
|
+
function defaultConnections() {
|
|
115780
|
+
const kind = ARENA_CONFIG.connections.defaultKind;
|
|
115781
|
+
return [{ name: "API", kind, baseUrl: CONNECTION_PRESETS[kind], apiKey: "", headers: defaultExtraHeaders(kind) }];
|
|
115782
|
+
}
|
|
115783
|
+
function defaultExtraHeaders(kind) {
|
|
115784
|
+
if (kind !== "openrouter") return "";
|
|
115785
|
+
return JSON.stringify({
|
|
115786
|
+
"HTTP-Referer": ARENA_CONFIG.connections.siteUrl,
|
|
115787
|
+
"X-OpenRouter-Title": ARENA_CONFIG.connections.siteName
|
|
115788
|
+
}, null, 2);
|
|
115789
|
+
}
|
|
115790
|
+
|
|
115797
115791
|
// src/assets/card-deal.mp3
|
|
115798
115792
|
var card_deal_default = "data:audio/mpeg;base64,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onWk/7WjRpaNbM8siaAVORzuRee5FH9wZbLzyz7MyRz84iRgkAly1NTnLBUZI1stX/NeHVFTqn1zzbZJcCsBI/UmTMTXtPb1pz/+c5xIlrzLV29aaizHVTScFb/S//uyZNQA1G53PYNJMJBm7bfBJKMQElme3M1gwGG/uNrEwwm6XeZk0jjVPrafqVFZ5uUuYzzPmMbMYUaVHlZOYxcuYzqAic0zqyG6lvZAIBZ0DAT5WUKJK0p2+YxW/NNQy6mNo+jUM+Y2gYVQMBCWQHTPhpVMQU1FNC4wVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYBAJFtyALEh6pQgm3TcX+zfs0AjnS9fnS//VAIVRJa1cMBUBEqzHf2aqX8aELpbS0iIEnImlUNItWjlSvKWl1mlUMULMZSl6Ras1GMalJZrkKGMf8RE0VWSwJNSRSQs1eERNeJBYNJocltCESkRN7ZWntItSRJil0ZKs+MYqzjlocISXlia3DcuuhQkpI74Qkv2XVjOoUyl/l/5jS3K5nmMBGMUpW/9jpVZaRU3mtZq+Pslv4bWv/4yhVWYoGx0qwdB8HQCwjdknQHQLhKAqIpJv8rXsSsrANg+tba/yjmJNVYZsm/2v2WrmlWG21FRzTS0UPgWOZqKFmZpWv5gI4C5f/DAWIeWAg3H0JinFnGROm0LgFANLjDDUfC2rAC4ybZelI5XycrVlyuooE3NTVny9mkt61mEMOhsAomAOFIehKUAbGYNSoB4gj6OJqIR+IqQRkMdSqakpGSTwSjsRSmTSsbEk8Eo7ElOTS8bElQSl4kpyaXlRi4ZPklsmoRaOURkvElsSUhOLpqSlYkrxJQi0cqUyokrzGi5tbAqKr5jQ+hPUzxy+c0XNrWo2VgqBItE6jaNUdpGEnSNROWRhIujUTlm4SenIlJGqJWc6JyRqiV08tTyyRyy//uyZOyA9HtpKzkDS/J3rlWSCQLcHTXkZi0w1cjeA8AB//gMMJGo1TJG4SsiYFHLIwSPInJHabhKyMBJnNTseb6od+moOvNBokKCSbKgbKmYHpFB1ipsnR5DqZoSYBsTLNmD8qgbUKIxkTL4mxIWuZu67ypMQU1FNC4wqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq";
|
|
115799
115793
|
|
|
@@ -116810,6 +116804,7 @@ var TournamentDirector = class {
|
|
|
116810
116804
|
async recoverHand(triggerError) {
|
|
116811
116805
|
this.currentDecision = null;
|
|
116812
116806
|
if (this.handComplete()) {
|
|
116807
|
+
this.completeHand();
|
|
116813
116808
|
this.broadcast();
|
|
116814
116809
|
return;
|
|
116815
116810
|
}
|
|
@@ -117322,14 +117317,6 @@ function addConnectionRow(connection = {}) {
|
|
|
117322
117317
|
if (event.target.value !== "openrouter" && headersInput.value.trim() === defaultOpenRouterHeaders) headersInput.value = "";
|
|
117323
117318
|
refreshSeatConnectionSelect();
|
|
117324
117319
|
});
|
|
117325
|
-
$("[data-field=kind]", row).addEventListener("change", () => {
|
|
117326
|
-
const kind = $("[data-field=kind]", row).value;
|
|
117327
|
-
const url = $("[data-field=baseUrl]", row);
|
|
117328
|
-
if (kind === "typesafe" && (!url.value || url.value.includes("openrouter.ai"))) url.value = CONNECTION_PRESETS.typesafe;
|
|
117329
|
-
if (kind === "openrouter" && (!url.value || url.value.includes("typesafe.ai"))) url.value = CONNECTION_PRESETS.openrouter;
|
|
117330
|
-
if (kind === "openai" && (!url.value || url.value.includes("typesafe.ai"))) url.value = CONNECTION_PRESETS.openai;
|
|
117331
|
-
refreshSeatConnectionSelect();
|
|
117332
|
-
});
|
|
117333
117320
|
$(".remove-connection", row).addEventListener("click", () => {
|
|
117334
117321
|
if (els.connectionsEditor.children.length <= 1) return;
|
|
117335
117322
|
const removedId = row.dataset.id;
|
|
@@ -119476,6 +119463,7 @@ var choicePickers = /* @__PURE__ */ new Map();
|
|
|
119476
119463
|
function syncChoicePicker(select) {
|
|
119477
119464
|
const picker = choicePickers.get(select);
|
|
119478
119465
|
if (!picker) return;
|
|
119466
|
+
const focusedIndex = document.activeElement?.closest?.(".choice-option")?.dataset.optionIndex;
|
|
119479
119467
|
picker.replaceChildren(...[...select.options].map((option, index) => {
|
|
119480
119468
|
const button = document.createElement("button");
|
|
119481
119469
|
button.type = "button";
|
|
@@ -119484,11 +119472,14 @@ function syncChoicePicker(select) {
|
|
|
119484
119472
|
button.dataset.optionIndex = String(index);
|
|
119485
119473
|
button.textContent = option.textContent;
|
|
119486
119474
|
button.disabled = select.disabled || option.disabled;
|
|
119487
|
-
|
|
119475
|
+
const checked = option.value === select.value;
|
|
119476
|
+
button.setAttribute("aria-checked", String(checked));
|
|
119477
|
+
button.tabIndex = checked ? 0 : -1;
|
|
119488
119478
|
return button;
|
|
119489
119479
|
}));
|
|
119490
119480
|
picker.dataset.optionCount = String(select.options.length);
|
|
119491
119481
|
picker.setAttribute("aria-disabled", String(select.disabled));
|
|
119482
|
+
if (focusedIndex != null) picker.querySelector(`[data-option-index="${focusedIndex}"]`)?.focus();
|
|
119492
119483
|
}
|
|
119493
119484
|
function syncChoicePickers() {
|
|
119494
119485
|
for (const select of choicePickers.keys()) syncChoicePicker(select);
|
|
@@ -119501,6 +119492,8 @@ function enhanceSelect(select) {
|
|
|
119501
119492
|
picker.role = "radiogroup";
|
|
119502
119493
|
picker.setAttribute("aria-label", select.getAttribute("aria-label") || `Choose ${id2.replace(/[-_]/g, " ")}`);
|
|
119503
119494
|
select.classList.add("native-select-enhanced");
|
|
119495
|
+
select.tabIndex = -1;
|
|
119496
|
+
select.setAttribute("aria-hidden", "true");
|
|
119504
119497
|
select.insertAdjacentElement("afterend", picker);
|
|
119505
119498
|
choicePickers.set(select, picker);
|
|
119506
119499
|
syncChoicePicker(select);
|