< BACK TO TERMINAL
MCTF2026 - Devil Diagram icon

MCTF2026 - Devil Diagram

You can download the challenge files and solutions here: MCTF2026-DevilDiagram-main.zip

Description (Player Facing)

Subject: Evidence Item #4B - "The St. Jude's Tapes" Background: St. Jude’s Asylum was shuttered in 1988 after the "incident" in the basement ward. Last night, urban explorers found an old medical XY-plotter plugged into a dead outlet. It was moving on its own. They recorded the sound of the machine screeching in the dark. It seems to be trying to draw something. Objective: Decipher the audio to see what the machine drew.

Solution

Step 1: Initial Reconnaissance

The player opens evidence_4b.wav in an audio editor (e.g., Sonic Visualizer or Audacity).

Step 1: Initial Reconnaissance — diagram

Step 2: Spectral Analysis

The player switches to a Spectrogram View. (Here we can see the two channels)

Step 2: Spectral Analysis — diagram

Step 2: Spectral Analysis — diagram

Step 2: Spectral Analysis — diagram

Step 3: Developing the Decoder

The player realizes they cannot decode this manually. They write a script to:

  1. Read the WAV file.
# 1. Load the file
rate, data = wavfile.read("challenge/evidence_4b.wav")
  1. Demodulate the Signal: Use the Hilbert Transform to extract the Instantaneous Frequency (pitch) of both channels.
# 2. Extract Signal (Hilbert Transform)
analytic_x = hilbert(data[:, 0])
analytic_y = hilbert(data[:, 1])

# Calculate frequency: frequency is the rate of change of the phase
freq_x = np.diff(np.unwrap(np.angle(analytic_x)))
freq_y = np.diff(np.unwrap(np.angle(analytic_y)))
  1. Detect Pen Lifts (Filter Silence): If the volume drops below a threshold, stop drawing.
# 3. Filter Silence (Simple "Pen Up" check)
# Only draw when the volume is loud enough (10% of max volume)
amplitude = np.abs(analytic_x[1:])  # Match length with freq array (which is one shorter)
mask = amplitude > (np.max(amplitude) * 0.1)
  1. Plot: Draw the extracted (X, Y) coordinates. Note: We skip manual normalization here because the Plotter automatically scales the graph.
# 4. Plot (We plot Frequency X vs Frequency Y)
plt.figure(figsize=(10, 10))
plt.scatter(freq_x[mask], freq_y[mask], s=0.1, c='green')  # Apply the mask to ignore silent sections
plt.axis('equal')        # Prevent squashing
plt.gca().invert_yaxis() # Fix SVG coordinate flip (origin is top-left)
plt.show()

Step 3: Developing the Decoder — diagram

← Previous writeupNext writeup →