The Arena is where genes prove their worth. Every gene submitted to the Arena receives a fitness score (F(g)) and a safety score (V(g)) — two metrics that determine its rank, survival, and your developer reputation.
In this tutorial, you'll submit a gene, understand its scores, iteratively improve it, and watch it climb the rankings.
Prerequisites
- Rotifer CLI installed (
npm i -g @rotifer/playground) - A gene ready to submit (see Your First Gene in 5 Minutes)
- (Optional) Cloud login for global Arena (
rotifer login)
Step 1: Submit to the Arena
Let's submit a gene. If you don't have one, create a quick JSON formatter:
mkdir -p genes/json-fmt && cat > genes/json-fmt/index.ts << 'EOF'
export async function express(input: { code: string; indent?: number }) {
const indent = input.indent ?? 2;
try {
const parsed = JSON.parse(input.code);
return { formatted: JSON.stringify(parsed, null, indent), valid: true };
} catch {
return { formatted: input.code, valid: false };
}
}
EOF
rotifer wrap json-fmt --domain code.format
rotifer compile json-fmtNow submit:
rotifer arena submit json-fmtTesting 'json-fmt' before submission...
✓ All tests passed
Submitting to Arena...
✓ Gene 'json-fmt' submitted
Rank: #2 in code.format
V(g): 0.9100
Fidelity: NativeStep 2: Understand the Scores
F(g) — Fitness Score
F(g) is multiplicative, not a weighted sum. Since v0.5.5 the model has been:
F(g) = S_r · ln(1 + C_util) · (1 + R_rob) · L · Cost| Symbol | What it measures | How it is obtained |
|---|---|---|
S_r |
success rate across sandbox runs | measured — zero here zeros the whole score |
C_util |
coverage / utilization | defaults to 0.5 when not measured |
R_rob |
robustness under adversarial input | defaults to 0.5 when not measured |
L |
latency efficiency, 1 / (1 + avg_ms / scale) — faster is nearer 1 |
measured |
Cost |
resource efficiency, 1 / (1 + avg_cost / scale) — cheaper is nearer 1 |
measured |
Five factors, multiplied, with no cap. L and Cost are efficiency scores, not the costs themselves: the faster and cheaper a gene runs, the nearer they sit to 1 and the higher the score; the slower and costlier, the nearer to 0 and the more the score is pulled down.
Fidelity is not a bonus term inside the formula — it is a discount applied afterwards: F(g) = base × discount, with Native 1.0, Hybrid 0.85, Wrapped 0.7.
Two of the five inputs are still placeholders, and the reference scale inside L and Cost is provisional and still being calibrated. That is why a submission today is filed as an estimate rather than a ranked score — and why the sample output in this post does not pin down absolute figures. What to read is how the terms move each other, not any one number. Read them as a reproducible execution record; the ordering they will eventually produce is not settled yet.
V(g) — Safety Score
The safety validation is a hard gate with a separate 0.0–1.0 score:
V(g) is Test_Pass_Rate / Security_Leak_Risk. The leak risk comes from a static pattern scan over the gene's source — line-level rules, not an AST analysis:
| Rule | Detects | Severity |
|---|---|---|
| S-01 | Dynamic code execution (eval, new Function) |
CRITICAL |
| S-02 | System command execution (child_process, exec, spawn) |
CRITICAL |
| S-03 | Code obfuscation (base64 decode then execute) | CRITICAL |
| S-04 | Suspicious external communication | HIGH |
| S-05 | Environment variable access | HIGH |
| S-06 | Persistent outbound connection | HIGH |
| S-07 | File system operations | MEDIUM |
Admission Gate
The protocol sets a threshold on each score:
- F(g) >= 0.3 (default τ)
- V(g) >= 0.7 (default V_min)
V(g) is a hard gate: fail it and the gene is rejected with diagnostic feedback.
The F(g) gate does not block submission for now. The reason is in the section above: two of its inputs are placeholders and the reference scale in its efficiency terms is provisional — gating admission on a score the same run files as "estimated, not ranked" measures the scale, not the gene. The CLI still prints F(g) and flags it when it falls below τ, but it will not reject on it. The gate returns once the reference scale is calibrated.
Step 3: Read What the Submission Actually Reports
Submitting again prints the full record, not just the headline score:
rotifer arena submit json-fmt✓ Gene 'json-fmt' submitted to Arena
Domain: code.format
Fidelity: Native
V(g): 0.9100
Success Rate: 90.0%
Latency Score: 0.9881
Admission: PASSED
Execution: Sandbox verified (20 runs)
Recorded as: estimatedThree things are worth reading carefully.
Success Rate: 90.0% is the lever. S_r sits in the numerator of F(g), and it is the one factor whose zero collapses the whole score. Two of the other inputs — C_util and R_rob — are still fixed placeholders, so they contribute the same amount to every gene in the Arena and cannot be optimized against.
Recorded as: estimated is not decoration. Until those placeholders are measured, submissions are filed as estimates: the execution happened and is recorded, but the number does not yet rank.
V(g) is a separate axis, not part of F(g). A gene can be perfectly safe and useless, or fast and rejected. They gate independently at F(g) >= 0.3 and V(g) >= 0.7.
So the question this tutorial can answer today is a narrow one: why did 2 of 20 runs fail, and what does fixing them do to the score?
Step 4: Raise the Success Rate
A run counts as a failure when express() throws, times out, or returns output that violates the declared outputSchema. For the JSON formatter, the failures are malformed input the naive implementation gives up on:
export async function express(input: { code: string; indent?: number }) {
const indent = input.indent ?? 2;
const code = (input.code ?? '').trim();
if (!code) {
return { formatted: '', valid: false };
}
try {
const parsed = JSON.parse(code);
return { formatted: JSON.stringify(parsed, null, indent), valid: true };
} catch {
// Salvage the common case: trailing commas
try {
const cleaned = code.replace(/,\s*([\]}])/g, '$1');
const parsed = JSON.parse(cleaned);
return { formatted: JSON.stringify(parsed, null, indent), valid: true };
} catch {
return { formatted: code, valid: false };
}
}
}Note what the fallback does not do: it never throws and never returns a partial object. Returning { formatted, valid: false } for genuinely broken input is a successful run reporting a negative result — that is a different thing from a crash, and the Arena scores it differently.
Recompile and resubmit:
rotifer compile json-fmt
rotifer arena submit json-fmt Success Rate: 100.0%S_r moved from 0.90 to 1.00 and F(g) moved with it, proportionally — S_r is a direct multiplier, so a ten percent gain in success rate is a ten percent gain in the score. That is the multiplicative structure doing exactly what it says.
Step 5: Check the Safety Axis Separately
V(g) comes from a static pattern scan over your source. Run it on its own before you submit:
rotifer vg json-fmtThe scan is line-level regex matching against the S-01 to S-07 rules above. That makes it fast and it makes it blunt: a string containing the word eval in a comment can trip S-01. Read the finding, don't just chase the grade — the point of V(g) is that a consumer can see what your gene reaches for before installing it.
Step 6: Watch the Rankings
Monitor your gene's position in real-time:
rotifer arena watch code.formatWatching code.format rankings...
[14:32:01] json-fmt ↑ #2 → #1 (F: 0.82 → 0.86)
[14:32:04] No changes
[14:32:07] No changes
Press Ctrl+C to stopStep 7: Go Global with Cloud Arena
Local Arena is great for testing. To compete globally:
rotifer login
rotifer arena submit json-fmt --cloud✓ Submitted to Cloud Arena
Rank: #8 globally in code.formatView global rankings:
rotifer arena list --cloud -d code.formatStep 8: Check Your Reputation
Every Arena submission affects your developer reputation:
rotifer reputation --mineDeveloper Reputation:
Arena Score: 0.82 (based on gene rankings)
Usage Score: 0.45 (based on install counts)
Stability: 0.91 (based on gene consistency)
Overall: 0.73Reputation is a composite of your genes' Arena performance, how often others use them, and their reliability over time.
The Optimization Loop
The cycle is the point, even while the scoring model is still being sharpened:
- Submit → get the execution record, not just a number
- Diagnose → find the runs that failed and why
- Improve → targeted code changes
- Resubmit → verify
S_rmoved - Watch → monitor competitive position
This is selection pressure in its earliest form: the Arena measures, and your gene adapts to what it measures. What it measures well today is whether the gene does what it declared it would.
Tips for High Fitness
- Handle edge cases: null inputs, empty strings, malformed data — every one that throws costs you
S_r - Return the declared shape, always: output that violates your
outputSchemacounts as a failed run, even when the function did not crash - Be deterministic: the same input should always produce the same output, so the runs agree with each other
- Declare fidelity honestly: the discount multiplied into F(g) is Native 1.0, Hybrid 0.85, Wrapped 0.7 — and an undeclared fidelity is treated as Wrapped, not Native
- Faster and cheaper does score higher:
LandCostare efficiency scores multiplied into the total, so cutting latency and resource use raises F(g). Their reference scale is still being calibrated, though, so tuning against absolute figures is not worth it — max outS_rfirst, then look at efficiency
Deep Dive: See the Gene Standard specification for the complete fitness model, and the Arena CLI Reference for all command options.
