--- phase: 09-frequency-design-and-group-architecture plan: 01 type: execute wave: 1 depends_on: [] files_modified: - synth/config.go - synth/config_test.go autonomous: true requirements: - FREQ-01 - FREQ-02 - FREQ-03 - GRP-01 - GRP-04 must_haves: truths: - "FreqConfig struct has a Group string field" - "All 14 existing ClassFreqConfigs entries have non-empty Group values" - "All existing classes are redistributed to new Hz values per the major-second ladder (65-1375 Hz range)" - "Within-family adjacent pairs satisfy at least a major second interval (ratio >= 1.122)" - "Non-Unknown family entries use WaveformPresetHarmonics instead of hand-tuned slices" - "Unknown family entries retain hand-tuned dissonant harmonics {1,1.0},{2,0.8},{3,0.4}" - "go test ./synth/... passes with all tests green" artifacts: - path: "synth/config.go" provides: "FreqConfig with Group field, rebalanced ClassFreqConfigs map" contains: "Group string" - path: "synth/config_test.go" provides: "TestGroupFieldPopulated, updated TestHarmonicsNonEmpty" contains: "TestGroupFieldPopulated" key_links: - from: "synth/config.go" to: "classify/types.go" via: "ClassFreqConfigs map keys reference classify.TrafficClass constants" pattern: "classify\\.Class" --- Add Group field to FreqConfig and rebalance all 14 existing ClassFreqConfigs entries to the new major-second frequency ladder with family-band organization, waveform-per-family assignments, and Group string values. Purpose: Lock the frequency design and group architecture so Phase 10 can add new TrafficClass constants that slot directly into the designed frequency bands. Per D-01, this is a full rebalance -- existing v1.0/v1.1 Hz values are NOT frozen. Output: Updated synth/config.go with Group field and rebalanced map, updated synth/config_test.go with new/fixed tests. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/09-frequency-design-and-group-architecture/09-CONTEXT.md @.planning/phases/09-frequency-design-and-group-architecture/09-RESEARCH.md ```go type FreqConfig struct { BaseHz float64 Harmonics []HarmonicDef Pan float64 WaveformType WaveformType } ``` ```go const ( WaveformCustom WaveformType = iota WaveformSine WaveformSquare WaveformSawtooth WaveformTriangle ) ``` ```go func WaveformPresetHarmonics(wt WaveformType, baseHz float64, sampleRate int) []HarmonicDef ``` ``` ClassICMP, ClassDNS, ClassHTTPS, ClassHTTP, ClassSSH, ClassSMTP, ClassNTP, ClassDHCP, ClassOtherTCP, ClassOtherUDP, ClassUnknown1, ClassUnknown2, ClassUnknown3, ClassUnknown4 ``` Task 1: Add Group field to FreqConfig and rebalance ClassFreqConfigs synth/config.go - synth/config.go (current FreqConfig struct at line 66, ClassFreqConfigs map at line 76) - .planning/phases/09-frequency-design-and-group-architecture/09-RESEARCH.md (full allocation table, code examples) 1. Add `Group string` field to the `FreqConfig` struct after the `WaveformType` field: ```go type FreqConfig struct { BaseHz float64 Harmonics []HarmonicDef Pan float64 WaveformType WaveformType Group string // sound family: "Infrastructure", "Web", "Mail", etc. } ``` 2. Add a frequency allocation table comment block above ClassFreqConfigs documenting all 32 designed slots (per FREQ-03). This is the authoritative design reference. The comment should list: ``` // Frequency Allocation Table (Phase 9 design — major-second ladder, 65-2449 Hz) // // Slot Hz Class Group Waveform Pan // 0 65 ICMP Infrastructure Triangle -0.3 // 1 73 NTP Infrastructure Triangle -0.1 // 2 82 DHCP Infrastructure Triangle 0.1 // 3 93 mDNS Infrastructure Triangle 0.3 (Phase 10) // 4 105 SSDP Infrastructure Triangle -0.2 (Phase 10) // 5 118 SNMP Infrastructure Triangle 0.2 (Phase 10) // 6 133 DNS Infrastructure Triangle 0.0 // 7 150 HTTPS Web Sawtooth -0.4 // 8 169 HTTP Web Sawtooth -0.3 // 9 190 HTTP3 Web Sawtooth -0.2 (Phase 10) // 10 214 SMTP Mail Triangle 0.2 // 11 241 IMAP Mail Triangle 0.3 (Phase 10) // 12 271 POP3 Mail Triangle 0.4 (Phase 10) // 13 305 SMTP-sub Mail Triangle 0.5 (Phase 10) // 14 343 SSH Remote Access Square -0.7 // 15 385 RDP Remote Access Square -0.6 (Phase 10) // 16 432 Telnet Remote Access Square -0.5 (Phase 10) // 17 485 VNC Remote Access Square -0.4 (Phase 10) // 18 545 FTP File Transfer Square 0.5 (Phase 10) // 19 612 SMB File Transfer Square 0.6 (Phase 10) // 20 687 TFTP File Transfer Square 0.7 (Phase 10) // 21 771 unknown-1 Unknown Custom -0.9 // 22 866 unknown-2 Unknown Custom 0.9 // 23 972 unknown-3 Unknown Custom -0.7 // 24 1091 unknown-4 Unknown Custom 0.7 // 25 1225 other-TCP Unknown Custom -0.5 // 26 1375 other-UDP Unknown Custom 0.5 // 27 1543 MySQL Database Sawtooth -0.4 (Phase 10) // 28 1732 PostgreSQL Database Sawtooth -0.2 (Phase 10) // 29 1944 Redis Database Sawtooth 0.2 (Phase 10) // 30 2182 MongoDB Database Sawtooth 0.4 (Phase 10) // 31 2449 SIP VoIP Sine 0.0 (Phase 10) // // Auto-assign range: [2500, 4000] Hz (see config/config.go) ``` 3. Replace the entire `ClassFreqConfigs` map literal with the rebalanced entries for the 14 existing classes. Use `WaveformPresetHarmonics` for non-Unknown entries (per D-08/D-09). Unknown entries retain hand-tuned harmonics. Exact entries (all 14): ```go var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{ // --- Infrastructure (Triangle, 65-133 Hz) --- classify.ClassICMP: { BaseHz: 65.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 65.0, SampleRate), Pan: -0.3, Group: "Infrastructure", }, classify.ClassNTP: { BaseHz: 73.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 73.0, SampleRate), Pan: -0.1, Group: "Infrastructure", }, classify.ClassDHCP: { BaseHz: 82.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 82.0, SampleRate), Pan: 0.1, Group: "Infrastructure", }, classify.ClassDNS: { BaseHz: 133.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 133.0, SampleRate), Pan: 0.0, Group: "Infrastructure", }, // --- Web (Sawtooth, 150-190 Hz) --- classify.ClassHTTPS: { BaseHz: 150.0, WaveformType: WaveformSawtooth, Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 150.0, SampleRate), Pan: -0.4, Group: "Web", }, classify.ClassHTTP: { BaseHz: 169.0, WaveformType: WaveformSawtooth, Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 169.0, SampleRate), Pan: -0.3, Group: "Web", }, // --- Mail (Triangle, 214 Hz) --- classify.ClassSMTP: { BaseHz: 214.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 214.0, SampleRate), Pan: 0.2, Group: "Mail", }, // --- Remote Access (Square, 343 Hz) --- classify.ClassSSH: { BaseHz: 343.0, WaveformType: WaveformSquare, Harmonics: WaveformPresetHarmonics(WaveformSquare, 343.0, SampleRate), Pan: -0.7, Group: "Remote Access", }, // --- Unknown (Custom harmonics, 771-1375 Hz) --- classify.ClassUnknown1: { BaseHz: 771.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: -0.9, Group: "Unknown", }, classify.ClassUnknown2: { BaseHz: 866.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: 0.9, Group: "Unknown", }, classify.ClassUnknown3: { BaseHz: 972.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: -0.7, Group: "Unknown", }, classify.ClassUnknown4: { BaseHz: 1091.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: 0.7, Group: "Unknown", }, classify.ClassOtherTCP: { BaseHz: 1225.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: -0.5, Group: "Unknown", }, classify.ClassOtherUDP: { BaseHz: 1375.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: 0.5, Group: "Unknown", }, } ``` IMPORTANT: Unknown family entries (ClassUnknown1-4, ClassOtherTCP, ClassOtherUDP) must NOT set WaveformType — leave it as zero value (WaveformCustom) so bank.go uses the stored Harmonics. Do NOT call WaveformPresetHarmonics for these entries. IMPORTANT: Do NOT remove the old `// Frequencies use musical intervals...` comment — replace it with the new allocation table comment. cd /home/dev/workspace/yoloyolo && go build ./synth/... - synth/config.go contains `Group string` inside the FreqConfig struct - synth/config.go contains `Group: "Infrastructure"` (at least 4 entries) - synth/config.go contains `Group: "Web"` (at least 2 entries) - synth/config.go contains `Group: "Mail"` (at least 1 entry) - synth/config.go contains `Group: "Remote Access"` (at least 1 entry) - synth/config.go contains `Group: "Unknown"` (at least 6 entries) - synth/config.go contains `BaseHz: 65.0` for ClassICMP - synth/config.go contains `BaseHz: 133.0` for ClassDNS - synth/config.go contains `BaseHz: 150.0` for ClassHTTPS - synth/config.go contains `BaseHz: 343.0` for ClassSSH - synth/config.go contains `WaveformPresetHarmonics(WaveformTriangle` for Infrastructure entries - synth/config.go contains `WaveformPresetHarmonics(WaveformSawtooth` for Web entries - synth/config.go contains `WaveformPresetHarmonics(WaveformSquare` for SSH entry - synth/config.go contains `// Frequency Allocation Table` comment block - Unknown entries do NOT contain `WaveformPresetHarmonics` — they use literal `[]HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}` - `go build ./synth/...` exits 0 FreqConfig has Group field, all 14 ClassFreqConfigs entries have correct Hz/waveform/group per allocation table, code compiles Task 2: Update synth tests — add TestGroupFieldPopulated, fix TestHarmonicsNonEmpty synth/config_test.go - synth/config_test.go (current test file — TestHarmonicsNonEmpty at line 40 checks len >= 2) - synth/config.go (the updated file from Task 1 — verify Group field exists) 1. Add `TestGroupFieldPopulated` test function after the existing tests. This test iterates all ClassFreqConfigs entries and fails if any have an empty Group string: ```go func TestGroupFieldPopulated(t *testing.T) { for class, cfg := range synth.ClassFreqConfigs { if cfg.Group == "" { t.Errorf("class %q has empty Group field in ClassFreqConfigs", class) } } } ``` 2. Update `TestHarmonicsNonEmpty` to accept single-harmonic entries. Change the threshold from `< 2` to `< 1`. This is needed because WaveformSine (used by SIP in Phase 10) returns only 1 harmonic. The current 8 non-Unknown entries all use Triangle/Sawtooth/Square which return multiple harmonics, so this change is safe now and future-proofs for Phase 10. Change line 42 from: ```go if len(cfg.Harmonics) < 2 { t.Errorf("class %q has fewer than 2 harmonics (got %d)", class, len(cfg.Harmonics)) ``` To: ```go if len(cfg.Harmonics) < 1 { t.Errorf("class %q has no harmonics (got %d)", class, len(cfg.Harmonics)) ``` This addresses Research Open Question 2: pure sine (1 harmonic) is valid, not a bug. cd /home/dev/workspace/yoloyolo && go test ./synth/... -v -count=1 - synth/config_test.go contains `func TestGroupFieldPopulated(t *testing.T)` - synth/config_test.go contains `cfg.Group == ""` - synth/config_test.go contains `len(cfg.Harmonics) < 1` (NOT `< 2`) - `go test ./synth/... -run TestGroupFieldPopulated` exits 0 - `go test ./synth/... -run TestHarmonicsNonEmpty` exits 0 - `go test ./synth/...` exits 0 (all tests pass) TestGroupFieldPopulated catches missing Group values; TestHarmonicsNonEmpty accepts single-harmonic (sine) entries; all synth tests pass - `go build ./synth/...` compiles without errors - `go test ./synth/... -v` passes all tests including new TestGroupFieldPopulated - `grep -c 'Group:' synth/config.go` returns 14 (one per ClassFreqConfigs entry) - `grep 'Group string' synth/config.go` confirms struct field exists - FreqConfig struct has Group string field (GRP-04) - All 14 existing ClassFreqConfigs entries have Group values matching their family (GRP-01) - Frequency allocation table comment documents all 32 designed slots (FREQ-03) - All existing classes redistributed to major-second ladder Hz values (FREQ-01) - Within-family spacing satisfies >= 1.122 ratio (FREQ-02) - All synth tests pass including new TestGroupFieldPopulated After completion, create `.planning/phases/09-frequency-design-and-group-architecture/09-01-SUMMARY.md`