@dupecom/botcha-cloudflare 0.3.3 → 0.9.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/dist/analytics.d.ts +60 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +130 -0
- package/dist/apps.d.ts +93 -0
- package/dist/apps.d.ts.map +1 -0
- package/dist/apps.js +152 -0
- package/dist/auth.d.ts +93 -6
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +251 -9
- package/dist/challenges.d.ts +31 -7
- package/dist/challenges.d.ts.map +1 -1
- package/dist/challenges.js +550 -144
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +412 -46
- package/dist/rate-limit.d.ts +11 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +13 -2
- package/dist/static.d.ts +517 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +635 -0
- package/package.json +1 -1
package/dist/challenges.js
CHANGED
|
@@ -69,10 +69,10 @@ async function deleteChallenge(kv, id) {
|
|
|
69
69
|
}
|
|
70
70
|
// ============ SPEED CHALLENGE ============
|
|
71
71
|
/**
|
|
72
|
-
* Generate a speed challenge: 5 SHA256 problems,
|
|
72
|
+
* Generate a speed challenge: 5 SHA256 problems, RTT-aware timeout
|
|
73
73
|
* Trivial for AI, impossible for humans to copy-paste fast enough
|
|
74
74
|
*/
|
|
75
|
-
export async function generateSpeedChallenge(kv) {
|
|
75
|
+
export async function generateSpeedChallenge(kv, clientTimestamp, app_id) {
|
|
76
76
|
cleanExpired();
|
|
77
77
|
const id = uuid();
|
|
78
78
|
const problems = [];
|
|
@@ -82,25 +82,57 @@ export async function generateSpeedChallenge(kv) {
|
|
|
82
82
|
problems.push({ num, operation: 'sha256_first8' });
|
|
83
83
|
expectedAnswers.push(await sha256First(num.toString(), 8));
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
// RTT-aware timeout calculation
|
|
86
|
+
const baseTimeLimit = 500; // Base computation time for AI agents
|
|
87
|
+
const MAX_RTT_MS = 5000; // Cap RTT to prevent timestamp spoofing (5s max)
|
|
88
|
+
const MAX_TIMESTAMP_AGE_MS = 30000; // Reject timestamps older than 30s
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
let rttMs = 0;
|
|
91
|
+
let adjustedTimeLimit = baseTimeLimit;
|
|
92
|
+
let rttInfo = undefined;
|
|
93
|
+
if (clientTimestamp && clientTimestamp > 0) {
|
|
94
|
+
// Reject timestamps in the future or too far in the past (anti-spoofing)
|
|
95
|
+
const age = now - clientTimestamp;
|
|
96
|
+
if (age >= 0 && age <= MAX_TIMESTAMP_AGE_MS) {
|
|
97
|
+
// Calculate RTT from client timestamp, capped to prevent abuse
|
|
98
|
+
rttMs = Math.min(age, MAX_RTT_MS);
|
|
99
|
+
// Adjust timeout: base + (2 * RTT) + 100ms buffer
|
|
100
|
+
// The 2x RTT accounts for request + response network time
|
|
101
|
+
adjustedTimeLimit = Math.max(baseTimeLimit, baseTimeLimit + (2 * rttMs) + 100);
|
|
102
|
+
rttInfo = {
|
|
103
|
+
measuredRtt: rttMs,
|
|
104
|
+
adjustedTimeout: adjustedTimeLimit,
|
|
105
|
+
explanation: `RTT: ${rttMs}ms → Timeout: ${baseTimeLimit}ms + (2×${rttMs}ms) + 100ms = ${adjustedTimeLimit}ms`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// else: invalid timestamp silently ignored, use base timeout
|
|
109
|
+
}
|
|
86
110
|
const challenge = {
|
|
87
111
|
id,
|
|
88
112
|
problems,
|
|
89
113
|
expectedAnswers,
|
|
90
|
-
issuedAt:
|
|
91
|
-
expiresAt:
|
|
114
|
+
issuedAt: now,
|
|
115
|
+
expiresAt: now + adjustedTimeLimit + 50, // Small server-side grace period
|
|
116
|
+
baseTimeLimit,
|
|
117
|
+
adjustedTimeLimit,
|
|
118
|
+
rttMs,
|
|
119
|
+
app_id,
|
|
92
120
|
};
|
|
93
121
|
// Store in KV with 5 minute TTL (safety buffer for time checks)
|
|
94
122
|
await storeChallenge(kv, id, challenge, 300);
|
|
123
|
+
const instructions = rttMs > 0
|
|
124
|
+
? `Compute SHA256 of each number, return first 8 hex chars of each. Submit as array. You have ${adjustedTimeLimit}ms (adjusted for your ${rttMs}ms network latency).`
|
|
125
|
+
: 'Compute SHA256 of each number, return first 8 hex chars of each. Submit as array. You have 500ms.';
|
|
95
126
|
return {
|
|
96
127
|
id,
|
|
97
128
|
problems,
|
|
98
|
-
timeLimit,
|
|
99
|
-
instructions
|
|
129
|
+
timeLimit: adjustedTimeLimit,
|
|
130
|
+
instructions,
|
|
131
|
+
rttInfo,
|
|
100
132
|
};
|
|
101
133
|
}
|
|
102
134
|
/**
|
|
103
|
-
* Verify a speed challenge response
|
|
135
|
+
* Verify a speed challenge response with RTT-aware timeout
|
|
104
136
|
*/
|
|
105
137
|
export async function verifySpeedChallenge(id, answers, kv) {
|
|
106
138
|
const challenge = await getChallenge(kv, id, true);
|
|
@@ -111,8 +143,21 @@ export async function verifySpeedChallenge(id, answers, kv) {
|
|
|
111
143
|
const solveTimeMs = now - challenge.issuedAt;
|
|
112
144
|
// Delete challenge immediately to prevent replay attacks
|
|
113
145
|
await deleteChallenge(kv, id);
|
|
146
|
+
// Use the challenge's adjusted timeout, fallback to base if not available
|
|
147
|
+
const timeLimit = challenge.adjustedTimeLimit || challenge.baseTimeLimit || 500;
|
|
114
148
|
if (now > challenge.expiresAt) {
|
|
115
|
-
|
|
149
|
+
const rttExplanation = challenge.rttMs
|
|
150
|
+
? ` (RTT-adjusted: ${challenge.rttMs}ms network + ${challenge.baseTimeLimit}ms compute = ${timeLimit}ms limit)`
|
|
151
|
+
: '';
|
|
152
|
+
return {
|
|
153
|
+
valid: false,
|
|
154
|
+
reason: `Too slow! Took ${solveTimeMs}ms, limit was ${timeLimit}ms${rttExplanation}`,
|
|
155
|
+
rttInfo: challenge.rttMs ? {
|
|
156
|
+
measuredRtt: challenge.rttMs,
|
|
157
|
+
adjustedTimeout: timeLimit,
|
|
158
|
+
actualTime: solveTimeMs,
|
|
159
|
+
} : undefined,
|
|
160
|
+
};
|
|
116
161
|
}
|
|
117
162
|
if (!Array.isArray(answers) || answers.length !== 5) {
|
|
118
163
|
return { valid: false, reason: 'Must provide exactly 5 answers as array' };
|
|
@@ -122,7 +167,16 @@ export async function verifySpeedChallenge(id, answers, kv) {
|
|
|
122
167
|
return { valid: false, reason: `Wrong answer for challenge ${i + 1}` };
|
|
123
168
|
}
|
|
124
169
|
}
|
|
125
|
-
return {
|
|
170
|
+
return {
|
|
171
|
+
valid: true,
|
|
172
|
+
solveTimeMs,
|
|
173
|
+
app_id: challenge.app_id,
|
|
174
|
+
rttInfo: challenge.rttMs ? {
|
|
175
|
+
measuredRtt: challenge.rttMs,
|
|
176
|
+
adjustedTimeout: timeLimit,
|
|
177
|
+
actualTime: solveTimeMs,
|
|
178
|
+
} : undefined,
|
|
179
|
+
};
|
|
126
180
|
}
|
|
127
181
|
// ============ STANDARD CHALLENGE ============
|
|
128
182
|
const DIFFICULTY_CONFIG = {
|
|
@@ -133,28 +187,31 @@ const DIFFICULTY_CONFIG = {
|
|
|
133
187
|
/**
|
|
134
188
|
* Generate a standard challenge: compute SHA256 of concatenated primes
|
|
135
189
|
*/
|
|
136
|
-
export async function generateStandardChallenge(difficulty = 'medium', kv) {
|
|
190
|
+
export async function generateStandardChallenge(difficulty = 'medium', kv, app_id) {
|
|
137
191
|
cleanExpired();
|
|
138
192
|
const id = uuid();
|
|
139
193
|
const config = DIFFICULTY_CONFIG[difficulty];
|
|
194
|
+
// Random salt makes each challenge unique — precomputed lookup tables won't work
|
|
195
|
+
const salt = uuid().replace(/-/g, '').substring(0, 16);
|
|
140
196
|
const primes = generatePrimes(config.primes);
|
|
141
|
-
const concatenated = primes.join('');
|
|
197
|
+
const concatenated = primes.join('') + salt;
|
|
142
198
|
const hash = await sha256(concatenated);
|
|
143
199
|
const answer = hash.substring(0, 16);
|
|
144
200
|
const challenge = {
|
|
145
201
|
id,
|
|
146
|
-
puzzle: `Compute SHA256 of the first ${config.primes} prime numbers concatenated (no separators). Return the first 16 hex characters.`,
|
|
202
|
+
puzzle: `Compute SHA256 of the first ${config.primes} prime numbers concatenated (no separators) followed by the salt "${salt}". Return the first 16 hex characters.`,
|
|
147
203
|
expectedAnswer: answer,
|
|
148
204
|
expiresAt: Date.now() + config.timeLimit + 1000,
|
|
149
205
|
difficulty,
|
|
206
|
+
app_id,
|
|
150
207
|
};
|
|
151
208
|
// Store in KV with 5 minute TTL
|
|
152
209
|
await storeChallenge(kv, id, challenge, 300);
|
|
153
210
|
return {
|
|
154
211
|
id,
|
|
155
|
-
puzzle: `Compute SHA256 of the first ${config.primes} prime numbers concatenated (no separators). Return the first 16 hex characters.`,
|
|
212
|
+
puzzle: `Compute SHA256 of the first ${config.primes} prime numbers concatenated (no separators) followed by the salt "${salt}". Return the first 16 hex characters.`,
|
|
156
213
|
timeLimit: config.timeLimit,
|
|
157
|
-
hint: `Example: First 5 primes = "235711" → SHA256 → first 16 chars`,
|
|
214
|
+
hint: `Example: First 5 primes + salt = "235711${salt}" → SHA256 → first 16 chars`,
|
|
158
215
|
};
|
|
159
216
|
}
|
|
160
217
|
/**
|
|
@@ -186,20 +243,22 @@ const landingTokens = new Map();
|
|
|
186
243
|
*/
|
|
187
244
|
export async function verifyLandingChallenge(answer, timestamp, kv) {
|
|
188
245
|
cleanExpired();
|
|
189
|
-
// Verify timestamp is recent (within
|
|
246
|
+
// Verify timestamp is recent (within 2 minutes — tighter window for security)
|
|
190
247
|
const submittedTime = new Date(timestamp).getTime();
|
|
191
248
|
const now = Date.now();
|
|
192
|
-
if (Math.abs(now - submittedTime) >
|
|
193
|
-
return { valid: false, error: 'Timestamp
|
|
249
|
+
if (Number.isNaN(submittedTime) || Math.abs(now - submittedTime) > 2 * 60 * 1000) {
|
|
250
|
+
return { valid: false, error: 'Timestamp expired or invalid. Request a fresh challenge.' };
|
|
194
251
|
}
|
|
195
|
-
//
|
|
252
|
+
// Per-request nonce: include the timestamp in the hash input so answers are unique per request
|
|
253
|
+
// This prevents answer sharing — each timestamp produces a different expected answer
|
|
196
254
|
const today = new Date().toISOString().split('T')[0];
|
|
197
|
-
const expectedHash = (await sha256(`BOTCHA-LANDING-${today}`)).substring(0, 16);
|
|
255
|
+
const expectedHash = (await sha256(`BOTCHA-LANDING-${today}-${timestamp}`)).substring(0, 16);
|
|
198
256
|
if (answer.toLowerCase() !== expectedHash.toLowerCase()) {
|
|
199
257
|
return {
|
|
200
258
|
valid: false,
|
|
201
259
|
error: 'Incorrect answer',
|
|
202
|
-
|
|
260
|
+
// Don't leak the formula — only give a generic hint
|
|
261
|
+
hint: 'Parse the challenge from <script type="application/botcha+json"> on the landing page and compute the answer.',
|
|
203
262
|
};
|
|
204
263
|
}
|
|
205
264
|
// Generate token
|
|
@@ -249,149 +308,490 @@ export async function solveSpeedChallenge(problems) {
|
|
|
249
308
|
// ============ REASONING CHALLENGE ============
|
|
250
309
|
// In-memory storage for reasoning challenges
|
|
251
310
|
const reasoningChallenges = new Map();
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
311
|
+
// ============ PARAMETERIZED QUESTION GENERATORS ============
|
|
312
|
+
// These generate unique questions each time, so a static lookup table won't work.
|
|
313
|
+
function randInt(min, max) {
|
|
314
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
315
|
+
}
|
|
316
|
+
function pickRandom(arr) {
|
|
317
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
318
|
+
}
|
|
319
|
+
// --- Math generators (randomized numbers each time) ---
|
|
320
|
+
function genMathAdd() {
|
|
321
|
+
const a = randInt(100, 999);
|
|
322
|
+
const b = randInt(100, 999);
|
|
323
|
+
return {
|
|
324
|
+
id: `math-add-${uuid().substring(0, 8)}`,
|
|
325
|
+
question: `What is ${a} + ${b}?`,
|
|
326
|
+
category: 'math',
|
|
327
|
+
acceptedAnswers: [(a + b).toString()],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function genMathMultiply() {
|
|
331
|
+
const a = randInt(12, 99);
|
|
332
|
+
const b = randInt(12, 99);
|
|
333
|
+
return {
|
|
334
|
+
id: `math-mul-${uuid().substring(0, 8)}`,
|
|
335
|
+
question: `What is ${a} × ${b}?`,
|
|
336
|
+
category: 'math',
|
|
337
|
+
acceptedAnswers: [(a * b).toString()],
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function genMathModulo() {
|
|
341
|
+
const a = randInt(50, 999);
|
|
342
|
+
const b = randInt(3, 19);
|
|
343
|
+
return {
|
|
344
|
+
id: `math-mod-${uuid().substring(0, 8)}`,
|
|
345
|
+
question: `What is ${a} % ${b} (modulo)?`,
|
|
346
|
+
category: 'math',
|
|
347
|
+
acceptedAnswers: [(a % b).toString()],
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function genMathSheep() {
|
|
351
|
+
const total = randInt(15, 50);
|
|
352
|
+
const remaining = randInt(3, total - 2);
|
|
353
|
+
return {
|
|
354
|
+
id: `math-sheep-${uuid().substring(0, 8)}`,
|
|
355
|
+
question: `A farmer has ${total} sheep. All but ${remaining} run away. How many sheep does he have left? Answer with just the number.`,
|
|
356
|
+
category: 'math',
|
|
357
|
+
acceptedAnswers: [remaining.toString()],
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function genMathDoubling() {
|
|
361
|
+
const days = randInt(20, 60);
|
|
362
|
+
return {
|
|
363
|
+
id: `math-double-${uuid().substring(0, 8)}`,
|
|
364
|
+
question: `A patch of lily pads doubles in size every day. If it takes ${days} days to cover the entire lake, how many days to cover half? Answer with just the number.`,
|
|
365
|
+
category: 'math',
|
|
366
|
+
acceptedAnswers: [(days - 1).toString()],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function genMathMachines() {
|
|
370
|
+
const n = pickRandom([5, 7, 8, 10, 12]);
|
|
371
|
+
const m = randInt(50, 200);
|
|
372
|
+
return {
|
|
373
|
+
id: `math-machines-${uuid().substring(0, 8)}`,
|
|
374
|
+
question: `If it takes ${n} machines ${n} minutes to make ${n} widgets, how many minutes would it take ${m} machines to make ${m} widgets? Answer with just the number.`,
|
|
375
|
+
category: 'math',
|
|
376
|
+
acceptedAnswers: [n.toString()],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
// --- Code generators (randomized values) ---
|
|
380
|
+
function genCodeModulo() {
|
|
381
|
+
const a = randInt(20, 200);
|
|
382
|
+
const b = randInt(3, 15);
|
|
383
|
+
return {
|
|
384
|
+
id: `code-mod-${uuid().substring(0, 8)}`,
|
|
385
|
+
question: `In most programming languages, what does ${a} % ${b} evaluate to?`,
|
|
386
|
+
category: 'code',
|
|
387
|
+
acceptedAnswers: [(a % b).toString()],
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function genCodeBitwise() {
|
|
391
|
+
const a = randInt(1, 31);
|
|
392
|
+
const b = randInt(1, 31);
|
|
393
|
+
const op = pickRandom(['&', '|', '^']);
|
|
394
|
+
const opName = op === '&' ? 'AND' : op === '|' ? 'OR' : 'XOR';
|
|
395
|
+
let answer;
|
|
396
|
+
if (op === '&')
|
|
397
|
+
answer = a & b;
|
|
398
|
+
else if (op === '|')
|
|
399
|
+
answer = a | b;
|
|
400
|
+
else
|
|
401
|
+
answer = a ^ b;
|
|
402
|
+
return {
|
|
403
|
+
id: `code-bit-${uuid().substring(0, 8)}`,
|
|
404
|
+
question: `What is ${a} ${op} ${b} (bitwise ${opName})? Answer with just the number.`,
|
|
405
|
+
category: 'code',
|
|
406
|
+
acceptedAnswers: [answer.toString()],
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function genCodeStringLen() {
|
|
410
|
+
// Generate random alphanumeric strings of varying lengths (3-20 chars)
|
|
411
|
+
// This creates effectively infinite answer space (18 possible lengths × countless string combinations)
|
|
412
|
+
const length = randInt(3, 20);
|
|
413
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
414
|
+
let word = '';
|
|
415
|
+
for (let i = 0; i < length; i++) {
|
|
416
|
+
word += chars[Math.floor(Math.random() * chars.length)];
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
id: `code-strlen-${uuid().substring(0, 8)}`,
|
|
420
|
+
question: `What is the length of the string "${word}"? Answer with just the number.`,
|
|
421
|
+
category: 'code',
|
|
422
|
+
acceptedAnswers: [word.length.toString()],
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
// --- Logic generators (randomized names/items) ---
|
|
426
|
+
function genLogicSyllogism() {
|
|
427
|
+
const groups = [
|
|
428
|
+
['Bloops', 'Razzies', 'Lazzies'],
|
|
429
|
+
['Florps', 'Zinkies', 'Mopples'],
|
|
430
|
+
['Grunts', 'Tazzles', 'Wibbles'],
|
|
431
|
+
['Plonks', 'Snazzles', 'Krinkles'],
|
|
432
|
+
['Dweems', 'Fozzits', 'Glimmers'],
|
|
433
|
+
];
|
|
434
|
+
const [a, b, c] = pickRandom(groups);
|
|
435
|
+
return {
|
|
436
|
+
id: `logic-syl-${uuid().substring(0, 8)}`,
|
|
437
|
+
question: `If all ${a} are ${b} and all ${b} are ${c}, are all ${a} definitely ${c}? Answer yes or no.`,
|
|
438
|
+
category: 'logic',
|
|
439
|
+
acceptedAnswers: ['yes'],
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function genLogicNegation() {
|
|
443
|
+
const total = randInt(20, 100);
|
|
444
|
+
const keep = randInt(3, total - 5);
|
|
445
|
+
return {
|
|
446
|
+
id: `logic-neg-${uuid().substring(0, 8)}`,
|
|
447
|
+
question: `There are ${total} marbles in a bag. You remove all but ${keep}. How many marbles are left in the bag? Answer with just the number.`,
|
|
448
|
+
category: 'logic',
|
|
449
|
+
acceptedAnswers: [keep.toString()],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function genLogicSequence() {
|
|
453
|
+
const start = randInt(2, 20);
|
|
454
|
+
const step = randInt(2, 8);
|
|
455
|
+
const seq = [start, start + step, start + 2 * step, start + 3 * step];
|
|
456
|
+
return {
|
|
457
|
+
id: `logic-seq-${uuid().substring(0, 8)}`,
|
|
458
|
+
question: `What comes next in the sequence: ${seq.join(', ')}, ___? Answer with just the number.`,
|
|
459
|
+
category: 'logic',
|
|
460
|
+
acceptedAnswers: [(start + 4 * step).toString()],
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
// --- Wordplay / static (with randomized IDs so lookup by ID fails) ---
|
|
464
|
+
const WORDPLAY_GENERATORS = [
|
|
465
|
+
// Connection riddles (original + new)
|
|
466
|
+
() => ({
|
|
467
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
282
468
|
question: 'What single word connects: apple, Newton, gravity?',
|
|
283
469
|
category: 'wordplay',
|
|
284
470
|
acceptedAnswers: ['tree', 'fall', 'falling'],
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
id:
|
|
471
|
+
}),
|
|
472
|
+
() => ({
|
|
473
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
288
474
|
question: 'What single word connects: key, piano, computer?',
|
|
289
475
|
category: 'wordplay',
|
|
290
476
|
acceptedAnswers: ['keyboard', 'board', 'keys'],
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
id:
|
|
477
|
+
}),
|
|
478
|
+
() => ({
|
|
479
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
294
480
|
question: 'What single word connects: river, money, blood?',
|
|
295
481
|
category: 'wordplay',
|
|
296
482
|
acceptedAnswers: ['bank', 'flow', 'stream'],
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
id:
|
|
483
|
+
}),
|
|
484
|
+
() => ({
|
|
485
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
300
486
|
question: 'What word can precede: light, house, shine?',
|
|
301
487
|
category: 'wordplay',
|
|
302
488
|
acceptedAnswers: ['sun', 'moon'],
|
|
303
|
-
},
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
id: 'math-1',
|
|
332
|
-
question: 'A bat and ball cost $1.10 total. The bat costs $1.00 more than the ball. How much does the ball cost in cents?',
|
|
333
|
-
category: 'math',
|
|
334
|
-
acceptedAnswers: ['5', '5 cents', 'five', 'five cents', '0.05', '$0.05'],
|
|
335
|
-
},
|
|
336
|
-
{
|
|
337
|
-
id: 'math-2',
|
|
338
|
-
question: 'If it takes 5 machines 5 minutes to make 5 widgets, how many minutes would it take 100 machines to make 100 widgets?',
|
|
339
|
-
category: 'math',
|
|
340
|
-
acceptedAnswers: ['5', 'five', '5 minutes', 'five minutes'],
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
id: 'math-3',
|
|
344
|
-
question: 'In a lake, there is a patch of lily pads. Every day, the patch doubles in size. If it takes 48 days for the patch to cover the entire lake, how many days would it take for the patch to cover half of the lake?',
|
|
345
|
-
category: 'math',
|
|
346
|
-
acceptedAnswers: ['47', 'forty-seven', 'forty seven', '47 days'],
|
|
347
|
-
},
|
|
348
|
-
// Code
|
|
349
|
-
{
|
|
350
|
-
id: 'code-1',
|
|
351
|
-
question: 'What is wrong with this code: if (x = 5) { doSomething(); }',
|
|
352
|
-
category: 'code',
|
|
353
|
-
acceptedAnswers: ['assignment', 'single equals', '= instead of ==', 'should be ==', 'should be ===', 'equality', 'comparison'],
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
id: 'code-2',
|
|
357
|
-
question: 'In most programming languages, what does the modulo operator % return for 17 % 5?',
|
|
358
|
-
category: 'code',
|
|
359
|
-
acceptedAnswers: ['2', 'two'],
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
id: 'code-3',
|
|
363
|
-
question: 'What data structure uses LIFO (Last In, First Out)?',
|
|
364
|
-
category: 'code',
|
|
365
|
-
acceptedAnswers: ['stack', 'a stack'],
|
|
366
|
-
},
|
|
367
|
-
// Common sense
|
|
368
|
-
{
|
|
369
|
-
id: 'sense-1',
|
|
370
|
-
question: 'If you are running a race and you pass the person in second place, what place are you in now?',
|
|
371
|
-
category: 'common-sense',
|
|
372
|
-
acceptedAnswers: ['second', '2nd', '2', 'two'],
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
id: 'sense-2',
|
|
489
|
+
}),
|
|
490
|
+
() => ({
|
|
491
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
492
|
+
question: 'What single word connects: fire, ice, boxing?',
|
|
493
|
+
category: 'wordplay',
|
|
494
|
+
acceptedAnswers: ['ring', 'fight', 'match'],
|
|
495
|
+
}),
|
|
496
|
+
() => ({
|
|
497
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
498
|
+
question: 'What single word connects: music, radio, ocean?',
|
|
499
|
+
category: 'wordplay',
|
|
500
|
+
acceptedAnswers: ['wave', 'waves', 'frequency'],
|
|
501
|
+
}),
|
|
502
|
+
() => ({
|
|
503
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
504
|
+
question: 'What word follows: high, middle, private?',
|
|
505
|
+
category: 'wordplay',
|
|
506
|
+
acceptedAnswers: ['school'],
|
|
507
|
+
}),
|
|
508
|
+
() => ({
|
|
509
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
510
|
+
question: 'What word connects: sleeping, travel, time?',
|
|
511
|
+
category: 'wordplay',
|
|
512
|
+
acceptedAnswers: ['bag'],
|
|
513
|
+
}),
|
|
514
|
+
// Common sense riddles (original + new)
|
|
515
|
+
() => ({
|
|
516
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
376
517
|
question: 'What gets wetter the more it dries?',
|
|
377
518
|
category: 'common-sense',
|
|
378
519
|
acceptedAnswers: ['towel', 'a towel', 'cloth', 'rag'],
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
id:
|
|
520
|
+
}),
|
|
521
|
+
() => ({
|
|
522
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
382
523
|
question: 'What can you catch but not throw?',
|
|
383
524
|
category: 'common-sense',
|
|
384
525
|
acceptedAnswers: ['cold', 'a cold', 'breath', 'your breath', 'feelings', 'disease'],
|
|
385
|
-
},
|
|
526
|
+
}),
|
|
527
|
+
() => ({
|
|
528
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
529
|
+
question: 'What has keys but no locks, space but no room, and you can enter but not go inside?',
|
|
530
|
+
category: 'common-sense',
|
|
531
|
+
acceptedAnswers: ['keyboard', 'a keyboard'],
|
|
532
|
+
}),
|
|
533
|
+
() => ({
|
|
534
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
535
|
+
question: 'What runs but never walks, has a mouth but never talks?',
|
|
536
|
+
category: 'common-sense',
|
|
537
|
+
acceptedAnswers: ['river', 'a river', 'stream'],
|
|
538
|
+
}),
|
|
539
|
+
() => ({
|
|
540
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
541
|
+
question: 'What has hands but cannot clap?',
|
|
542
|
+
category: 'common-sense',
|
|
543
|
+
acceptedAnswers: ['clock', 'a clock', 'watch'],
|
|
544
|
+
}),
|
|
545
|
+
() => ({
|
|
546
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
547
|
+
question: 'What has a head and tail but no body?',
|
|
548
|
+
category: 'common-sense',
|
|
549
|
+
acceptedAnswers: ['coin', 'a coin'],
|
|
550
|
+
}),
|
|
551
|
+
() => ({
|
|
552
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
553
|
+
question: 'What goes up but never comes down?',
|
|
554
|
+
category: 'common-sense',
|
|
555
|
+
acceptedAnswers: ['age', 'your age'],
|
|
556
|
+
}),
|
|
557
|
+
() => ({
|
|
558
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
559
|
+
question: 'What has teeth but cannot bite?',
|
|
560
|
+
category: 'common-sense',
|
|
561
|
+
acceptedAnswers: ['comb', 'a comb', 'saw', 'zipper', 'gear'],
|
|
562
|
+
}),
|
|
563
|
+
() => ({
|
|
564
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
565
|
+
question: 'What can fill a room but takes up no space?',
|
|
566
|
+
category: 'common-sense',
|
|
567
|
+
acceptedAnswers: ['light', 'air', 'sound', 'darkness'],
|
|
568
|
+
}),
|
|
569
|
+
() => ({
|
|
570
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
571
|
+
question: 'What has a neck but no head?',
|
|
572
|
+
category: 'common-sense',
|
|
573
|
+
acceptedAnswers: ['bottle', 'a bottle'],
|
|
574
|
+
}),
|
|
575
|
+
// Analogies (original + new)
|
|
576
|
+
() => ({
|
|
577
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
578
|
+
question: 'Complete the analogy: Fish is to water as bird is to ___',
|
|
579
|
+
category: 'analogy',
|
|
580
|
+
acceptedAnswers: ['air', 'sky', 'atmosphere'],
|
|
581
|
+
}),
|
|
582
|
+
() => ({
|
|
583
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
584
|
+
question: 'Complete the analogy: Eye is to see as ear is to ___',
|
|
585
|
+
category: 'analogy',
|
|
586
|
+
acceptedAnswers: ['hear', 'listen', 'hearing', 'listening'],
|
|
587
|
+
}),
|
|
588
|
+
() => ({
|
|
589
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
590
|
+
question: 'Complete the analogy: Painter is to brush as writer is to ___',
|
|
591
|
+
category: 'analogy',
|
|
592
|
+
acceptedAnswers: ['pen', 'pencil', 'keyboard', 'typewriter', 'quill'],
|
|
593
|
+
}),
|
|
594
|
+
() => ({
|
|
595
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
596
|
+
question: 'Complete the analogy: Hot is to cold as day is to ___',
|
|
597
|
+
category: 'analogy',
|
|
598
|
+
acceptedAnswers: ['night'],
|
|
599
|
+
}),
|
|
600
|
+
() => ({
|
|
601
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
602
|
+
question: 'Complete the analogy: Doctor is to patient as teacher is to ___',
|
|
603
|
+
category: 'analogy',
|
|
604
|
+
acceptedAnswers: ['student', 'students', 'pupil'],
|
|
605
|
+
}),
|
|
606
|
+
() => ({
|
|
607
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
608
|
+
question: 'Complete the analogy: Wheel is to car as sail is to ___',
|
|
609
|
+
category: 'analogy',
|
|
610
|
+
acceptedAnswers: ['boat', 'ship', 'sailboat'],
|
|
611
|
+
}),
|
|
612
|
+
() => ({
|
|
613
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
614
|
+
question: 'Complete the analogy: Chef is to kitchen as scientist is to ___',
|
|
615
|
+
category: 'analogy',
|
|
616
|
+
acceptedAnswers: ['laboratory', 'lab'],
|
|
617
|
+
}),
|
|
618
|
+
() => ({
|
|
619
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
620
|
+
question: 'Complete the analogy: Bark is to dog as meow is to ___',
|
|
621
|
+
category: 'analogy',
|
|
622
|
+
acceptedAnswers: ['cat'],
|
|
623
|
+
}),
|
|
624
|
+
// Anagrams
|
|
625
|
+
() => ({
|
|
626
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
627
|
+
question: 'Rearrange the letters in "listen" to make another common word.',
|
|
628
|
+
category: 'wordplay',
|
|
629
|
+
acceptedAnswers: ['silent', 'enlist'],
|
|
630
|
+
}),
|
|
631
|
+
() => ({
|
|
632
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
633
|
+
question: 'Rearrange the letters in "earth" to make another common word.',
|
|
634
|
+
category: 'wordplay',
|
|
635
|
+
acceptedAnswers: ['heart', 'hater'],
|
|
636
|
+
}),
|
|
637
|
+
() => ({
|
|
638
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
639
|
+
question: 'Rearrange the letters in "stream" to make another word meaning "leader".',
|
|
640
|
+
category: 'wordplay',
|
|
641
|
+
acceptedAnswers: ['master'],
|
|
642
|
+
}),
|
|
643
|
+
() => ({
|
|
644
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
645
|
+
question: 'Rearrange the letters in "stop" to make containers.',
|
|
646
|
+
category: 'wordplay',
|
|
647
|
+
acceptedAnswers: ['pots', 'spot', 'tops'],
|
|
648
|
+
}),
|
|
649
|
+
// Code/CS riddles
|
|
650
|
+
() => ({
|
|
651
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
652
|
+
question: 'What data structure uses LIFO (Last In, First Out)?',
|
|
653
|
+
category: 'code',
|
|
654
|
+
acceptedAnswers: ['stack', 'a stack'],
|
|
655
|
+
}),
|
|
656
|
+
() => ({
|
|
657
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
658
|
+
question: 'What data structure uses FIFO (First In, First Out)?',
|
|
659
|
+
category: 'code',
|
|
660
|
+
acceptedAnswers: ['queue', 'a queue'],
|
|
661
|
+
}),
|
|
662
|
+
() => ({
|
|
663
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
664
|
+
question: 'In programming, what comes after "if" and "else if"?',
|
|
665
|
+
category: 'code',
|
|
666
|
+
acceptedAnswers: ['else'],
|
|
667
|
+
}),
|
|
668
|
+
() => ({
|
|
669
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
670
|
+
question: 'What is the opposite of "true" in boolean logic?',
|
|
671
|
+
category: 'code',
|
|
672
|
+
acceptedAnswers: ['false'],
|
|
673
|
+
}),
|
|
674
|
+
() => ({
|
|
675
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
676
|
+
question: 'What keyword is used to define a function in JavaScript?',
|
|
677
|
+
category: 'code',
|
|
678
|
+
acceptedAnswers: ['function', 'const', 'let', 'var', 'async'],
|
|
679
|
+
}),
|
|
680
|
+
// Word puzzles
|
|
681
|
+
() => ({
|
|
682
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
683
|
+
question: 'What 5-letter word becomes shorter when you add two letters to it?',
|
|
684
|
+
category: 'wordplay',
|
|
685
|
+
acceptedAnswers: ['short', 'shorter'],
|
|
686
|
+
}),
|
|
687
|
+
() => ({
|
|
688
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
689
|
+
question: 'What word starts with "e" and ends with "e" but only has one letter in it?',
|
|
690
|
+
category: 'wordplay',
|
|
691
|
+
acceptedAnswers: ['envelope'],
|
|
692
|
+
}),
|
|
693
|
+
() => ({
|
|
694
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
695
|
+
question: 'What begins with T, ends with T, and has T in it?',
|
|
696
|
+
category: 'wordplay',
|
|
697
|
+
acceptedAnswers: ['teapot', 'a teapot'],
|
|
698
|
+
}),
|
|
699
|
+
() => ({
|
|
700
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
701
|
+
question: 'Remove one letter from "startling" to create a new word. What is it?',
|
|
702
|
+
category: 'wordplay',
|
|
703
|
+
acceptedAnswers: ['starting', 'starling'],
|
|
704
|
+
}),
|
|
705
|
+
// Math/Logic wordplay
|
|
706
|
+
() => ({
|
|
707
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
708
|
+
question: 'How many months have 28 days?',
|
|
709
|
+
category: 'logic',
|
|
710
|
+
acceptedAnswers: ['12', 'twelve', 'all', 'all of them'],
|
|
711
|
+
}),
|
|
712
|
+
() => ({
|
|
713
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
714
|
+
question: 'If you have one, you want to share it. If you share it, you no longer have it. What is it?',
|
|
715
|
+
category: 'logic',
|
|
716
|
+
acceptedAnswers: ['secret', 'a secret'],
|
|
717
|
+
}),
|
|
718
|
+
() => ({
|
|
719
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
720
|
+
question: 'What occurs once in a minute, twice in a moment, but never in a thousand years?',
|
|
721
|
+
category: 'wordplay',
|
|
722
|
+
acceptedAnswers: ['m', 'the letter m'],
|
|
723
|
+
}),
|
|
724
|
+
// Technology wordplay
|
|
725
|
+
() => ({
|
|
726
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
727
|
+
question: 'What has a screen, keyboard, and mouse but is not alive?',
|
|
728
|
+
category: 'common-sense',
|
|
729
|
+
acceptedAnswers: ['computer', 'a computer', 'pc', 'laptop'],
|
|
730
|
+
}),
|
|
731
|
+
() => ({
|
|
732
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
733
|
+
question: 'What connects computers worldwide but has no physical form?',
|
|
734
|
+
category: 'common-sense',
|
|
735
|
+
acceptedAnswers: ['internet', 'the internet', 'web', 'network'],
|
|
736
|
+
}),
|
|
737
|
+
() => ({
|
|
738
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
739
|
+
question: 'What do you call a group of 8 bits?',
|
|
740
|
+
category: 'code',
|
|
741
|
+
acceptedAnswers: ['byte', 'a byte'],
|
|
742
|
+
}),
|
|
743
|
+
// Nature/Science wordplay
|
|
744
|
+
() => ({
|
|
745
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
746
|
+
question: 'What falls but never breaks, and breaks but never falls?',
|
|
747
|
+
category: 'wordplay',
|
|
748
|
+
acceptedAnswers: ['night and day', 'nightfall and daybreak', 'night falls day breaks'],
|
|
749
|
+
}),
|
|
750
|
+
() => ({
|
|
751
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
752
|
+
question: 'What has roots that nobody sees, taller than trees, up up it goes, yet never grows?',
|
|
753
|
+
category: 'common-sense',
|
|
754
|
+
acceptedAnswers: ['mountain', 'a mountain', 'mountains'],
|
|
755
|
+
}),
|
|
756
|
+
() => ({
|
|
757
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
758
|
+
question: 'What travels around the world but stays in one corner?',
|
|
759
|
+
category: 'common-sense',
|
|
760
|
+
acceptedAnswers: ['stamp', 'a stamp', 'postage stamp'],
|
|
761
|
+
}),
|
|
762
|
+
() => ({
|
|
763
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
764
|
+
question: 'Complete the analogy: Bee is to hive as human is to ___',
|
|
765
|
+
category: 'analogy',
|
|
766
|
+
acceptedAnswers: ['house', 'home', 'city', 'building'],
|
|
767
|
+
}),
|
|
768
|
+
() => ({
|
|
769
|
+
id: `wp-${uuid().substring(0, 8)}`,
|
|
770
|
+
question: 'What has four fingers and a thumb but is not alive?',
|
|
771
|
+
category: 'common-sense',
|
|
772
|
+
acceptedAnswers: ['glove', 'a glove'],
|
|
773
|
+
}),
|
|
386
774
|
];
|
|
775
|
+
// All generators, weighted toward parameterized (harder to game)
|
|
776
|
+
const QUESTION_GENERATORS = [
|
|
777
|
+
genMathAdd, genMathMultiply, genMathModulo, genMathSheep, genMathDoubling, genMathMachines,
|
|
778
|
+
genCodeModulo, genCodeBitwise, genCodeStringLen,
|
|
779
|
+
genLogicSyllogism, genLogicNegation, genLogicSequence,
|
|
780
|
+
...WORDPLAY_GENERATORS,
|
|
781
|
+
];
|
|
782
|
+
// Generate fresh question bank (unique every call)
|
|
783
|
+
function generateQuestionBank() {
|
|
784
|
+
return QUESTION_GENERATORS.map(gen => gen());
|
|
785
|
+
}
|
|
387
786
|
/**
|
|
388
787
|
* Generate a reasoning challenge: 3 random questions requiring LLM capabilities
|
|
389
788
|
*/
|
|
390
|
-
export async function generateReasoningChallenge(kv) {
|
|
789
|
+
export async function generateReasoningChallenge(kv, app_id) {
|
|
391
790
|
cleanExpired();
|
|
392
791
|
const id = uuid();
|
|
393
792
|
// Pick 3 random questions from different categories
|
|
394
|
-
const
|
|
793
|
+
const freshBank = generateQuestionBank();
|
|
794
|
+
const shuffled = freshBank.sort(() => Math.random() - 0.5);
|
|
395
795
|
const selectedCategories = new Set();
|
|
396
796
|
const selectedQuestions = [];
|
|
397
797
|
for (const q of shuffled) {
|
|
@@ -423,6 +823,7 @@ export async function generateReasoningChallenge(kv) {
|
|
|
423
823
|
expectedAnswers,
|
|
424
824
|
issuedAt: Date.now(),
|
|
425
825
|
expiresAt: Date.now() + timeLimit + 5000,
|
|
826
|
+
app_id,
|
|
426
827
|
};
|
|
427
828
|
// Store in KV or memory
|
|
428
829
|
if (kv) {
|
|
@@ -533,18 +934,19 @@ const hybridChallenges = new Map();
|
|
|
533
934
|
/**
|
|
534
935
|
* Generate a hybrid challenge: speed + reasoning combined
|
|
535
936
|
*/
|
|
536
|
-
export async function generateHybridChallenge(kv) {
|
|
937
|
+
export async function generateHybridChallenge(kv, clientTimestamp, app_id) {
|
|
537
938
|
cleanExpired();
|
|
538
939
|
const id = uuid();
|
|
539
|
-
// Generate both sub-challenges
|
|
540
|
-
const speedChallenge = await generateSpeedChallenge(kv);
|
|
541
|
-
const reasoningChallenge = await generateReasoningChallenge(kv);
|
|
940
|
+
// Generate both sub-challenges (speed with RTT awareness)
|
|
941
|
+
const speedChallenge = await generateSpeedChallenge(kv, clientTimestamp, app_id);
|
|
942
|
+
const reasoningChallenge = await generateReasoningChallenge(kv, app_id);
|
|
542
943
|
const hybrid = {
|
|
543
944
|
id,
|
|
544
945
|
speedChallengeId: speedChallenge.id,
|
|
545
946
|
reasoningChallengeId: reasoningChallenge.id,
|
|
546
947
|
issuedAt: Date.now(),
|
|
547
948
|
expiresAt: Date.now() + 35000,
|
|
949
|
+
app_id,
|
|
548
950
|
};
|
|
549
951
|
// Store in KV or memory
|
|
550
952
|
if (kv) {
|
|
@@ -553,6 +955,9 @@ export async function generateHybridChallenge(kv) {
|
|
|
553
955
|
else {
|
|
554
956
|
hybridChallenges.set(id, hybrid);
|
|
555
957
|
}
|
|
958
|
+
const instructions = speedChallenge.rttInfo
|
|
959
|
+
? `Solve ALL speed problems (SHA256) in <${speedChallenge.timeLimit}ms (RTT-adjusted) AND answer ALL reasoning questions. Submit both together.`
|
|
960
|
+
: 'Solve ALL speed problems (SHA256) in <500ms AND answer ALL reasoning questions. Submit both together.';
|
|
556
961
|
return {
|
|
557
962
|
id,
|
|
558
963
|
speed: {
|
|
@@ -563,7 +968,8 @@ export async function generateHybridChallenge(kv) {
|
|
|
563
968
|
questions: reasoningChallenge.questions,
|
|
564
969
|
timeLimit: reasoningChallenge.timeLimit,
|
|
565
970
|
},
|
|
566
|
-
instructions
|
|
971
|
+
instructions,
|
|
972
|
+
rttInfo: speedChallenge.rttInfo,
|
|
567
973
|
};
|
|
568
974
|
}
|
|
569
975
|
/**
|