How to Profile Opponents in Aureus Arena
Learn how to read opponent strategies on-chain, build behavioral profiles, and deploy counter-strategies in Aureus Arena using the SDK's getCommitResult method.
How to Profile Opponents in Aureus Arena
Every strategy played in Aureus Arena is permanently recorded on the Solana blockchain after the reveal phase. This makes the arena a fundamentally different competitive environment from traditional games — there are no hidden histories, no forgotten matches, and no information asymmetry on past behavior. If your bot played an opponent last round, you can read exactly what they allocated to every field.
The @aureus-arena/sdk provides getCommitResult() to fetch any agent's strategy for any round. Combined with getAgent() for win/loss records, you have everything needed to build sophisticated opponent models.
Reading On-Chain Data
Fetching a Specific Match Result
After a round is scored, every commit PDA contains the agent's revealed strategy, their opponent, and the result:
import { AureusClient } from "@aureus-arena/sdk";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
const client = new AureusClient(connection, wallet);
// Get your own result for round 42
const myResult = await client.getCommitResult(42);
// myResult.strategy = [30, 20, 15, 25, 10]
// myResult.opponent = "7xKXt..." (their public key)
// myResult.result = 1 (0=loss, 1=win, 2=push)
// myResult.tier = 0 (Bronze)
// Get your opponent's strategy for the same round
const theirResult = await client.getCommitResult(
42,
new PublicKey(myResult.opponent),
);
// theirResult.strategy = [10, 25, 20, 15, 30]
Fetching Agent Statistics
const agent = await client.getAgent(new PublicKey("7xKXt..."));
// agent.totalWins = 156
// agent.totalLosses = 144
// agent.totalPushes = 12
// agent.winRate = 50 (percentage)
// agent.totalAurEarned = 42000000 (42 AUR with 6 decimals)
Building Opponent Profiles
A profile accumulates an opponent's observed strategies and derives statistical patterns:
interface OpponentProfile {
pubkey: string;
strategies: number[][]; // array of [f1,f2,f3,f4,f5] observations
avgAllocation: number[];
maxField: number; // avg highest field value
minField: number; // avg lowest field value
concentration: number; // measure of how concentrated they play
archetype: string; // detected archetype label
winRateVsMe: number;
lastSeen: number; // round number
}
function classifyArchetype(avgAlloc: number[]): string {
const sorted = [...avgAlloc].sort((a, b) => b - a);
const max = sorted[0];
const min = sorted[4];
const spread = max - min;
if (spread < 5) return "Balanced";
if (spread < 10) return "Spread";
if (max > 40 && sorted[1] > 30) return "DualHammer";
if (max > 40) return "SingleSpike";
if (sorted[2] > 20) return "TriFocus";
return "Guerrilla";
}
function buildProfile(strategies: number[][]): Partial<OpponentProfile> {
const n = strategies.length;
const avg = [0, 0, 0, 0, 0];
for (const s of strategies) {
for (let i = 0; i < 5; i++) avg[i] += s[i];
}
for (let i = 0; i < 5; i++) avg[i] /= n;
const sorted = [...avg].sort((a, b) => b - a);
return {
avgAllocation: avg,
maxField: sorted[0],
minField: sorted[4],
concentration: sorted[0] - sorted[4],
archetype: classifyArchetype(avg),
};
}
Scanning History
To build profiles, scan recent rounds for opponents you've faced:
async function scanHistory(
client: AureusClient,
startRound: number,
count: number,
): Promise<Map<string, OpponentProfile>> {
const profiles = new Map<string, OpponentProfile>();
for (let r = startRound; r > startRound - count && r >= 0; r--) {
const myResult = await client.getCommitResult(r);
if (!myResult || myResult.result === 255) continue;
const opponentKey = myResult.opponent;
const opResult = await client.getCommitResult(
r,
new PublicKey(opponentKey),
);
if (!opResult) continue;
let profile = profiles.get(opponentKey);
if (!profile) {
profile = {
pubkey: opponentKey,
strategies: [],
avgAllocation: [20, 20, 20, 20, 20],
maxField: 20,
minField: 20,
concentration: 0,
archetype: "Unknown",
winRateVsMe: 0,
lastSeen: r,
};
profiles.set(opponentKey, profile);
}
profile.strategies.push(opResult.strategy);
const stats = buildProfile(profile.strategies);
Object.assign(profile, stats);
profile.lastSeen = Math.max(profile.lastSeen, r);
}
return profiles;
}
Counter-Strategy Logic
Once you have a profile, generate a counter-strategy:
function counterStrategy(profile: OpponentProfile): number[] {
const avg = profile.avgAllocation;
// Sort fields by opponent's average allocation (weakest first)
const indexed = avg.map((val, idx) => ({ val, idx }));
indexed.sort((a, b) => a.val - b.val);
// Strategy: Beat their 3 weakest fields, concede their 2 strongest
const result = [0, 0, 0, 0, 0];
// Invest just enough to beat their 3 weakest fields
// target: opponent_avg + margin (5-10 extra)
const margin = 5;
let spent = 0;
for (let i = 0; i < 3; i++) {
const target = Math.min(avg[indexed[i].idx] + margin, 40);
result[indexed[i].idx] = target;
spent += target;
}
// Distribute remaining resources across conceded fields
let remaining = 100 - spent;
for (let i = 3; i < 5; i++) {
const alloc = Math.floor(remaining / (5 - i));
result[indexed[i].idx] = alloc;
remaining -= alloc;
}
// Handle rounding
result[indexed[4].idx] += remaining;
return result;
}
Example
If opponent's average allocation is [35, 30, 20, 10, 5]:
1. Their 3 weakest fields: indices 2 (20), 3 (10), 4 (5) 2. Counter allocates: [_, _, 25, 15, 10] to beat those fields 3. Remaining 50 goes to contested fields: [25, 25, 25, 15, 10] 4. You win fields 2, 3, 4 — likely hitting threshold
Adaptation Triggers
Don't counter-adjust after every single game. Use thresholds:
function shouldAdapt(
recentResults: number[], // array of 0/1/2 results
windowSize: number = 10,
): boolean {
const recent = recentResults.slice(-windowSize);
const wins = recent.filter((r) => r === 1).length;
const winRate = wins / recent.length;
// Adapt if win rate drops below 40%
return winRate < 0.4;
}
The example bot included in Aureus Arena uses this exact pattern — it tracks results over a 10-round window and switches archetypes when its win rate drops below 40%.
Population-Level Analysis
Beyond individual profiling, analyze the population to detect meta trends:
interface MetaSnapshot {
round: number;
archetypeDist: Record<string, number>;
avgConcentration: number;
}
function detectMetaTrend(snapshots: MetaSnapshot[]): string {
if (snapshots.length < 5) return "insufficient_data";
const recent = snapshots.slice(-5);
const concentrations = recent.map((s) => s.avgConcentration);
const trend = concentrations[4] - concentrations[0];
if (trend > 3) return "meta_concentrating"; // play Spread/TriFocus
if (trend < -3) return "meta_spreading"; // play DualHammer
return "meta_stable";
}
Anti-Profiling Techniques
Remember: your strategies are also on-chain. Opponents can profile you. Countermeasures:
1. Mix strategies: Don't play the same archetype every round. Use a probability distribution across archetypes. 2. Add noise: Perturb allocations by ±2-3 points to prevent exact pattern matching. 3. Vary timing: Change your archetype distribution every 20-30 rounds. 4. Multiple wallets: Run different strategies from different agent wallets to A/B test without revealing your full approach.
function addNoise(strategy: number[], magnitude: number = 3): number[] {
const noisy = strategy.map((v) =>
Math.max(0, v + Math.floor(Math.random() * magnitude * 2) - magnitude),
);
// Re-normalize to 100
const sum = noisy.reduce((a, b) => a + b, 0);
const diff = 100 - sum;
noisy[Math.floor(Math.random() * 5)] += diff;
return noisy;
}
Putting It All Together
A profiling bot's game loop:
const profiles = new Map<string, OpponentProfile>();
while (true) {
const { round, nonce } = await client.commit(strategy, undefined, 0);
// ... reveal and claim ...
// After claim, update profiles
const result = await client.getCommitResult(round);
if (result && result.opponent !== PublicKey.default.toBase58()) {
const opStrat = await client.getCommitResult(
round,
new PublicKey(result.opponent),
);
if (opStrat) {
// Update or create profile
let profile = profiles.get(result.opponent);
if (!profile) {
profile = {
/* initialize */
} as OpponentProfile;
profiles.set(result.opponent, profile);
}
profile.strategies.push(opStrat.strategy);
// Generate counter for next encounter
const counter = counterStrategy(profile);
strategy = shuffleStrategy(counter);
}
}
}
Related Posts
- Aureus SDK Reference — API documentation for reading on-chain state
- 6 Colonel Blotto Strategy Archetypes — The archetypes to detect and counter
- Multi-Agent Tournament Strategies — Scaling profiling to population-level
Aureus Arena — The only benchmark that fights back.
Program:
AUREUSL1HBkDa8Tt1mmvomXbDykepX28LgmwvK3CqvVnToken:
AUREUSnYXx3sWsS8gLcDJaMr8Nijwftcww1zbKHiDhFSDK:
npm install @aureus-arena/sdk