Ashutosh Mishra · Team Ganondorf · Speed Demon 2026, Hackathon Raptors
Repo: https://github.com/Dev-Am12/speed-demon-ganondorf · frozen commit fe581bf · scripts: https://gist.github.com/Dev-Am12/47f7f67e54d0fcb94994550fd51631eb
Our best Speed Demon score was 70.74. The commit we froze scored 67.41 on the board.
That was on purpose. In the last two and a half hours before the freeze we decided the code should be easy for a stranger to adapt, not only fast: exact identity checks instead of hash luck, buffers sized for any valid input instead of for seed 1. We knew the board would charge for it. We did not know how much, and we never decided how much we were willing to pay. The board's verdict on the frozen commit arrived at 20:33 IST, three minutes after the freeze closed:
| Track | Push before (3b7b716) | Frozen commit (fe581bf) | Change |
|---|---|---|---|
| A Ingest | 75.92 · 0.041 s · 22 MB | 73.54 · 0.041 s · 38 MB | −2.38 |
| B Index | 60.64 · 0.308 s · 122 MB | 58.02 · 0.381 s · 128 MB | −2.62 |
| C Join | 75.43 · 0.042 s · 9 MB | 71.79 · 0.051 s · 11 MB | −3.64 |
| Overall (geometric mean) | 70.29 | 67.41 | −2.88 |
I would make the trade again. I would not make it that way again. This post prices what we did after the fact, shows which justification for it survives the arithmetic, and ends with the plan we should have started with. It is also the optimisation journey the quest asks for, including its last step, which went backwards.
TL;DR
- Speed Demon: four tasks, one scoring formula over time, memory, source lines and dependencies. We entered all four in Rust, standard library only: 202 board runs over 57 pushed commits, up to 11:51 UTC on 2 Nov.
- Three commits in the last 2.5 hours (
b6e1f37on A,a3dc327and7b6a495on B and C) cost 2.9 overall points. B and C each got 21% to 24% slower, because every hash match is now checked against the real bytes. A paid in memory.- Two justifications: insurance against a failed hidden-dataset gate, and code a reviewer would merge. The first fails by seven orders of magnitude (derived risk about 10⁻⁸, break-even 15% to 22%). The second cannot be priced, only bet on.
- I measured the ruler too. Board noise on byte-identical code is 0.4 to 0.9 points, so the drops are real, our "best" numbers were partly luck, and for 26 of 47 earlier pushes the board had not said what we thought it said. One script,
check_post_numbers.py, asserts every number in this post against the repo.
A four-way trade with a rate card
The score is 50 × log10 K, where
K = (Tref/T)^0.50 × (Mref/M)^0.20 × (Sref/S)^0.15 × (Dref/D)^0.15 T is wall time, M peak memory, S source lines and D dependencies, each against a deliberately naive Python reference. Read the exponents as a rate card. 1% of time is worth 2.5% of memory and 3.3% of source lines. Almost every decision this weekend was a trade on that card, and it predicted both of B's biggest trades to within half a percent.
| Track | Task | Reference | Best board run | Speed-up |
|---|---|---|---|---|
| A Ingest | 3.1M log lines into percentiles | 32.05 s | 0.037 s / 23 MB / 423 lines | 866× |
| C Join | hostile 202 MB CSV, join, aggregate | 14.44 s | 0.042 s / 9 MB / 371 lines | 344× |
| B Index | 50,000 boolean and phrase queries | 56.35 s | 0.297 s / 122 MB / 1,235 lines | 190× |
| D Traverse | 2,000 shortest paths on 5M edges | 72.69 s | 0.917 s / 135 MB / 369 lines | 79× |
Two people, Rust we were not fluent in. Most code came from coding agents under prompts I wrote and gates I ran. My teammate owned A and D and the tooling; I owned B and C, the fixtures and the oracle. We dropped D early, which the scoring allows: the overall counts your best three. This post is about B and C, and uses the board data of all four.
The journey, short version

