Files
yoloyolo/.planning/phases/03-pipeline-integration-and-mvp/03-CONTEXT.md
T
2026-03-26 12:36:44 +01:00

6.1 KiB

Phase 3: Pipeline Integration and MVP - Context

Gathered: 2026-03-26 Status: Ready for planning

## Phase Boundary

Wire the capture-classify-aggregate pipeline into the audio synthesis and MP3 encoding engine. Deliver the complete v1 MVP: netsynth -i eth0 -o out.mp3 → capture → Ctrl+C → valid MP3. Replace the single "unknown" traffic class with 4 hash-bucketed sub-clusters, each producing a distinct dissonant drone tone.

## Implementation Decisions

Auto-clustering

  • D-01: Hash-bucketing approach — deterministic hash of (dst_port, protocol) into 4 fixed buckets. No k-means, no warmup period, no external dependency.
  • D-02: 4 buckets for unknown traffic. Total layer count becomes 14 (10 known + 4 unknown buckets).
  • D-03: Hash-bucketing happens inside the existing classifier — classifier returns unknown-1 through unknown-4 instead of plain unknown. New TrafficClass constants added.
  • D-04: Replace ClassUnknown entirely with ClassUnknown1-ClassUnknown4. AllClasses() returns 14 classes. Clean break, no fallback.

Cluster tone assignment

  • D-05: All 4 unknown buckets live in a dedicated 850-1100 Hz dissonant range, above the known protocol range (60-800 Hz). Slightly detuned intervals between them.
  • D-06: Same dissonant harmonic character for all 4 buckets (matching Phase 2's D-04/D-06 for the original "unknown" tone). Differentiated by pitch only, preserving the "something foreign" sonic identity.

Shutdown & encoding feedback

  • D-07: Status line during encoding: print Encoding N windows to <path>... then Saved <path> (Xs, N KB, encoded in Xs) on completion. Minimal but confirms encoding is happening.
  • D-08: Protocol summary prints BEFORE encoding. Ctrl+C → protocol summary → "Encoding..." → "Saved". User sees capture stats immediately.

Post-run output

  • D-09: Saved line includes path, audio duration, file size, and encoding time. e.g., Saved out.mp3 (12.5s, 198 KB, encoded in 0.3s).
  • D-10: Unknown buckets appear in the protocol summary naturally as unknown-1: 42, unknown-2: 17, etc. Falls out of existing summary logic since they're full TrafficClass values.

Claude's Discretion

  • Exact hash function for port/protocol → bucket mapping (within the 4-bucket constraint)
  • Exact Hz values for the 4 unknown bucket tones (within 850-1100 Hz range, detuned intervals)
  • GainPerLayer recalculation for 14 layers (was 1/11 for 11 layers)
  • Stereo panning positions for the 4 unknown buckets
  • Encoding time measurement implementation

<canonical_refs>

Canonical References

Downstream agents MUST read these before planning or implementing.

Project context

  • .planning/PROJECT.md — Core value, constraints (Go, MP3 output, non-interactive)
  • .planning/REQUIREMENTS.md — CAPT-03 (graceful flush), CLAS-02 (auto-clustering)

Prior phase context

  • .planning/phases/01-capture-and-classification/01-CONTEXT.md — Phase 1 decisions; classifier design, pipeline architecture
  • .planning/phases/02-audio-synthesis-engine/02-CONTEXT.md — Phase 2 decisions; tone mapping, mixing, encoding

Key source files (integration points)

  • cmd/netsynth/main.go — CLI wiring with TODO(phase-3) at line 99; accumulator loop at lines 92-97
  • encode/mp3.goRunSynthesis() and EncodeMP3() — the synthesis entry point
  • synth/bank.goOscillatorBank and RenderWindow() — renders audio from WindowSnapshots
  • synth/config.goClassFreqConfigs map, NumLayers, GainPerLayer constants
  • classify/types.goTrafficClass constants, AllClasses(), WindowSnapshot struct
  • classify/rules.go — Classification rules (first-match-wins ordered slice)
  • classify/classifier.goClassify() method where hash-bucketing will be added
  • aggregate/summary.goPrintSummary() and PrintWindowLine() for exit output

Stack decisions

  • CLAUDE.md — Technology stack section; muesli/kmeans listed but NOT needed (hash-bucketing chosen instead)

</canonical_refs>

<code_context>

Existing Code Insights

Reusable Assets

  • encode.RunSynthesis([]WindowSnapshot, string) error — already takes snapshots and produces MP3; main.go just needs to call it
  • synth.NewBank(tau) / bank.RenderWindow(snap) — synthesis engine already works with WindowSnapshot
  • classify.NewClassifier(rules) / classifier.Classify(pkt) — classifier where hash-bucketing logic will be added
  • aggregate.PrintSummary(io.Writer, map[TrafficClass]int64) — exit summary; will naturally show unknown-1 through unknown-4

Established Patterns

  • Channel-based pipeline: capture → classify → aggregate → (synthesis goes here)
  • done <-chan struct{} for shutdown signaling via signal.NotifyContext
  • Buffered channels for stage decoupling (512-buffer capture, 1024-buffer classified, 8-buffer aggregate)
  • io.Writer injection for testable output (aggregate/summary.go)
  • Config-driven classifier with ordered []Rule slice, first-match-wins

Integration Points

  • main.go:99 — Replace _ = collectedSnapshots with encode.RunSynthesis(collectedSnapshots, outputPath) call
  • main.go:92-97 — Snapshot accumulator loop already collects snapshots; wiring is mechanical
  • synth/config.goClassFreqConfigs map needs 4 new entries for unknown buckets; NumLayers and GainPerLayer need updating from 11 to 14
  • classify/types.go — Remove ClassUnknown, add ClassUnknown1-ClassUnknown4, update AllClasses()

</code_context>

## Specific Ideas
  • The end-to-end flow should feel like tcpdump: start capturing, see activity, Ctrl+C, get your results
  • Unknown bucket tones should be immediately recognizable as "something foreign" — the dissonant character is the sonic signal, pitch differentiates which bucket
  • The protocol summary + saved line should give the user everything they need at a glance without running ffprobe
## Deferred Ideas

None — discussion stayed within phase scope


Phase: 03-pipeline-integration-and-mvp Context gathered: 2026-03-26