How a face-customization puzzle at HackMIT went from clicking sliders by hand to reverse-engineering a hidden formula from 10,000 API calls.
Face Value looked simple at first glance: ten sliders (Face, Skin, Hair, Brows, Eyes, Nose, Mouth, Glasses, Mole, Accessory), each 0-9, controlling a cartoon avatar. A hidden model scored every configuration, and the goal was to find one it would fully accept:
- Confidence ≥ 99.9%
- Edit distance from the starter config ≤ 5 (only half the sliders could move)
- Charm check: pass
- Sync check: pass
The puzzle's own hint: "Not all features affect the model equally. Some are more sensitive than others, especially together. Single-feature sweeps can be misleading."
That warning turned out to be the whole game.
Phase 1: Brute Force by Hand
The first instinct is the obvious one: click a slider, hit Query, read the result, adjust, repeat. Every query returned four numbers, shown together in a Reviewer panel: Probability, Charm, edit Distance, and Sync. All four had to align at once.
This works, sort of. Over the first ~24 manual queries, real patterns emerged: certain Glasses values seemed to matter for Sync, Mole and Accessory nudged confidence up, some sliders had sharp peaks rather than smooth slopes. But progress plateaued hard around 60-77% confidence. Manual testing can only really explore one or two dimensions at a time, and the puzzle explicitly warned that the model cared about combinations; you can't discover a 3-way interaction by changing one slider and squinting at the result.
The first real breakthrough was small but important: after enough fiddling, one query came back with Sync: True for the first time, confidence still low (8.14%), but proof that the four conditions weren't mutually exclusive.
Phase 2: Escaping the UI
The turning point was popping open Chrome DevTools, clicking Query once, and grabbing the actual network request as a curl command. Underneath the slick UI was a plain JSON API:
POST https://facevalue.hackmit.org/api/query
{"sessionId": "...", "config": {...}, "userId": "..."}
Enter fullscreen mode Exit fullscreen mode
Once you can hit that directly from a script, the whole problem changes shape. Instead of "click, screenshot, reason, repeat" at maybe one iteration per minute, you get hundreds of iterations per minute and, more importantly, you can run a real search algorithm instead of manual intuition.
Simulated Annealing, Coordinate Descent, and a Lot of Patience
The core tool was simulated annealing: propose a random tweak to 1-3 sliders, check the real score, and usually keep improvements but sometimes accept a worse result too, with a probability that shrinks over time. That randomness is the whole point: it's what lets the search escape a mediocre local optimum instead of getting stuck the moment it finds something "pretty good."
def anneal(current, budget, T0):
result = query(current)
current_score = score(result, current)
best_score, best_config = current_score, dict(current)
for step in range(budget):
T = max(T0 * (1 - step / budget), 0.005)
candidate = random_neighbor(current)
r = query(candidate)
s = score(r, candidate)
if s > current_score or random.random() < pow(2.71828, (s - current_score) / T):
current, current_score = candidate, s
if s > best_score:
best_score, best_config = s, dict(candidate)
return best_config
Enter fullscreen mode Exit fullscreen mode
Layered on top of that:
- Coordinate descent — exhaustively trying all 10 values for one feature at a time, holding everything else fixed, to squeeze out anything a random search missed.
- Random restarts across entirely different subsets of "which 5 features to change" — since only 5 of 10 sliders could move, which five mattered as much as their values.
- Systematic subset sweeps — eventually testing over 140 of the 252 possible five-feature combinations, each with its own mini hill-climb.
Confidence climbed in satisfying jumps: 77% → 92% → 94% → 96% → 98%, each jump coming from re-seeding the search with the previous best and letting it explore further.
Then it stalled. Hard. At 98.059%, every strategy converged to the same point: annealing, restarts, coordinate descent, pairwise grids, thousands of queries, and nothing beat it.
The Rate Limit (and the Discord)
Around 10,000 queries in, the server started throwing 429 Too Many Requests, and eventually the UI itself said: "Query limit reached." Turns out the hackathon had a hard cap: 10,000 queries.
Reverse-Engineering the Hidden Rule
Here's the part that actually cracked it. After ~10,000 queries, there was a lot of labeled data sitting around: thousands of (config → sync pass/fail) pairs in CSV logs from every search run. If Sync really was deterministic arithmetic rather than a fuzzy model, it should be discoverable directly from the data, no more API calls required.
The approach: brute-force search over every subset of the 10 features and every modulus from 2 to 10, checking whether sum(subset) mod k perfectly separated every single True from every single False in the dataset.
for mask in range(1, 2**len(COLS)):
idxs = [i for i in range(len(COLS)) if mask & (1 << i)]
col_sum = X[:, idxs].sum(axis=1)
for k in range(2, 11):
s = col_sum % k
true_residues = set(np.unique(s[y]).tolist())
false_residues = set(np.unique(s[~y]).tolist())
if true_residues and not (true_residues & false_residues):
print(f"PERFECT FIT: sum({idxs}) mod {k} in {true_residues}")
Enter fullscreen mode Exit fullscreen mode
With numpy vectorizing the check, this ran in seconds over 9,245 unique real examples. It found exactly one formula that fit all 9,245 points with zero exceptions:
Sync = True ⟺ (hair + eyes + glasses) mod 5 == 2
Enter fullscreen mode Exit fullscreen mode
Just three of the ten features. A clean, provable rule hiding under a "hidden model" description.
The Final Push
Knowing the exact Sync rule meant every remaining query could be spent purely chasing confidence and Charm, with Sync computed instantly and for free.
The winning combination needed one more twist. The best-known config (99.779%) used Face and Mole as the two "extra" features alongside the Sync-satisfying hair/eyes/glasses trio. Rather than search blind, the final script dropped each of those two "extra" features back to its starter value one at a time, and tried every untested feature in its place:
BASE = {'faceShape': 8, 'skinTone': 4, 'hair': 3, 'eyebrows': 7, 'eyes': 5,
'nose': 6, 'mouth': 2, 'glasses': 9, 'mole': 5, 'accessory': 1}
untested = ["eyebrows", "nose", "mouth", "accessory", "skinTone"]
for drop_col in ["faceShape", "mole"]:
for new_col in untested:
for v in range(10):
if v == STARTER[new_col]:
continue
trial = dict(BASE)
trial[drop_col] = STARTER[drop_col]
trial[new_col] = v
if edit_distance(trial) > 5:
continue
res = query(trial)
if res['probability'] > best[0] and res['sync'] and res['charm'] >= 3.0:
best = (res['probability'], dict(trial), res)
Enter fullscreen mode Exit fullscreen mode
Deep in that sweep, one combination broke through the plateau instantly:
drop mole, eyebrows=1: prob=99.9390% charm=3.92 sync=True
Enter fullscreen mode Exit fullscreen mode
Face=8, Skin=4, Hair=3, Brows=1, Eyes=5, Nose=6, Mouth=2, Glasses=9, Mole=0, Acc=1
Probability: 99.939%. Charm: 3.92. Sync: True. Edit distance: 5.
SOLVED — Your face has been accepted.
Enter fullscreen mode Exit fullscreen mode
What Actually Worked
A few things made the difference between "stuck at 98%" and "solved":
- Getting off the UI and onto the real API turned a slow manual process into a fast, scriptable one, the single biggest unlock.
- A real search algorithm beats intuition for high-dimensional interacting spaces. Simulated annealing found combinations no amount of "let me try changing this one thing" would have surfaced.
When something looks deterministic, treat it as a data problem, not a search problem. Ten thousand data points sitting in a CSV were enough to derive an exact formula in seconds, once thought to be looking for one instead of continuing to brute-force around it.
Respect the shared infrastructure. Getting rate-limited was a fair signal to slow down, not a wall to smash through faster.
Net result: a puzzle that looked like idle slider-clicking turned into a genuine applied optimization exercise, simulated annealing, coordinate descent, and a bit of reverse-engineering, running against a live scoring API instead of a toy dataset.





0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.