Every board run of Track B and C. Green dots changed the source and beat the noise; grey dots did not. B is nearly flat after hour 31.
Most of the improvement came early. B reached 54.5 points at hour 15, 84% of everything it ever gained, and after hour 22 37 of its 53 runs bought 4.2 points. These are the steps that cleared the noise (hours since kickoff):
| Hour | Track B | Result | What changed |
|---|---|---|---|
| 4.5 | 40ad00c v0 | 13.5 s, 400 MB | single thread, corpus on the heap |
| 6.3 | 832e2c8 v1 | 1.41 s, 278 MB | memory-mapped input, parallel two-pass scan, hybrid sets, phrase verification |
| 7.3 | 1a55090 v2 | 0.97 s, 103 MB | no change description recorded (time −31%, memory −63% against v1) |
| 7.7 | 1a65ccb | 0.65 s, 108 MB | no change description recorded |
| 31.0 | b22f4e2 | 0.42 s, 67 MB | tagged vocabulary and a block tokenizer |
| 54.3 | f29eac6 | 0.30 s, 133 MB | rewrite around a single scan, sparse hits in bucketed logs |
| Hour | Track C | Result | What changed |
|---|---|---|---|
| 20.6 | a3f64b1 | 0.168 s, 29 MB, 622 lines | two-pass SIMD quote-parity scanner, region lookup table |
| 21.5 | 080c956 | 0.074 s, 10 MB, 509 lines | single-pass record-boundary snapping, mapped region loader, 64-bit name table |
| 27.6 | 1039e85 | 0.068 s, 12 MB, 389 lines | prefetch and structure work |
| 43.5 | e3c539c | 0.043 s, 9 MB, 450 lines | no change description recorded (+3.5 points) |
| 68.9 | 3b7b716 | 0.042 s, 9 MB, 371 lines | best board result; the snapshot we later hardened |
Three of the five biggest jumps have no recorded description. Our notes were thinnest exactly where the score moved most, which matters for a contest that rewards PERF.md for citing what measured each step.
Three smaller stories are the parts I would repeat to a friend.
The profile contradicted the plan. My plan read B as two symmetric scans of the corpus. A diagnostic profile on 1 Nov said pass 2 took 244 to 284 ms against 93 to 101 ms for pass 1, about 2.7 times as much, because pass 2 does the random postings writes. The three lookup-structure experiments aimed at the smaller part (B2a, B2b, B2c) produced no repeatable gain.
A cache with a 0.015% hit rate. On C, a 2,048-entry cache of recent (name, country) pairs sat in front of the shared name table. Its measured hit rate was 0.015%. Deleting it gave 1.09× and a tighter spread. It had been there since the first draft because it felt like the thing a fast program has.
Memory is counted with the page cache. The board's peak memory is the cgroup's memory.peak, which includes the kernel's page cache for the file you write. Track A (my teammate's) unmaps its input and calls malloc_trim before formatting output so the two peaks never overlap, and its board runs landed between 10 and 23 MB.
What a wall looks like
B stalled at 0.42 s and 67 MB around hour 31. I called it a wall. The rate card says what it was: a frontier. Every move left on the table traded time against memory at about the exchange rate the formula already prices.
| Move on B | Time | Memory | Rate card says | Board said |
|---|---|---|---|---|
single scan (faaeb9d → 005c99a) | −26% | +82% | K × 1.035 | K × 1.031 |
fewer index threads (25e6277 → fbe7674) | +21% | −26% | K × 0.966 | K × 0.966 |
Both were break-even, as predicted, and I paid a push for each. Getting off the frontier needs a design that improves both axes at once, and I did not have one. On the leaderboard I read on 2 Nov, the top B run had K 44.5 against our 16.0, a factor of 2.8: time ×2.2, memory ×1.24, source ×1.03, with a run time of 0.064 s against our 0.297 s (my arithmetic from public numbers; I never saw their code).
For C I priced five ideas on paper before spending a push on any: a round-based exchange for the distinct count (net about −2.5% in K by the card), storing every pair and deduplicating (+13 MB, −15%), per-thread bitsets (memory ×12, −39%), non-atomic marking (about 18 lost updates per run, a gate fail) and a smaller name table (below 2¹⁷ slots, which 100,000 distinct names would fill). None was worth building. That was the one place this weekend where I priced a decision before making it.
Measuring the ruler
Before arguing about a drop of three points I needed to know how noisy the board is. It runs each track once per push on the scoring machine. I could not afford repeat runs, but I found a free experiment afterwards.
How do you know two runs are the same code? Not by commit id: a docs-only commit gets a new id and the same binary. I asked git. git rev-parse <board-commit>:track-b gives the hash of that folder's contents, and two runs of one track with equal hashes ran byte-identical source, so any difference between them is the instrument.
Track D made the best control, because we stopped touching it and every push that changed A, B or C re-ran it:

