Sampati: The Hundred-Yojana Sight
By Team Cryptonite · 1 min read
About the CTF: Pinaka CTF
A nonlinear reconstruction puzzle: rebuild the scene from many noisy sightings with bundle adjustment, and read the flag from what Sampati saw.
**Pinaka CTF 2026** · Misc & OSINT · Team Cryptonite · All 44 Pinaka write-ups
Category: misc (nonlinear reconstruction / bundle adjustment)
Flag: PINAKA{sampatishatayojanadrishti_lanka}
Files
K05_player/sightings.sampati- custom little-endian binary sighting logK05_player/README.md- optical model
Reversing the format
The file is a custom format with CRC32 on every record. Layout:
magic[8] = "PNKSAMP1"
u16 version = 1
u16 perch_count = 6
u16 peak_count = 3
double f = 800.0
double cx = 0.0
double cy = 0.0
u32 sighting_count = 18
u32 header CRC32 (over previous 42 bytes)
Each perch record (102 bytes, CRC32 over first 98):
u16 station_id
double pos[3] # C
double rot[9] # R, row-major 3x3
u32 crc32
Each sighting record (variable length, CRC32 over everything before the CRC):
u16 station_id
u16 peak_id
u8 mask # bit0: u present, bit1: v present
double u # if mask & 1
double v # if mask & 2
u32 crc32
This parses all 18 sightings cleanly (CRC32 verified). 10 are complete, 4 have
only u, 4 have only v.
The model
For perch C with world-to-eye rotation R and peak L:
Pc = R @ (L - C)
x = Xc/Zc, y = Yc/Zc
r2 = x^2 + y^2
D = 1 + k1*r2 + k2*r2^2
u = f*D*x + cx + dcx
v = f*D*y + cy + dcy
Unknowns: 3 peaks (9) + k1,k2 (2) + dcx,dcy (2) = 13. There are 28 scalar
observations, so the system is overdetermined and has a unique solution.
Solving
- Linear initialisation - ignore distortion/offset and solve the pinhole
equations per peak with least squares:
((u/f) R[2] - R[0]) * (L - C) = 0 (and the analogous v equation).
This gives peaks near (-25,421,62), (-31,386,22), (-5,413,43).
- Bundle adjustment - run Levenberg-Marquardt over all 13 unknowns and all
28 residuals. It converges to machine precision (max residual ~ 2e-13 px).
import json, numpy as np
from scipy.optimize import least_squares
P = json.load(open("k05_parsed.json"))
f, cx, cy = P["f"], P["cx"], P["cy"]
perches = {int(k): (np.array(v[0]), np.array(v[1]).reshape(3, 3))
for k, v in P["perches"].items()}
sights = P["sights"]
def residuals(x):
peaks = x[:9].reshape(3, 3)
k1, k2, dcx, dcy = x[9:]
out = []
for st, pk, mask, u, v in sights:
C, R = perches[st]
X, Y, Z = R @ (peaks[pk] - C)
xx, yy = X / Z, Y / Z
r2 = xx * xx + yy * yy
D = 1 + k1 * r2 + k2 * r2 * r2
if u is not None: out.append(u - (f * D * xx + cx + dcx))
if v is not None: out.append(v - (f * D * yy + cy + dcy))
return np.array(out)
x0 = np.concatenate([peaks_init.ravel(), [0, 0, 0, 0]])
r = least_squares(residuals, x0, method="lm",
xtol=1e-15, ftol=1e-15, gtol=1e-15, max_nfev=100000)
Result:
peaks = [[-22, 452, 68], [-26, 412, 27], [1, 437, 50]]
k1 = 0.129756
k2 = -0.046241
dcx = -5.925
dcy = 6.986
max residual ~ 2.3e-13 px
Completing the missing coordinates
Evaluate the model with the rounded submission parameters for each incomplete
sighting. axis=0 means a missing u, axis=1 a missing v;
value_milli = round(predicted * 1e3):
[0,1,0,-109809] [0,2,1,128699]
[1,0,0,-541219] [1,2,1,-389037]
[3,1,1,-70833] [3,2,0,-418968]
[4,0,0,-474587] [5,2,1,152426]
Submission values
distortion = [129756, -46241] # round(k*1e6)
calibration = [-5925, 6986] # round(d*1e3)
sight_range = round(|mean(peaks)|) = round(|(-15.667, 433.667, 48.333)|) = 437
Digest string:
P0:-22,452,68;P1:-26,412,27;P2:1,437,50;K:129756,-46241;C:-5925,6986;
M:0,1,0,-109809;M:0,2,1,128699;M:1,0,0,-541219;M:1,2,1,-389037;
M:3,1,1,-70833;M:3,2,0,-418968;M:4,0,0,-474587;M:5,2,1,152426
(the M: entries joined in ascending [station, peak, axis, value] order)
reconstruction_digest =
731a0108944f512b787b66c7721ca1990e99b0971a39e36c62246b9fd26a78db
Submission
curl -X POST https://pinaka-k05-cyber-xo-x.vercel.app/lanka/sighting \
-H 'Content-Type: application/json' -d @submission.json
Response:
{"flag":"PINAKA{sampati_shatayojana_drishti_lanka}",
"proclamation":"Sampati opens his hundred-yojana eyes: there stands Lanka",
"status":"sighting-confirmed"}