aap-agent-server 2.0.0 → 2.6.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/challenges.js CHANGED
@@ -1,11 +1,20 @@
1
1
  /**
2
- * @aap/server - Challenge Generator v2.0
2
+ * @aap/server - Challenge Generator v2.6
3
3
  *
4
- * "Deterministic Instruction Following"
5
- * - Natural language instructions (requires LLM to understand)
6
- * - Deterministic answers (server knows the correct answer)
4
+ * "Burst Mode with Entropy Injection"
5
+ * - 7 challenges in 6 seconds (humans cannot pass)
6
+ * - Salt injection prevents caching attacks
7
+ * - Natural language instructions (requires LLM)
7
8
  *
8
- * Principle: Instructions in natural language, but answers are verifiable.
9
+ * v2.6 Changes:
10
+ * - BATCH_SIZE: 5 → 7 (more problems = harder for humans)
11
+ * - MAX_RESPONSE_TIME_MS: 8000 → 6000 (tighter window)
12
+ * - Random chance reduced: (1/3)^7 = 0.05%
13
+ *
14
+ * v2.5 Changes:
15
+ * - BATCH_SIZE: 3 → 5
16
+ * - MAX_RESPONSE_TIME_MS: 12000 → 8000
17
+ * - Salt injection in challenges (must be echoed back)
9
18
  */
10
19
 
11
20
  import { createHash } from 'node:crypto';
@@ -14,12 +23,12 @@ import { createHash } from 'node:crypto';
14
23
  * Word pools for dynamic challenge generation
15
24
  */
16
25
  const WORD_POOLS = {
17
- animals: ['cat', 'dog', 'rabbit', 'tiger', 'lion', 'elephant', 'giraffe', 'penguin', 'eagle', 'shark'],
18
- fruits: ['apple', 'banana', 'orange', 'grape', 'strawberry', 'watermelon', 'peach', 'kiwi', 'mango', 'cherry'],
19
- colors: ['red', 'blue', 'yellow', 'green', 'purple', 'orange', 'pink', 'black', 'white', 'brown'],
20
- countries: ['Korea', 'Japan', 'USA', 'UK', 'France', 'Germany', 'Australia', 'Canada', 'Brazil', 'India'],
21
- verbs: ['runs', 'eats', 'sleeps', 'plays', 'works', 'studies', 'travels', 'cooks'],
22
- adjectives: ['big', 'small', 'fast', 'slow', 'beautiful', 'cute', 'delicious', 'interesting']
26
+ animals: ['cat', 'dog', 'rabbit', 'tiger', 'lion', 'elephant', 'giraffe', 'penguin', 'eagle', 'shark', 'wolf', 'bear', 'fox', 'deer', 'owl'],
27
+ fruits: ['apple', 'banana', 'orange', 'grape', 'strawberry', 'watermelon', 'peach', 'kiwi', 'mango', 'cherry', 'lemon', 'lime', 'pear', 'plum'],
28
+ colors: ['red', 'blue', 'yellow', 'green', 'purple', 'orange', 'pink', 'black', 'white', 'brown', 'gray', 'cyan', 'magenta'],
29
+ countries: ['Korea', 'Japan', 'USA', 'UK', 'France', 'Germany', 'Australia', 'Canada', 'Brazil', 'India', 'Italy', 'Spain', 'Mexico'],
30
+ verbs: ['runs', 'eats', 'sleeps', 'plays', 'works', 'studies', 'travels', 'cooks', 'reads', 'writes', 'sings', 'dances'],
31
+ adjectives: ['big', 'small', 'fast', 'slow', 'beautiful', 'cute', 'delicious', 'interesting', 'bright', 'dark']
23
32
  };
24
33
 
25
34
  /**
@@ -49,34 +58,59 @@ function seededNumber(nonce, offset, min, max) {
49
58
  return (seed % (max - min + 1)) + min;
50
59
  }
51
60
 
61
+ /**
62
+ * Generate salt from nonce (for entropy injection)
63
+ * @param {string} nonce - Base nonce
64
+ * @param {number} offset - Offset for variation
65
+ * @returns {string} 6-character salt
66
+ */
67
+ function generateSalt(nonce, offset = 0) {
68
+ return nonce.slice(offset, offset + 6).toUpperCase();
69
+ }
70
+
52
71
  /**
53
72
  * Challenge type definitions
54
73
  */