Track D never changed, yet 37 board runs of the same bytes scored from 44.93 to 48.69.
Thirty-seven runs of identical source took from 0.917 s to 1.297 s. Pooled over every group of identical source on each track, the noise is:
| Track | Noise (sd) in points | in K |
|---|---|---|
| A | 0.66 | 3.1% |
| B | 0.42 | 1.9% |
| C | 0.73 | 3.4% |
| D | 0.89 | 4.1% |
A difference of two single runs needs about 2√2 standard deviations to count: 1.9 points on A, 1.2 on B, 2.1 on C. All three final drops clear it, by factors of 1.3, 2.2 and 1.7, so they are real.
The same threshold is unkind to our earlier work. Of 47 pushes on A, B and C where the source changed, 26 are indistinguishable from nothing. Our own B notes say "the board decided" for a run of 0.435 s against 0.417 s; that is 0.8 standard deviations of a two-run difference. Our best B result, 005c99a, sat one point above the push before it, and the repair commit that restored that exact source later scored 59.55. Each track's "best" was a maximum over noisy draws, the winner's curse: the three headlines give 70.74, the three same-source means give 70.23. "Inside the noise" is not "zero effect", since four honest 1% wins also hide there, but for more than half of our pushes the board had not said what we said it had.
The bill
Git makes the attribution clean. After 3b7b716, A's source changed in exactly one commit (b6e1f37, 12:28 UTC), and B's and C's in exactly two (a3dc327 at 12:58 UTC and 7b6a495 at 13:42 UTC, 78 minutes before the freeze). D changed twice and moved +0.9, which is noise. Splitting each track's loss with the rate card:

