Eddie Campbell

Home / CV / Contact

Answer

aGt@!!?eeP4565tql

Seventeen characters, and (per the hint below) it is meant to be read as a regular expression, not decoded any further.

(⬆ annotated glyph breakdown)

The chain

puzzle.html is a pandoc-generated page whose entire body is:

<h1 id="puzzle">Puzzle</h1>
<p><img src="puzzle.gif" alt="Puzzle" /></p>

No hint text, no comments, no hidden fields. Everything is in puzzle.gif.

Layer Result
puzzle.gif 7-frame 519×519 animated QR, each frame a QR code
QR payloads 7 chunks of base64: 2,500 chars ×6 + 2,232 = 17,232 chars
base64 → bytes 12,924 bytes starting fd 37 7a 58 5a 00 → XZ stream
XZ → file 13,079-byte grayscale JPEG, 531×67
JPEG → glyphs 17 characters, constant 6 px gaps

Reproducible end to end:

curl -sO https://eddiecampbell.uk/puzzle.gif
zbarimg -q puzzle.gif                    # 7 base64 payloads
cat payloads.txt | tr -d '\n' > joined.b64
base64 -d joined.b64 > joined.xz
xz -t joined.xz && xz -dk joined.xz       # -> answer.bin
file answer.bin                           # JPEG, 531 x 67

sha256(answer.bin) = 7042c4b5425832d31bc2ad7ff48ffd23a155ce1cb7070aaf9de10c935a1a7ee7

There is no further layer: the JPEG’s EOI marker sits at offset 0x3315 with zero trailing bytes, and no APPn/comment segments are present.

Segmenting the glyphs

Threshold at 128, take column runs, merge runs separated by ≤3 px. That yields exactly 17 runs, and the gap between consecutive glyph bounding boxes is 6 px for every single pair.

That constant gap is worth stating explicitly, because it is a trap: the glyphs were re-spaced after rendering, so advance widths carry no information and cannot be used to identify a character. Any method that leans on spacing is dead on arrival here.

Reading the glyphs

# char x-range evidence
0 a 3–28 bowl + straight vertical right stem ending at the baseline
1 G 35–82 C-shape + crossbar (y20–24) + bottom-right stem (y25–40), 0 holes
2, 14 t 89–106, 463–480 full-height stem + crossbar + curved foot; identical pair
3 @ 113–152 outer ring + inner a bowl with a right stem + tail
4, 5 ! 159–164, 171–176 bar (y8–36) + separated dot (y41–50); identical pair
6 ? 183–205 hook + descending diagonal + dot
7, 8 e 212–239, 246–273 crossbar at y30–33, aperture bottom-right; identical pair
9 P 280–302 stem + closed upper bowl
10 4 309–346 open-top 4, diagonal overshooting the stem
11, 13 5 353–384, 425–456 flat top bar + left stem + lower bowl; identical pair
12 6 391–418 curved entry stroke sweeping into a closed bowl
15 q 487–515 bowl + right stem descending to y55
16 l 522–526 plain 4 px stem, y1–43

The two that mattered

Glyph 0 — a, not o. The giveaway is that the right side does not curve. For a round letter the bottom-right stroke must sweep back left to close the bowl. Compare the e (glyph 7), whose right side is at col 29–30 at y26–28 and then retreats to col 19 by y43. Glyph 0’s right edge instead sits at cols 22–25 from y28 all the way to y42, then tapers to col 25 at y44 — a straight stem that terminates on the baseline. A stem-plus-bowl at x-height ending at the baseline is a single-story a. The q (glyph 15) is the same construction with a 12 px descender, which is what rules out g, p and Q here.

Glyph 16 — l, not I or 1. A bare 4 px vertical stem, so shape alone cannot separate l from I (identical in most sans faces). Height does the work: the flat cap line is y2 (from P at y2–44 and t at y2–44), while this stem starts at y1 — one pixel above the cap, i.e. ascender height rather than cap height. It also has no flag or base serif, which rules out 1.

What I got wrong first

Cross-checks

Independent OCR, both wrong in instructive ways:

tesseract --psm 7   actell?eeP+565tal      17 chars, agrees on 11 positions
tesseract --psm 8   actell?eeP+5e65taql
gocr                aGt@!!?eeP__G_tqI

Tesseract independently returns a first and l last, and confirms @!!?eeP. Its errors are all explicable as shape confusions: c→G, e→@, l→!, +→4, a→q.

The checks that actually settled it were internal to the image:

The hint

https://eddiecampbell.uk/cv.html, under Skills → Misc:

fan of regex

That is the confirmation that the decoded string is the answer in its own right. It also says stop: base64, ROT13, Atbash and ROT47 all fail immediately on the @ and the !!.

Retrospective

The extraction was trivial; the transcription was not.

Phase Wall clock
QR → base64 → XZ → JPEG ~12 seconds
17 glyphs → a correct string ~2 h 15 m

Almost all of that time went into two characters. The genuinely wasted block was font identification: template-matching ~2,150 installed faces by IoU produced a best whole-string score of 0.31, and the one apparently strong hit (Chilanka-Regular.otf, IoU 0.908 on glyph 1) was a shape-only false positive — the per-glyph bboxes were normalised, so a generic G scores well against any round G. The font was never identified and never needed to be. What worked was the boring stuff: measure the glyphs, diff them against each other, count holes, and look at the greyscale.

Files

File
puzzle.gif the original animated QR (sha256 112e4033…)
answer.jpg the decoded payload, byte-identical to the XZ output
answer.png lossless grayscale, 531×67
answer@4x.png 2124×268 upscale — legible at post width
answer-annotated.png per-glyph boxes, indices and x-ranges

top