55
74
  export const CHALLENGE_TYPES = {
56
75
  /**
57
- * Extract entities from natural language sentence
76
+ * Extract entities from natural language sentence (HARD - more distractors)
58
77
  */
59
78
  nlp_extract: {
60
79
  generate: (nonce) => {
80
+ const salt = generateSalt(nonce, 0);
61
81
  const category = ['animals', 'fruits', 'colors'][parseInt(nonce[0], 16) % 3];
62
82
  const pool = WORD_POOLS[category];
63
- const targets = seededSelect(pool, nonce, 2, 0);
83
+ const targets = seededSelect(pool, nonce, 3, 0); // 3 targets now
84
+ const distractorPool = category === 'animals' ? 'fruits' : category === 'fruits' ? 'colors' : 'animals';
85
+ const distractors = seededSelect(WORD_POOLS[distractorPool], nonce, 2, 8);
64
86
  const verb = seededSelect(WORD_POOLS.verbs, nonce, 1, 4)[0];
87
+ const adj = seededSelect(WORD_POOLS.adjectives, nonce, 1, 6)[0];
65
88
 
66
- const sentence = `The ${targets[0]} and ${targets[1]} ${verb} in the park.`;
89
+ // Complex sentence with distractors mixed in
90
+ const templates = [
91
+ `The ${adj} ${targets[0]}, a ${distractors[0]}, the ${targets[1]}, and ${targets[2]} all ${verb} near the ${distractors[1]}.`,
92
+ `I saw ${targets[0]} and ${distractors[0]} yesterday, but today only ${targets[1]}, ${targets[2]}, and a ${distractors[1]} appeared.`,
93
+ `Between the ${distractors[0]} and ${distractors[1]}, there were ${targets[0]}, ${targets[1]}, and ${targets[2]} ${verb}ing.`
94
+ ];
95
+ const sentence = templates[parseInt(nonce[12], 16) % templates.length];
67
96
 
68
97
  return {
69
- challenge_string: `Extract only the ${category} from the following sentence and respond as a JSON array.
98
+ challenge_string: `[REQ-${salt}] Extract ONLY the ${category} from this sentence (ignore other categories).
70
99
  Sentence: "${sentence}"
71
- Response format: {"items": ["item1", "item2"]}`,
72
- expected: targets.sort(),
100
+ Response format: {"salt": "${salt}", "items": ["item1", "item2", "item3"]}`,
101
+ expected: { salt, items: targets.map(s => s.toLowerCase()).sort() },
73
102
  validate: (solution) => {
74
103
  try {
75
104
  const match = solution.match(/\{[\s\S]*\}/);
76
105
  if (!match) return false;
77
106
  const obj = JSON.parse(match[0]);
107
+
108
+ // Check salt
109
+ if (obj.salt !== salt) return false;
110
+
78
111
  const items = (obj.items || obj.animals || obj.fruits || obj.colors || []).map(s => s.toLowerCase()).sort();
79
- return JSON.stringify(items) === JSON.stringify(targets.map(s => s.toLowerCase()).sort());
112
+ const expected = targets.map(s => s.toLowerCase()).sort();
113
+ return JSON.stringify(items) === JSON.stringify(expected);
80
114
  } catch { return false; }
81
115
  }
82
116
  };
@@ -84,41 +118,59 @@ Response format: {"items": ["item1", "item2"]}`,
84
118
  },
85
119
 
86
120
  /**
87
- * Math problem expressed in natural language
121
+ * Math problem expressed in natural language (EXTREME)
88
122
  */
89
123
  nlp_math: {
90
124
  generate: (nonce) => {
91
- const a = seededNumber(nonce, 0, 10, 50);
92
- const b = seededNumber(nonce, 2, 5, 20);
93
- const c = seededNumber(nonce, 4, 2, 5);
125
+ const salt = generateSalt(nonce, 2);
126
+ const a = seededNumber(nonce, 0, 50, 200);
127
+ const b = seededNumber(nonce, 2, 10, 50);
128
+ const c = seededNumber(nonce, 4, 2, 9);
129
+ const d = seededNumber(nonce, 6, 5, 25);
130
+ const e = seededNumber(nonce, 8, 2, 6);
94
131
 
95
132
  const templates = [
96
133
  {
97
- text: `Subtract ${b} from ${a}, then multiply the result by ${c}.`,
98
- answer: (a - b) * c
134
+ text: `Start with ${a}. Subtract ${b}. Multiply that by ${c}. Divide the result by ${e}. Finally, add ${d}. What's the final value?`,
135
+ answer: (((a - b) * c) / e) + d
99
136
  },
100
137
  {
101
- text: `Add ${a} and ${b} together, then divide by ${c}.`,
102
- answer: (a + b) / c
138
+ text: `Compute: ((${a} + ${b}) × ${c} - ${d}) ÷ ${e}. Give the result rounded to two decimal places.`,
139
+ answer: ((a + b) * c - d) / e
103
140
  },
104
141
  {
105
- text: `Divide ${a} by ${c}, then add ${b} to the result.`,
106
- answer: a / c + b
142
+ text: `Take the sum of ${a} and ${b}. Square root of that sum, rounded down. Then multiply by ${c} and subtract ${d}.`,
143
+ answer: Math.floor(Math.sqrt(a + b)) * c - d
144
+ },
145
+ {
146
+ text: `${a} divided by ${c}, plus ${b} divided by ${e}, minus ${d}. Round to nearest integer.`,
147
+ answer: Math.round(a / c + b / e - d)
148
+ },
149
+ {
150
+ text: `Triple ${a}, halve that result, add ${b}, then take away ${d}. Multiply everything by ${e}. Final answer?`,
151
+ answer: ((a * 3 / 2) + b - d) * e
152
+ },
153
+ {
154
+ text: `What is ${a} mod ${c} (remainder), plus ${b} mod ${e}, times ${d}?`,
155
+ answer: ((a % c) + (b % e)) * d
107
156
  }
108
157
  ];
109
158
 
110
- const template = templates[parseInt(nonce[6], 16) % templates.length];
159
+ const template = templates[parseInt(nonce[10], 16) % templates.length];
111
160
  const expected = Math.round(template.answer * 100) / 100;
112
161
 
113
162
  return {
114
- challenge_string: `${template.text}
115
- Response format: {"result": number}`,
116
- expected,
163
+ challenge_string: `[REQ-${salt}] ${template.text}
164
+ Response format: {"salt": "${salt}", "result": number}`,
165
+ expected: { salt, result: expected },
117
166
  validate: (solution) => {
118
167
  try {
119
168
  const match = solution.match(/\{[\s\S]*\}/);
120
169
  if (!match) return false;
121
170
  const obj = JSON.parse(match[0]);
171
+
172
+ if (obj.salt !== salt) return false;
173
+
122
174
  const result = parseFloat(obj.result);
123
175
  return Math.abs(result - expected) < 0.01;
124
176
  } catch { return false; }
@@ -128,43 +180,68 @@ Response format: {"result": number}`,
128
180
  },
129
181
 
130
182
  /**
131
- * String transformation described in natural language
183
+ * String transformation described in natural language (EXTREME - multi-step transforms)
132
184
  */
133
185
  nlp_transform: {
134
186
  generate: (nonce) => {
135
- const input = nonce.slice(0, 6);
136
- const transformType = parseInt(nonce[6], 16) % 4;
187
+ const salt = generateSalt(nonce, 4);
188
+ const input = nonce.slice(6, 16); // Longer input: 10 chars
189
+ const transformType = parseInt(nonce[16], 16) % 6;
137
190
 
138
191
  let instruction, expected;
139
192
 
140
193
  switch (transformType) {
141
194
  case 0:
142
- instruction = `Reverse the string "${input}" and convert it to uppercase.`;
143
- expected = input.split('').reverse().join('').toUpperCase();
195
+ // Reverse uppercase take first 5
196
+ expected = input.split('').reverse().join('').toUpperCase().slice(0, 5);
197
+ instruction = `Take "${input}": reverse it, convert to uppercase, then return only the first 5 characters.`;
144
198
  break;
145
199
  case 1:
146
- instruction = `Extract only the digits from "${input}" and calculate their sum.`;
147
- expected = input.split('').filter(c => /\d/.test(c)).reduce((a, b) => a + parseInt(b), 0);
200
+ // Extract digits sum return as string with prefix
201
+ const digitSum = input.split('').filter(c => /\d/.test(c)).reduce((a, b) => a + parseInt(b), 0);
202
+ expected = `SUM:${digitSum}`;
203
+ instruction = `From "${input}": extract all digits, sum them, and format as "SUM:X" where X is the total.`;
148
204
  break;
149
205
  case 2:
150
- instruction = `Extract only the letters from "${input}" and sort them alphabetically.`;
151
- expected = input.split('').filter(c => /[a-zA-Z]/.test(c)).sort().join('');
206
+ // Letters only sort reverse join with dots
207
+ expected = input.split('').filter(c => /[a-zA-Z]/.test(c)).sort().reverse().join('.');
208
+ instruction = `From "${input}": extract letters only, sort alphabetically, reverse that order, join with dots.`;
152
209
  break;
153
210
  case 3:
154
- instruction = `Insert a hyphen "-" between each character of "${input}".`;
155
- expected = input.split('').join('-');
211
+ // Alternate case: odd positions uppercase, even lowercase
212
+ expected = input.split('').map((c, i) => i % 2 === 0 ? c.toLowerCase() : c.toUpperCase()).join('');
213
+ instruction = `Transform "${input}": characters at even positions (0,2,4...) lowercase, odd positions (1,3,5...) uppercase.`;
214
+ break;
215
+ case 4:
216
+ // Count each char type
217
+ const letters = input.split('').filter(c => /[a-zA-Z]/.test(c)).length;
218
+ const digits = input.split('').filter(c => /\d/.test(c)).length;
219
+ expected = `L${letters}D${digits}`;
220
+ instruction = `Analyze "${input}": count letters and digits. Format answer as "LxDy" where x=letter count, y=digit count.`;
221
+ break;
222
+ case 5:
223
+ // Replace vowels with *, consonants with #, keep digits
224
+ expected = input.split('').map(c => {
225
+ if (/[aeiouAEIOU]/.test(c)) return '*';
226
+ if (/[a-zA-Z]/.test(c)) return '#';
227
+ return c;
228
+ }).join('');
229
+ instruction = `Transform "${input}": replace vowels with "*", consonants with "#", keep digits unchanged.`;
156
230
  break;
157
231
  }
158
232
 
159
233
  return {
160
- challenge_string: `${instruction}
161
- Response format: {"output": "result"}`,
162
- expected,
234
+ challenge_string: `[REQ-${salt}] ${instruction}
235
+ Response format: {"salt": "${salt}", "output": "result"}`,
236
+ expected: { salt, output: expected },
163
237
  validate: (solution) => {
164
238
  try {
165
239
  const match = solution.match(/\{[\s\S]*\}/);
166
240
  if (!match) return false;
167
241
  const obj = JSON.parse(match[0]);
242
+
243
+ if (obj.salt !== salt) return false;
244
+
168
245
  const output = String(obj.output);
169
246
  return output === String(expected) || output.toLowerCase() === String(expected).toLowerCase();
170
247
  } catch { return false; }
@@ -174,40 +251,84 @@ Response format: {"output": "result"}`,
174
251
  },
175
252
 
176
253
  /**
177
- * Conditional logic
254
+ * Conditional logic (EXTREME - multi-layer nested conditions)
178
255
  */
179
256
  nlp_logic: {
180
257
  generate: (nonce) => {
181
- const a = seededNumber(nonce, 0, 10, 100);
182
- const b = seededNumber(nonce, 2, 10, 100);
183
- const threshold = seededNumber(nonce, 4, 20, 80);
258
+ const salt = generateSalt(nonce, 6);
259
+ const a = seededNumber(nonce, 0, 20, 150);
260
+ const b = seededNumber(nonce, 2, 20, 150);
261
+ const c = seededNumber(nonce, 4, 20, 100);
262
+ const d = seededNumber(nonce, 6, 10, 50);
263
+ const threshold = seededNumber(nonce, 8, 40, 100);
184
264
 
185
265
  const templates = [
186
266
  {
187
- text: `If the larger number between ${a} and ${b} is greater than ${threshold}, answer "YES". Otherwise, answer "NO".`,
188
- answer: Math.max(a, b) > threshold ? "YES" : "NO"
267
+ text: `Let X=${a}, Y=${b}, Z=${c}, W=${d}. If (X > Y AND Z > W) OR (X < Y AND Z < W), answer "CONSISTENT". If (X > Y AND Z < W) OR (X < Y AND Z > W), answer "CROSSED". Otherwise, answer "EQUAL".`,
268
+ answer: ((a > b && c > d) || (a < b && c < d)) ? "CONSISTENT" : ((a > b && c < d) || (a < b && c > d)) ? "CROSSED" : "EQUAL"
189
269
  },
190
270
  {
191
- text: `If the sum of ${a} and ${b} is less than ${threshold * 2}, answer "SMALL". Otherwise, answer "LARGE".`,
192
- answer: (a + b) < (threshold * 2) ? "SMALL" : "LARGE"
271
+ text: `Given four numbers [${a}, ${b}, ${c}, ${d}]: Count how many are divisible by 3. If count is 0, say "NONE". If 1-2, say "FEW". If 3-4, say "MANY".`,
272
+ answer: (() => {
273
+ const count = [a, b, c, d].filter(n => n % 3 === 0).length;
274
+ return count === 0 ? "NONE" : count <= 2 ? "FEW" : "MANY";
275
+ })()
193
276
  },
194
277
  {
195
- text: `If ${a} is even and ${b} is odd, answer "MIXED". Otherwise, answer "SAME".`,
196
- answer: (a % 2 === 0 && b % 2 === 1) ? "MIXED" : "SAME"
278
+ text: `Evaluate: Is (${a} + ${b}) greater than (${c} + ${d})? AND is (${a} × ${d}) less than (${b} × ${c})? If BOTH true: "ALPHA". If NEITHER true: "GAMMA". Otherwise: "BETA".`,
279
+ answer: (() => {
280
+ const cond1 = (a + b) > (c + d);
281
+ const cond2 = (a * d) < (b * c);
282
+ return (cond1 && cond2) ? "ALPHA" : (!cond1 && !cond2) ? "GAMMA" : "BETA";
283
+ })()
284
+ },
285
+ {
286
+ text: `Numbers: ${a}, ${b}, ${c}, ${d}. First, find the median (average of middle two when sorted). If median > ${threshold}, output "HIGH". If median < ${threshold / 2}, output "LOW". Otherwise, output "MID".`,
287
+ answer: (() => {
288
+ const sorted = [a, b, c, d].sort((x, y) => x - y);
289
+ const median = (sorted[1] + sorted[2]) / 2;
290
+ return median > threshold ? "HIGH" : median < (threshold / 2) ? "LOW" : "MID";
291
+ })()
292
+ },
293
+ {
294
+ text: `Check these conditions for [${a}, ${b}, ${c}, ${d}]: (1) Sum > ${threshold * 3}? (2) Product of smallest two < ${threshold * 10}? (3) Largest is even? Count TRUE conditions. Answer with the count (0-3).`,
295
+ answer: (() => {
296
+ const sorted = [a, b, c, d].sort((x, y) => x - y);
297
+ let count = 0;
298
+ if (a + b + c + d > threshold * 3) count++;
299
+ if (sorted[0] * sorted[1] < threshold * 10) count++;
300
+ if (sorted[3] % 2 === 0) count++;
301
+ return String(count);
302
+ })()
303
+ },
304
+ {
305
+ text: `If ${a} AND ${b} are both prime, answer "TWIN". If exactly one is prime, answer "SOLO". If neither is prime, answer "NONE". (Hint: primes are only divisible by 1 and themselves)`,
306
+ answer: (() => {
307
+ const isPrime = n => {
308
+ if (n < 2) return false;
309
+ for (let i = 2; i <= Math.sqrt(n); i++) if (n % i === 0) return false;
310
+ return true;
311
+ };
312
+ const ap = isPrime(a), bp = isPrime(b);
313
+ return (ap && bp) ? "TWIN" : (ap || bp) ? "SOLO" : "NONE";
314
+ })()
197
315
  }
198
316
  ];
199
317
 
200
- const template = templates[parseInt(nonce[6], 16) % templates.length];
318
+ const template = templates[parseInt(nonce[10], 16) % templates.length];
201
319
 
202
320
  return {
203
- challenge_string: `${template.text}
204
- Response format: {"answer": "your answer"}`,
205
- expected: template.answer,
321
+ challenge_string: `[REQ-${salt}] ${template.text}
322
+ Response format: {"salt": "${salt}", "answer": "your answer"}`,
323
+ expected: { salt, answer: template.answer },
206
324
  validate: (solution) => {
207
325
  try {
208
326
  const match = solution.match(/\{[\s\S]*\}/);
209
327
  if (!match) return false;
210
328
  const obj = JSON.parse(match[0]);
329
+
330
+ if (obj.salt !== salt) return false;
331
+
211
332
  return obj.answer?.toUpperCase() === template.answer.toUpperCase();
212
333
  } catch { return false; }
213
334
  }
@@ -216,35 +337,56 @@ Response format: {"answer": "your answer"}`,
216
337
  },
217
338
 
218
339
  /**
219
- * Counting task
340
+ * Counting task (EXTREME - multiple categories, complex sentences)
220
341
  */
221
342
  nlp_count: {
222
343
  generate: (nonce) => {
223
- const category = ['animals', 'fruits', 'colors'][parseInt(nonce[0], 16) % 3];
224
- const pool = WORD_POOLS[category];
225
- const count1 = seededNumber(nonce, 0, 2, 4);
226
- const count2 = seededNumber(nonce, 2, 1, 3);
344
+ const salt = generateSalt(nonce, 8);
345
+ const targetCategory = ['animals', 'fruits', 'colors'][parseInt(nonce[0], 16) % 3];
346
+ const pool = WORD_POOLS[targetCategory];
347
+ const targetCount = seededNumber(nonce, 0, 3, 6);
348
+
349
+ // Add distractors from OTHER categories
350
+ const distractor1Cat = targetCategory === 'animals' ? 'fruits' : 'animals';
351
+ const distractor2Cat = targetCategory === 'colors' ? 'fruits' : 'colors';
352
+ const distractorCount1 = seededNumber(nonce, 2, 2, 4);
353
+ const distractorCount2 = seededNumber(nonce, 4, 2, 3);
227
354
 
228
- const items1 = seededSelect(pool, nonce, count1, 0);
229
- const items2 = seededSelect(WORD_POOLS.countries, nonce, count2, 8);
355
+ const targets = seededSelect(pool, nonce, targetCount, 0);
356
+ const distractors1 = seededSelect(WORD_POOLS[distractor1Cat], nonce, distractorCount1, 6);
357
+ const distractors2 = seededSelect(WORD_POOLS[distractor2Cat], nonce, distractorCount2, 10);
358
+ const countryDistractors = seededSelect(WORD_POOLS.countries, nonce, 2, 14);
230
359
 
231
- // Create sentence with mixed items
232
- const allItems = [...items1, ...items2].sort(() =>
233
- parseInt(nonce.slice(10, 12), 16) % 2 - 0.5
234
- );
235
- const sentence = `I see ${allItems.join(', ')} in the picture.`;
360
+ // Mix everything together
361
+ const allItems = [...targets, ...distractors1, ...distractors2, ...countryDistractors];
362
+ // Shuffle using nonce
363
+ for (let i = allItems.length - 1; i > 0; i--) {
364
+ const j = parseInt(nonce.slice(i % 28, (i % 28) + 2), 16) % (i + 1);
365
+ [allItems[i], allItems[j]] = [allItems[j], allItems[i]];
366
+ }
367
+
368
+ const templates = [
369
+ `At the market, I noticed: ${allItems.join(', ')}. Quite a mix!`,
370
+ `The list contains: ${allItems.join(', ')}. Some things don't belong.`,
371
+ `Inventory check: ${allItems.join(', ')}. Sort by category mentally.`,
372
+ `Mixed bag: ${allItems.join(', ')}. Focus on what matters.`
373
+ ];
374
+ const sentence = templates[parseInt(nonce[20], 16) % templates.length];
236
375
 
237
376
  return {
238
- challenge_string: `Count only the ${category} in the following sentence.
239
- Sentence: "${sentence}"
240
- Response format: {"count": number}`,
241
- expected: count1,
377
+ challenge_string: `[REQ-${salt}] Count ONLY the ${targetCategory} in this text. Ignore all other categories (other nouns are distractors).
378
+ Text: "${sentence}"
379
+ Response format: {"salt": "${salt}", "count": number}`,
380
+ expected: { salt, count: targetCount },
242
381
  validate: (solution) => {
243
382
  try {
244
383
  const match = solution.match(/\{[\s\S]*\}/);
245
384
  if (!match) return false;
246
385
  const obj = JSON.parse(match[0]);
247
- return parseInt(obj.count) === count1;
386
+
387
+ if (obj.salt !== salt) return false;
388
+
389
+ return parseInt(obj.count) === targetCount;
248
390
  } catch { return false; }
249
391
  }
250
392
  };
@@ -252,38 +394,95 @@ Response format: {"count": number}`,
252
394
  },
253
395
 
254
396
  /**
255
- * Multi-step instruction following
397
+ * Multi-step instruction following (EXTREME - 5-6 steps)
256
398
  */
257
399
  nlp_multistep: {
258
400
  generate: (nonce) => {
401
+ const salt = generateSalt(nonce, 10);
259
402
  const numbers = [
260
- seededNumber(nonce, 0, 1, 9),
261
- seededNumber(nonce, 2, 1, 9),
262
- seededNumber(nonce, 4, 1, 9),
263
- seededNumber(nonce, 6, 1, 9)
403
+ seededNumber(nonce, 0, 5, 30),
404
+ seededNumber(nonce, 2, 5, 30),
405
+ seededNumber(nonce, 4, 5, 30),
406
+ seededNumber(nonce, 6, 5, 30),
407
+ seededNumber(nonce, 8, 5, 30),
408
+ seededNumber(nonce, 10, 5, 30)
264
409
  ];
265
410
 
266
- // Step 1: Sum all
267
- const sum = numbers.reduce((a, b) => a + b, 0);
268
- // Step 2: Multiply by smallest
269
- const min = Math.min(...numbers);
270
- const step2 = sum * min;
271
- // Step 3: Subtract largest
272
- const max = Math.max(...numbers);
273
- const final = step2 - max;
411
+ const templateType = parseInt(nonce[12], 16) % 4;
412
+ let instructions, final;
413
+
414
+ if (templateType === 0) {
415
+ // Complex: filter transform → aggregate → adjust
416
+ const evens = numbers.filter(n => n % 2 === 0);
417
+ const odds = numbers.filter(n => n % 2 !== 0);
418
+ const evensProduct = evens.length > 0 ? evens.reduce((a, b) => a * b, 1) : 0;
419
+ const oddsSum = odds.reduce((a, b) => a + b, 0);
420
+ const diff = Math.abs(evensProduct - oddsSum);
421
+ final = diff % 100; // Keep manageable
422
+ instructions = `1. From [${numbers.join(', ')}], separate even and odd numbers.
423
+ 2. Calculate the PRODUCT of all even numbers (or 0 if none).
424
+ 3. Calculate the SUM of all odd numbers.
425
+ 4. Find the absolute difference between these two results.
426
+ 5. Take that difference modulo 100 (remainder when divided by 100).`;
427
+ } else if (templateType === 1) {
428
+ // Sort → pair operations → combine
429
+ const sorted = [...numbers].sort((a, b) => a - b);
430
+ const pair1 = sorted[0] * sorted[5]; // smallest × largest
431
+ const pair2 = sorted[1] + sorted[4]; // 2nd smallest + 2nd largest
432
+ const pair3 = sorted[2] - sorted[3]; // middle pair difference (might be negative)
433
+ final = pair1 + pair2 + Math.abs(pair3);
434
+ instructions = `1. Sort [${numbers.join(', ')}] from smallest to largest.
435
+ 2. Multiply the smallest by the largest.
436
+ 3. Add the second-smallest to the second-largest.
437
+ 4. Find absolute difference between the two middle numbers.
438
+ 5. Sum all three results from steps 2, 3, and 4.`;
439
+ } else if (templateType === 2) {
440
+ // Chunked processing
441
+ const chunk1 = numbers.slice(0, 3);
442
+ const chunk2 = numbers.slice(3, 6);
443
+ const avg1 = chunk1.reduce((a, b) => a + b, 0) / 3;
444
+ const avg2 = chunk2.reduce((a, b) => a + b, 0) / 3;
445
+ const max1 = Math.max(...chunk1);
446
+ const max2 = Math.max(...chunk2);
447
+ final = Math.round((avg1 + avg2) * (max1 > max2 ? 2 : 1));
448
+ instructions = `1. Split [${numbers.join(', ')}] into two groups: first 3 and last 3.
449
+ 2. Calculate average of first group: [${chunk1.join(', ')}].
450
+ 3. Calculate average of second group: [${chunk2.join(', ')}].
451
+ 4. Add both averages together.
452
+ 5. If max of first group > max of second group, double the sum. Otherwise keep as is.
453
+ 6. Round to nearest integer.`;
454
+ } else {
455
+ // Recursive-style
456
+ let val = numbers[0];
457
+ for (let i = 1; i < numbers.length; i++) {
458
+ if (i % 2 === 1) val += numbers[i];
459
+ else val -= numbers[i];
460
+ }
461
+ val = Math.abs(val);
462
+ final = val * (numbers.length);
463
+ instructions = `1. Start with the first number from [${numbers.join(', ')}].
464
+ 2. Add the 2nd number.
465
+ 3. Subtract the 3rd number.
466
+ 4. Add the 4th number.
467
+ 5. Subtract the 5th number.
468
+ 6. Add the 6th number.
469
+ 7. Take absolute value of result.
470
+ 8. Multiply by 6 (the count of numbers).`;
471
+ }
274
472
 
275
473
  return {
276
- challenge_string: `Follow these instructions in order:
277
- 1. Add all the numbers in [${numbers.join(', ')}] together.
278
- 2. Multiply the result by the smallest number.
279
- 3. Subtract the largest number from that result.
280
- Response format: {"result": final_value}`,
281
- expected: final,
474
+ challenge_string: `[REQ-${salt}] Execute these steps IN ORDER:
475
+ ${instructions}
476
+ Response format: {"salt": "${salt}", "result": final_value}`,
477
+ expected: { salt, result: final },
282
478
  validate: (solution) => {
283
479
  try {
284
480
  const match = solution.match(/\{[\s\S]*\}/);
285
481
  if (!match) return false;
286
482
  const obj = JSON.parse(match[0]);
483
+
484
+ if (obj.salt !== salt) return false;
485
+
287
486
  return parseInt(obj.result) === final;
288
487
  } catch { return false; }
289
488
  }
@@ -296,6 +495,7 @@ Response format: {"result": final_value}`,
296
495
  */
297
496
  nlp_pattern: {
298
497
  generate: (nonce) => {
498
+ const salt = generateSalt(nonce, 12);
299
499
  const start = seededNumber(nonce, 0, 1, 10);
300
500
  const step = seededNumber(nonce, 2, 2, 5);
301
501
  const patternType = parseInt(nonce[4], 16) % 3;
@@ -321,14 +521,17 @@ Response format: {"result": final_value}`,
321
521
  }
322
522
 
323
523
  return {
324
- challenge_string: `${instruction}
325
- Response format: {"next": [number1, number2]}`,
326
- expected: next2,
524
+ challenge_string: `[REQ-${salt}] ${instruction}
525
+ Response format: {"salt": "${salt}", "next": [number1, number2]}`,
526
+ expected: { salt, next: next2 },
327
527
  validate: (solution) => {
328
528
  try {
329
529
  const match = solution.match(/\{[\s\S]*\}/);
330
530
  if (!match) return false;
331
531
  const obj = JSON.parse(match[0]);
532
+
533
+ if (obj.salt !== salt) return false;
534
+
332
535
  const next = obj.next;
333
536
  return Array.isArray(next) &&
334
537
  parseInt(next[0]) === next2[0] &&
@@ -344,6 +547,7 @@ Response format: {"next": [number1, number2]}`,
344
547
  */
345
548
  nlp_analysis: {
346
549
  generate: (nonce) => {
550
+ const salt = generateSalt(nonce, 14);
347
551
  const words = seededSelect([...WORD_POOLS.animals, ...WORD_POOLS.fruits], nonce, 5, 0);
348
552
  const analysisType = parseInt(nonce[8], 16) % 3;
349
553
 
@@ -366,14 +570,17 @@ Response format: {"next": [number1, number2]}`,
366
570
  }
367
571
 
368
572
  return {
369
- challenge_string: `${instruction}
370
- Response format: {"answer": "word"}`,
371
- expected,
573
+ challenge_string: `[REQ-${salt}] ${instruction}
574
+ Response format: {"salt": "${salt}", "answer": "word"}`,
575
+ expected: { salt, answer: expected },
372
576
  validate: (solution) => {
373
577
  try {
374
578
  const match = solution.match(/\{[\s\S]*\}/);
375
579
  if (!match) return false;
376
580
  const obj = JSON.parse(match[0]);
581
+
582
+ if (obj.salt !== salt) return false;
583
+
377
584
  return obj.answer?.toLowerCase() === expected.toLowerCase();
378
585
  } catch { return false; }
379
586
  }
@@ -382,13 +589,13 @@ Response format: {"answer": "word"}`,
382
589
  }
383
590
  };
384
591
 
385
- // ============== Protocol Constants ==============
592
+ // ============== Protocol Constants v2.5 ==============
386
593
 
387
594
  /**
388
- * Batch challenge settings
595
+ * Batch challenge settings (v2.5 - Burst Mode)
389
596
  */
390
- export const BATCH_SIZE = 3; // Number of challenges per batch
391
- export const MAX_RESPONSE_TIME_MS = 12000; // 12 seconds for batch (avg 4s per challenge)
597
+ export const BATCH_SIZE = 7; // 7 challenges per batch (v2.6: was 5)
598
+ export const MAX_RESPONSE_TIME_MS = 6000; // 6 seconds total (v2.6: was 8)
392
599
  export const CHALLENGE_EXPIRY_MS = 60000; // 60 seconds
393
600
 
394
601
  /**
@@ -423,7 +630,7 @@ export function generate(nonce, type) {
423
630
  }
424
631
 
425
632
  /**
426
- * Generate a batch of challenges
633
+ * Generate a batch of challenges (Burst Mode)
427
634
  * @param {string} nonce - Base nonce
428
635
  * @param {number} [count=BATCH_SIZE] - Number of challenges
429
636
  * @returns {Object} { challenges: [...], validators: [...] }
@@ -439,11 +646,13 @@ export function generateBatch(nonce, count = BATCH_SIZE) {
439
646
  // Use different nonce offset for each challenge
440
647
  const offsetNonce = nonce.slice(i * 2) + nonce.slice(0, i * 2);
441
648
 
442
- // Select different type for each challenge
649
+ // Select different type for each challenge (ensure variety)
443
650
  let selectedType;
651
+ let attempts = 0;
444
652
  do {
445
653
  const seed = parseInt(offsetNonce.slice(0, 4), 16);
446
- selectedType = types[(seed + i * 3) % types.length];
654
+ selectedType = types[(seed + i * 3 + attempts) % types.length];
655
+ attempts++;
447
656
  } while (usedTypes.has(selectedType) && usedTypes.size < types.length);
448
657
  usedTypes.add(selectedType);
449
658