Points lost by axis. A: memory. B and C: mostly time.
- B now matches a 44-bit tag, then compares the stored key. In the same change the id field grew from 18 to 20 bits so the table can hold the spec's maximum of 338,000 query terms (seed 1 has 75,747), which is what shrank the tag from 46 bits. Time rose 24%, from 0.308 s to 0.381 s, for −2.3 of its −2.6 points.
- C now compares names byte by byte whenever the 64-bit hash matches, and adds a lookup for region ids above the table. Time rose from 0.042 s to 0.051 s (−2.1 points), memory from 9 MB to 11 MB (−0.9) and source from 371 to 446 lines (−0.6). My reading of the diff: the time is the verification. Every hit now loads a second array and then reads the first-seen name from somewhere random inside a 200 MB file, a likely cache miss, on every record. The region lookup only runs for ids that cannot match anyway. I have no per-change timing to prove this, so it is a hypothesis.
- A, my teammate's track, paid 2.4 points almost entirely in memory (22 MB to 38 MB) for a more general layout of its tables.
while m != 0 { let pay = bk.0[(m.trailing_zeros() & 3) as usize] & ID_MASK; if pay != 0 && self.keys[(pay - 1) as usize] == k { // B: the exact check (comment added), one extra load per tag match return (pay - 1) as u32; } m &= m - 1; } The 2.9 points are board speed points. Only 65% of a track total is the machine score, and I do not know how the organisers map K to it, so the cost in final track points is smaller than 2.9. I just cannot say by how much.
Two justifications, one survives
I can give two reasons for the change. They fail very differently.
Reason 1: insurance against failing a gate on the hidden dataset. Failing a gate scores zero on that track, so I priced it like an insurance policy. Break-even is simple: p* = premium ÷ payout.
- Payout. The overall counts the best three tracks and D was waiting, so a failure costs 3.9 overall points on B and 8.3 on C, not a full track.
- Premium. A third of a track's loss, since the overall is a geometric mean of three: 0.87 for B and 1.21 for C.
- Probability. For C, 100,000 distinct names under a 64-bit hash collide with probability n(n−1)/2 / 2⁶⁴, about 2.7×10⁻¹⁰ per dataset. For B, the source comment said about 10⁻⁷ and my planning documents said 4×10⁻⁸ and 2.5×10⁻⁸, and none was derived. Deriving it: about 534,000 distinct absent tokens, each meeting about 1.16 occupied slots in its bucket, each matching a tag with probability 2⁻⁴⁶. That is 8.8×10⁻⁹, or 3.5×10⁻⁸ had the 44-bit tags been left unchecked.
The policy only breaks even at a 22% chance of failure on B and 15% on C.

The risk we removed versus the risk that would have justified the price.
As gate insurance the change was mispriced by about seven orders of magnitude. If that had been the whole reason we should have shipped 3b7b716, whose scores were known.
Reason 2: code a reviewer would merge. This is the one we acted on. Thirty-five percent of every track total is human judging, and its largest slice is code quality, defined in the brief as "would you merge this". A stranger who adapts our B or C to a new corpus should not have to trust that 64 bits of hash happened to be enough. Track B's README opens its hardening note with the words "Adoptability and robustness" and says the aim was to generalise across valid hidden datasets instead of leaning on seed 1, so this was the stated reason, not a story invented afterwards. We were also not on course for a podium, so we were optimising for the reader. I still think that is a defensible bet.
But it is a bet, and I cannot price what it buys. Judges see the run history and the code, and they have not yet scored it. I only know the stake. And I had the chance to learn the stake before the last night: on 1 Nov an exact-slot variant of B measured 6 to 9% slower locally, and we reverted it. At the end we paid 24% in time for a change we had not measured first.
What we should have done: a robustness budget
Robustness is a design constraint, and the cheapest place to spend it is early.
- Set the budget at hour 12, in K. Say 3% of K. By the rate card that is 0.66 points, or 6.3% of time, or 16.5% of memory. We spent 2.9 points, about four and a half times that.
- Spend it in the design. Layouts that carry exactness for almost nothing (wider identity inside the same load, table sizes taken from the data's own maxima) are available at hour 12 and gone by hour 70. I have not tested the first idea, but it is where I would start.
- Price each safeguard on its own push.
a3dc327, which started the B and C hardening, was a commit named "readme v2 and track b&c docs". The board could not tell us which half of the change cost what. - Merge what you decided to ship. On 1 Nov at 07:10 UTC I committed two small correctness fixes to a branch,
c/v8: one for a final record without a trailing newline, one for region ids longer than seven digits. Both are inputs the fuzz generator produces and the spec rules out, the baseline had disagreed with the reference on 298 of 300 fuzz seeds, and with the fixes it matched all 300. Neither was ever merged intospeed-demon, the branch that is scored, and neither had a board timing. I found out on 2 Nov, writing documentation, when the ledger described a path the source did not have. A fix that exists on a branch is not a fix.
The planner priced by feel too. I used Claude Sonnet as planner and reviewer, and its forecasts are in the logs:
| Forecast | Outcome on the board |
|---|---|
| B v3: K 16.2, from local timing | K 12.30 |
| B "Phase 5" plan: K about 20 to 22 | no change passed the keep rule; K stayed 15.7 to 16.1 |
My laptop, a hybrid 2 P-core, 8 E-core chip under WSL2, was no better as a referee: in five of six paired runs, whichever binary ran second came out 7 to 27% slower. The board was the only absolute measure we had, and it is noisy.
Fact-checking this post
While writing this I read our submission the way a reviewer would, and found the paperwork for the insurance still says we are uninsured.
- The frozen root README says B's lookup "is tag-based, not exact", calls it "the main correctness risk on Track B", and lists "make the lookup exact" as next work. The frozen B source is exact.
- Track B's own README opens by describing the exact-key hardening, and then, in its known-limits section, still says the lookup does not compare the full key.
- The root README lists C's region ids of 1,000,000 or more as an open correctness question. The spec bounds
regions.csvat 999,999, so unmatched was already the right answer, and the code now has a lookup for ids the spec says cannot exist. The open item is written down; its resolution is not. - The brief wants each step of PERF.md to cite the board run that measured it. A last-minute change cannot cite its own result: Track B's README says the hardening pass "has not yet received a board timing".
So the change we made for the people reading the code is the one those people will find contradicted by the documents we wrote for them. That is a cost I did not put on the bill, because it does not show on the board. The repo is frozen and post-freeze edits would not count, so it stays as it is, and this paragraph is the correction.
What I still don't know
Whether the 35% pays out. Judges score code, run history and write-ups until 12 November, and I will find out on the 13th. If they reward adoptability, the 2.9 points were cheap. If they weigh run IDs and the board history, we paid for a change that is hard to cite.
What I do know is on the board. Of the 3.3 points between our best score and our frozen commit, 2.9 is what we paid and 0.5 is luck we never had. We made our fast code slower on purpose. Next time I would like to know, before I push, what it costs.
Thank you
Speed Demon was unlike any hackathon I have done. Most of them end with a demo and a verdict you wait days for. This one gave us a live scoreboard on real hardware, public specs and generators, and a rank that moved every time we pushed. Competing against other teams in real time, with the same four tasks and the same ruler, was exciting in a way a regular hackathon is not. It was also the most honest benchmark I have ever worked against, which is why I could measure its noise in the first place.
Thank you to my teammate Shivam, who took on A, D, Docker and the tooling so I could stay deep in B and C, and who made a two-person team feel bigger than that. And thank you to the Hackathon Raptors team for building something this original and running it so openly: a contest where you can check the scoring yourself is rare, and a genuinely fresh experience.
Whatever the result turns out to be, I had a great time, and I am hoping for the best.
Where to check each claim
| Claim | Command or place |
|---|---|
| Every number in this post (114 assertions) | python3 scripts/check_post_numbers.py prints ALL PASS |
| Noise floor, the 37-run control, 26 of 47 pushes | python3 scripts/noise_audit.py -v |
| The 2.9 points, split by axis; premium, payout, break-even; risk derivations | python3 scripts/tax_audit.py |
| Attribution to three commits | git log --format='%h %ad %s' 3b7b716..fe581bf -- track-a/src track-b/src track-c/src |
| Unmerged fixes | git merge-base --is-ancestor 09d07cc fe581bf (exit code 1) |
| Stale README claims | README.md ("Lookup is tag-based") and track-b/README.md ("Known limits") at fe581bf |
| The frozen run's scores | the board feed, run fe581bf, finished 2 Nov 15:03 UTC |
git clone https://github.com/Dev-Am12/speed-demon-ganondorf.git && cd speed-demon-ganondorf git clone https://gist.github.com/Dev-Am12/47f7f67e54d0fcb94994550fd51631eb scripts python3 scripts/check_post_numbers.py mkdir -p figures && python3 scripts/make_figs.py figures/ && python3 scripts/make_figs_tax.py figures/ The scripts need runs_ledger.md (parsed from the public board feed, up to 11:51 UTC on 2 Nov), the .git folder, and matplotlib for the figures. The frozen run's numbers are constants in tax_audit.py, copied from the board.
References
- Speed Demon 2026 brief: the scoring formula (K), the three gates, the 65/35 split and the judging criteria.
- Public board feed: board.json
- Linux kernel documentation, cgroup v2 (
memory.peak, page-cache accounting): docs.kernel.org/admin-guide/cgroup-v2.html - Birthday-bound collision estimate used for the C name hash: en.wikipedia.org/wiki/Birthday_problem
- Built for Speed Demon 2026 by Hackathon Raptors.
