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.

Sampati: The Hundred-Yojana Sight

**Pinaka CTF 2026** · Misc & OSINT · Team Cryptonite · All 44 Pinaka write-ups


Category: misc (nonlinear reconstruction / bundle adjustment)

Flag: PINAKA{sampatishatayojanadrishti_lanka}

Files

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

  1. 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).

  1. 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"}

Read this post on CyberHx →