chore: archive v1.2 milestone — Extended Protocol Coverage

35 traffic classes across 9 protocol families shipped. Archives
ROADMAP, REQUIREMENTS, and phase directories to milestones/v1.2-*.
Updates README with new protocol families, sound design table,
and [groups] TOML config documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 16:48:53 +01:00
co-authored by Claude Opus 4.6
parent 0a4d48c9c1
commit 494385b528
42 changed files with 290 additions and 166 deletions
@@ -0,0 +1,364 @@
---
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"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
<interfaces>
<!-- From synth/config.go — current FreqConfig struct (line 66-71) -->
```go
type FreqConfig struct {
BaseHz float64
Harmonics []HarmonicDef
Pan float64
WaveformType WaveformType
}
```
<!-- From synth/config.go — WaveformType constants (line 14-22) -->
```go
const (
WaveformCustom WaveformType = iota
WaveformSine
WaveformSquare
WaveformSawtooth
WaveformTriangle
)
```
<!-- From synth/config.go — WaveformPresetHarmonics signature (line 27) -->
```go
func WaveformPresetHarmonics(wt WaveformType, baseHz float64, sampleRate int) []HarmonicDef
```
<!-- From classify/types.go — existing TrafficClass constants used as map keys -->
```
ClassICMP, ClassDNS, ClassHTTPS, ClassHTTP, ClassSSH, ClassSMTP,
ClassNTP, ClassDHCP, ClassOtherTCP, ClassOtherUDP,
ClassUnknown1, ClassUnknown2, ClassUnknown3, ClassUnknown4
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add Group field to FreqConfig and rebalance ClassFreqConfigs</name>
<files>synth/config.go</files>
<read_first>
- 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)
</read_first>
<action>
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.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && go build ./synth/...</automated>
</verify>
<acceptance_criteria>
- 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
</acceptance_criteria>
<done>FreqConfig has Group field, all 14 ClassFreqConfigs entries have correct Hz/waveform/group per allocation table, code compiles</done>
</task>
<task type="auto">
<name>Task 2: Update synth tests — add TestGroupFieldPopulated, fix TestHarmonicsNonEmpty</name>
<files>synth/config_test.go</files>
<read_first>
- 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)
</read_first>
<action>
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.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && go test ./synth/... -v -count=1</automated>
</verify>
<acceptance_criteria>
- 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)
</acceptance_criteria>
<done>TestGroupFieldPopulated catches missing Group values; TestHarmonicsNonEmpty accepts single-harmonic (sine) entries; all synth tests pass</done>
</task>
</tasks>
<verification>
- `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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/09-frequency-design-and-group-architecture/09-01-SUMMARY.md`
</output>
@@ -0,0 +1,120 @@
---
phase: 09-frequency-design-and-group-architecture
plan: "01"
subsystem: synth
tags: [frequency-design, group-architecture, config, oscillator-fix]
dependency_graph:
requires: [08-01]
provides: [FreqConfig.Group, rebalanced-ClassFreqConfigs, oscillator-normalization-fix]
affects: [synth, config, bank_test, config_test]
tech_stack:
added: []
patterns:
- "Major-second ladder Hz allocation for family-band frequency design"
- "math.Abs normalization for alternating-sign harmonic series in additive oscillator"
key_files:
created: []
modified:
- synth/config.go
- synth/config_test.go
- synth/oscillator.go
- synth/bank_test.go
- config/config_test.go
decisions:
- "Group field added to FreqConfig as a string (not enum) — extensible for Phase 10 new family names without code changes"
- "Within-family spacing satisfies major second (1.122 ratio): 65->73 = 1.123, 73->82 = 1.123, 82->133 = 1.622 (Infrastructure skips slots reserved for Phase 10), 150->169 = 1.127 (Web), 771->866 = 1.123 (Unknown)"
- "Oscillator normalization fixed to use math.Abs — alternating-sign harmonics (triangle wave) were causing output to exceed [-1,1] bounds when using WaveformPresetHarmonics"
metrics:
duration: "6m 30s"
completed: "2026-03-27"
tasks_completed: 2
files_modified: 5
---
# Phase 9 Plan 1: Frequency Design and Group Architecture Summary
FreqConfig gains a Group string field; all 14 ClassFreqConfigs entries rebalanced to major-second ladder in family frequency bands (65-1375 Hz), with family-specific waveforms. Oscillator normalization bug fixed for alternating-sign harmonic series.
## What Was Built
### Task 1: Add Group field to FreqConfig and rebalance ClassFreqConfigs
`synth/config.go` was updated with:
1. `Group string` field added to `FreqConfig` struct after `WaveformType`
2. Frequency allocation table comment block documenting all 32 designed slots (Phase 9 + Phase 10 placeholders)
3. Stale `NumLayers` and `GainPerLayer` constants removed (Phase 8 cleanup carried forward)
4. All 14 `ClassFreqConfigs` entries rebalanced to the major-second ladder:
- Infrastructure (Triangle, 65-133 Hz): ICMP=65, NTP=73, DHCP=82, DNS=133
- Web (Sawtooth, 150-169 Hz): HTTPS=150, HTTP=169
- Mail (Triangle, 214 Hz): SMTP=214
- Remote Access (Square, 343 Hz): SSH=343
- Unknown (Custom harmonics, 771-1375 Hz): Unknown1=771, Unknown2=866, Unknown3=972, Unknown4=1091, OtherTCP=1225, OtherUDP=1375
Non-Unknown entries use `WaveformPresetHarmonics`; Unknown entries retain hand-tuned `{1,1.0},{2,0.8},{3,0.4}` with `WaveformType` left as zero value (`WaveformCustom`) so `bank.go` uses stored harmonics directly.
### Task 2: Update synth tests and fix oscillator normalization bug
`synth/config_test.go`:
- `TestGroupFieldPopulated` added: iterates all ClassFreqConfigs entries, fails if any have empty Group
- `TestHarmonicsNonEmpty` threshold changed from `< 2` to `< 1` (accepts single-harmonic sine entries for Phase 10 SIP)
**Auto-fix (Rule 1 - Bug): Oscillator normalization for alternating-sign harmonics**
Found during Task 2 verification: `TestNewBankCustomConfigNoClip` was failing with `L=-1.023`. Root cause: `oscillator.go` `Advance()` accumulated `totalWeight += h.Amplitude` (signed sum), but `WaveformPresetHarmonics(WaveformTriangle, ...)` produces alternating-sign amplitudes. The signed sum (~0.916 for 65 Hz triangle with 170 harmonics) is much smaller than the absolute sum (~1.232), causing the normalization denominator to be deflated, which inflated output amplitude beyond [-1, 1].
Fix: changed to `totalWeight += math.Abs(h.Amplitude)` in `oscillator.go`. The old config used positive-only amplitudes `{k, 1/k}` so the bug was latent. The new `WaveformPresetHarmonics(WaveformTriangle, ...)` exposed it.
**Test updates (Rule 1 - Bug):**
- `synth/bank_test.go`: `TestStereoPan` updated from ClassDHCP (new pan=0.1, right-biased) to ClassSSH (pan=-0.7, wide-left); `TestNewBankCustomConfigNoClip` passes after oscillator fix
- `config/config_test.go`: `TestLoadPartialOverrideFrequency` changed from hardcoded `WaveformCustom` to `synth.ClassFreqConfigs[classify.ClassICMP].WaveformType` (ICMP is now WaveformTriangle); `TestAutoFreqSkipsBuiltins` changed from hardcoded 175.0 to `synth.ClassFreqConfigs[classify.ClassHTTPS].BaseHz` (now 150.0)
## Verification Results
```
go build ./synth/... → exit 0
go test ./synth/... -v -count=1 → 42 tests PASS
go test ./... → all 7 packages PASS
grep -c 'Group:' synth/config.go → 14
grep 'Group string' synth/config.go → FOUND
grep -c 'BaseHz: 65.0' synth/config.go → ICMP confirmed
grep '// Frequency Allocation Table' synth/config.go → FOUND
```
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Fixed oscillator normalization for alternating-sign harmonic amplitudes**
- **Found during:** Task 2 verification (`go test ./synth/...`)
- **Issue:** `oscillator.Advance()` used `totalWeight += h.Amplitude` (signed sum). Triangle wave harmonics from `WaveformPresetHarmonics` alternate signs (1, -1/9, 1/25, -1/49...), making signed total ~0.916 vs absolute total ~1.232. This caused 34% undercount, so output exceeded [-1, 1] bounds.
- **Fix:** Changed to `totalWeight += math.Abs(h.Amplitude)` in `synth/oscillator.go`
- **Files modified:** `synth/oscillator.go`, `synth/bank_test.go`, `config/config_test.go`
- **Commit:** c97682d
**2. [Rule 1 - Bug] Updated TestStereoPan for new ClassDHCP pan value**
- **Found during:** Task 2 verification
- **Issue:** TestStereoPan expected ClassDHCP to be left-panned (old pan=-0.75) but new config has pan=0.1 (right-biased)
- **Fix:** Changed test to use ClassSSH (pan=-0.7, clearly left-biased in new config)
- **Files modified:** `synth/bank_test.go`
- **Commit:** c97682d
**3. [Rule 1 - Bug] Updated config_test.go for new default values**
- **Found during:** Task 2 running `go test ./...`
- **Issue:** Two config tests had hardcoded values from old defaults (ICMP WaveformCustom, HTTPS BaseHz=175.0)
- **Fix:** Changed assertions to derive expected values from `synth.ClassFreqConfigs` defaults
- **Files modified:** `config/config_test.go`
- **Commit:** c97682d
## Known Stubs
None. All 14 ClassFreqConfigs entries have populated Group, Hz, waveform, and harmonics. Phase 10 slot placeholders are documented in comments only.
## Self-Check: PASSED
- synth/config.go: FOUND
- synth/config_test.go: FOUND
- synth/oscillator.go: FOUND
- 09-01-SUMMARY.md: FOUND
- Commit 812f0de: FOUND (feat: Group field + rebalanced ClassFreqConfigs)
- Commit c97682d: FOUND (fix: TestGroupFieldPopulated + oscillator normalization + test updates)
@@ -0,0 +1,206 @@
---
phase: 09-frequency-design-and-group-architecture
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
- config/config.go
- config/config_test.go
autonomous: true
requirements:
- FREQ-04
must_haves:
truths:
- "autoAssignFreq returns Hz values in [2500, 4000] range, not the old [1200, 2350]"
- "Auto-assigned classes cannot collide with any built-in frequency (max built-in is 2449 Hz, auto-assign starts at 2500 Hz)"
- "TestAutoFreqAssignment passes with the new range bounds"
- "addAutoFreqEntries produces FreqConfig with empty Group (zero value) for user-defined classes"
artifacts:
- path: "config/config.go"
provides: "Updated autoAssignFreq constants"
contains: "baseHz = 2500.0"
- path: "config/config_test.go"
provides: "Updated range assertion"
contains: "cfg.BaseHz < 2500.0 || cfg.BaseHz > 4000.0"
key_links:
- from: "config/config.go"
to: "synth/config.go"
via: "addAutoFreqEntries creates synth.FreqConfig entries"
pattern: "synth\\.FreqConfig"
---
<objective>
Update the auto-assign frequency range from [1200, 2350] to [2500, 4000] Hz so custom user-defined classes cannot collide with any built-in frequency. Update the corresponding test assertion.
Purpose: Per D-04, the auto-assign range must be pushed above all built-in frequencies (max 2449 Hz) to prevent collision. The 51 Hz buffer between 2449 and 2500 guarantees no overlap.
Output: Updated config/config.go constants, updated config/config_test.go assertion.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
<interfaces>
<!-- From config/config.go — current autoAssignFreq function (lines 208-219) -->
```go
func autoAssignFreq(className string) float64 {
h := fnv.New32a()
h.Write([]byte(className))
const (
baseHz = 1200.0
stepHz = 50.0
numSteps = uint32(24)
)
return baseHz + float64(h.Sum32()%numSteps)*stepHz
}
```
<!-- From config/config.go — addAutoFreqEntries (lines 225-238) -->
```go
func addAutoFreqEntries(cfgs map[classify.TrafficClass]synth.FreqConfig, userRules []classify.Rule, autoClasses map[classify.TrafficClass]bool) {
for _, rule := range userRules {
if _, exists := cfgs[rule.Class]; !exists {
baseHz := autoAssignFreq(string(rule.Class))
cfgs[rule.Class] = synth.FreqConfig{
BaseHz: baseHz,
WaveformType: synth.WaveformSine,
Harmonics: synth.WaveformPresetHarmonics(synth.WaveformSine, baseHz, synth.SampleRate),
Pan: 0.0,
}
autoClasses[rule.Class] = true
}
}
}
```
<!-- From config/config_test.go — current range assertion (line 363) -->
```go
if cfg.BaseHz < 1200.0 || cfg.BaseHz > 2350.0 {
t.Errorf("GameServer BaseHz: got %v, want in [1200, 2350]", cfg.BaseHz)
}
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Update autoAssignFreq constants and comment</name>
<files>config/config.go</files>
<read_first>
- config/config.go (lines 208-238: autoAssignFreq function and addAutoFreqEntries)
</read_first>
<action>
1. In the `autoAssignFreq` function (line 208-219), update three things:
a. Change the function comment from `[1200, 2350]` to `[2500, 4000]`:
```go
// autoAssignFreq computes a deterministic frequency in [2500, 4000] Hz for a class name
```
b. Change the constants inside the function:
```go
const (
baseHz = 2500.0
stepHz = 50.0
numSteps = uint32(31) // [2500, 4000] Hz in 50 Hz steps
)
```
Verification: `2500.0 + float64(30)*50.0 = 4000.0`. With numSteps=31, the modulo produces values 0..30, yielding Hz values 2500, 2550, ..., 4000.
2. Do NOT modify `addAutoFreqEntries`. The Group field will be zero-value `""` for auto-assigned entries, which is correct — user-defined classes are not part of any built-in family. The struct literal in addAutoFreqEntries does not need a `Group:` field because Go zero-initializes missing named fields.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && go build ./config/...</automated>
</verify>
<acceptance_criteria>
- config/config.go contains `baseHz = 2500.0` (NOT 1200.0)
- config/config.go contains `numSteps = uint32(31)` (NOT 24)
- config/config.go contains `// autoAssignFreq computes a deterministic frequency in [2500, 4000] Hz`
- config/config.go still contains `stepHz = 50.0` (unchanged)
- `go build ./config/...` exits 0
</acceptance_criteria>
<done>autoAssignFreq produces frequencies in [2500, 4000] Hz range with 31 steps of 50 Hz</done>
</task>
<task type="auto">
<name>Task 2: Update config_test.go auto-assign range assertion</name>
<files>config/config_test.go</files>
<read_first>
- config/config_test.go (search for `1200.0` and `2350.0` — the TestAutoFreqAssignment assertion around line 363)
</read_first>
<action>
1. In `TestAutoFreqAssignment` (around line 363), update the range assertion:
Change from:
```go
if cfg.BaseHz < 1200.0 || cfg.BaseHz > 2350.0 {
t.Errorf("GameServer BaseHz: got %v, want in [1200, 2350]", cfg.BaseHz)
}
```
To:
```go
if cfg.BaseHz < 2500.0 || cfg.BaseHz > 4000.0 {
t.Errorf("GameServer BaseHz: got %v, want in [2500, 4000]", cfg.BaseHz)
}
```
2. Also update the test comment at the top of TestAutoFreqAssignment (around line 344-346) from:
```
// NO [sounds.GameServer] -> FreqCfgs contains "GameServer" entry with BaseHz in [1200, 2350]
```
To:
```
// NO [sounds.GameServer] -> FreqCfgs contains "GameServer" entry with BaseHz in [2500, 4000]
```
3. Check if `TestAutoFreqSkipsBuiltins` (around line 563) references `BaseHz == 175.0` for HTTPS. After Plan 01 rebalances HTTPS to 150.0, this test will need its expected value updated from `175.0` to `150.0`. Find the assertion and update it.
Search for `175.0` in config_test.go and update to `150.0` wherever it references the built-in HTTPS BaseHz.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && go test ./config/... -v -count=1 -run "TestAutoFreq"</automated>
</verify>
<acceptance_criteria>
- config/config_test.go contains `cfg.BaseHz < 2500.0 || cfg.BaseHz > 4000.0` (NOT 1200/2350)
- config/config_test.go contains `want in [2500, 4000]` in the error message
- config/config_test.go contains `BaseHz in [2500, 4000]` in the test comment
- If HTTPS 175.0 was referenced, it is now 150.0
- `go test ./config/... -run TestAutoFreqAssignment` exits 0
- `go test ./config/... -run TestAutoFreqDeterministic` exits 0
- `go test ./config/... -run TestAutoFreqSkipsBuiltins` exits 0
</acceptance_criteria>
<done>All auto-assign test assertions match new [2500, 4000] range; HTTPS built-in reference updated to 150.0 if applicable; all config tests pass</done>
</task>
</tasks>
<verification>
- `go build ./config/...` compiles without errors
- `go test ./config/... -v -run "TestAutoFreq"` passes all auto-freq tests
- `go test ./...` passes full suite (cross-package with synth changes from Plan 01)
</verification>
<success_criteria>
- autoAssignFreq base is 2500.0 Hz, not 1200.0 (FREQ-04)
- numSteps is 31, producing range [2500, 4000] with 50 Hz steps
- No collision possible between built-in frequencies (max 2449 Hz) and auto-assign (min 2500 Hz)
- All config tests pass with updated assertions
</success_criteria>
<output>
After completion, create `.planning/phases/09-frequency-design-and-group-architecture/09-02-SUMMARY.md`
</output>
@@ -0,0 +1,78 @@
---
phase: 09-frequency-design-and-group-architecture
plan: "02"
subsystem: config
tags: [frequency-design, auto-assign, collision-prevention, test-update]
dependency_graph:
requires: []
provides: [updated-autoAssignFreq-range, collision-free-auto-assign]
affects: [config/config.go, config/config_test.go]
tech_stack:
added: []
patterns: [FNV-32a hash, deterministic frequency assignment]
key_files:
created: []
modified:
- config/config.go
- config/config_test.go
decisions:
- "autoAssignFreq range moved to [2500, 4000] Hz with numSteps=31 (50 Hz steps) to guarantee 51 Hz gap above max built-in frequency of 2449 Hz"
- "TestAutoFreqSkipsBuiltins updated to expect 150.0 for HTTPS (coordinated with Plan 01 rebalancing)"
metrics:
duration: "~3 min"
completed: "2026-03-27"
tasks_completed: 2
files_modified: 2
---
# Phase 9 Plan 2: Auto-Assign Frequency Range Update Summary
Update autoAssignFreq to use [2500, 4000] Hz with 31 steps of 50 Hz, ensuring no collision with any built-in frequency (max 2449 Hz after Phase 9 Plan 1 rebalancing).
## What Was Built
Updated the `autoAssignFreq` function constants and corresponding test assertions so user-defined traffic classes are always assigned frequencies in the [2500, 4000] Hz range, safely above all built-in protocol frequencies.
## Tasks Completed
| Task | Name | Commit | Files |
|------|------|--------|-------|
| 1 | Update autoAssignFreq constants and comment | 8ae1ddc | config/config.go |
| 2 | Update config_test.go auto-assign range assertion | dbcbeb3 | config/config_test.go |
## Changes Made
### Task 1: config/config.go
- Changed `baseHz` constant from `1200.0` to `2500.0`
- Changed `numSteps` from `uint32(24)` to `uint32(31)` (covers [2500, 4000] in 50 Hz steps: 2500 + 30*50 = 4000)
- Updated function comment from `[1200, 2350]` to `[2500, 4000]`
- `stepHz = 50.0` unchanged
### Task 2: config/config_test.go
- Updated `TestAutoFreqAssignment` comment from `[1200, 2350]` to `[2500, 4000]`
- Updated range bounds assertion from `< 1200.0 || > 2350.0` to `< 2500.0 || > 4000.0`
- Updated `TestAutoFreqSkipsBuiltins` HTTPS expected value from `175.0` to `150.0` (coordinated with Plan 01 HTTPS rebalancing)
## Verification Results
- `go build ./config/...` exits 0
- `TestAutoFreqAssignment` passes
- `TestAutoFreqDeterministic` passes
- `TestAutoFreqSkipsBuiltins` will pass once Plan 01 updates HTTPS BaseHz to 150.0
## Deviations from Plan
None - plan executed exactly as written.
## Known Stubs
None.
## Self-Check: PASSED
- config/config.go contains `baseHz = 2500.0`: FOUND
- config/config.go contains `numSteps = uint32(31)`: FOUND
- config/config_test.go contains `cfg.BaseHz < 2500.0 || cfg.BaseHz > 4000.0`: FOUND
- Commits 8ae1ddc and dbcbeb3 exist in git log
@@ -0,0 +1,111 @@
# Phase 9: Frequency Design and Group Architecture - Context
**Gathered:** 2026-03-27
**Status:** Ready for planning
<domain>
## Phase Boundary
Design a complete, documented frequency allocation table for all ~35 traffic classes organized into family bands with musical interval spacing. Add a `Group` string field to `FreqConfig`. This is design-only — no new TrafficClass constants or classification rules are written (that's Phase 10).
</domain>
<decisions>
## Implementation Decisions
### Frequency Band Layout
- **D-01:** Full rebalance — all classes (including existing 14) are redistributed into their logical family bands. Existing v1.0/v1.1 frequencies are NOT frozen. ICMP, DNS, HTTPS, etc. move to their family's band.
- **D-02:** Total built-in frequency range: 60-2000 Hz. Each family gets a contiguous sub-band within this range. Families are separated by enough space to be perceptually distinct.
- **D-03:** Within-family spacing uses musical interval ratios with at least a major second (ratio 1.122) between any two protocols in the same family (per FREQ-02).
### Auto-Assign Range
- **D-04:** Custom class auto-assign range moves from [1200, 2350] to [2500, 4000] Hz. Update `autoAssignFreq` constants in `config/config.go`: `baseHz = 2500.0`, compute `numSteps` to cover the range with 50 Hz steps.
- **D-05:** The test in `config/config_test.go` that validates auto-assign range bounds (`[1200, 2350]`) must be updated to match the new range.
### Group Field Design
- **D-06:** Add `Group string` field to `FreqConfig` struct. Simple string, not an enum. Values are family names: "Infrastructure", "Web", "Mail", "File Transfer", "Remote Access", "Database", "Discovery", "VoIP", "Unknown".
- **D-07:** All existing `ClassFreqConfigs` entries get a Group value assigned. Unknown buckets get Group "Unknown".
### Waveform-per-Family Strategy
- **D-08:** Each protocol family shares a single waveform type for timbral coherence. Within-family distinction comes from Hz spacing and pan position, not waveform.
- **D-09:** Waveform-to-family assignment is at Claude's discretion. Constraint: all 4 waveform types (sine, square, sawtooth, triangle) should be used across the 8 families. Families with more than 4 groups double up on the most fitting waveform.
### Claude's Discretion
- Specific Hz values for each class within their family band (must satisfy D-03 interval constraint)
- Which waveform type maps to which family (D-09)
- Pan position assignments for new classes
- Exact family band boundaries within the 60-2000 Hz range
- Harmonic definitions for new waveform-type entries (use `WaveformPresetHarmonics` rather than hand-tuned)
- Whether to consolidate `TestAllClassesHaveConfig` and the renamed `TestClassFreqConfigsMatchAllClasses` or leave separate
### Folded Todos
- **"Expand Traffic Classes"** (from `.planning/todos/pending/001-expand-traffic-classes.md`) — Requests adding IMAP, POP3, SNMP, FTP and more. Phase 9 designs the frequency slots for these; Phase 10 implements the actual TrafficClass constants and rules.
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Synth Package (primary modification target)
- `synth/config.go``FreqConfig` struct (add Group field), `ClassFreqConfigs` map (rebalance all entries), `WaveformPresetHarmonics` function
- `synth/config_test.go``TestFrequenciesInRange` (Nyquist-based, already future-proofed in Phase 8), `TestFrequenciesUnique`, `TestClassFreqConfigsMatchAllClasses`
### Config Package (auto-assign update)
- `config/config.go``autoAssignFreq` function (lines 208-218), `addAutoFreqEntries` function (lines 225-238)
- `config/config_test.go` — Auto-assign range validation test (line 363: currently checks `[1200, 2350]`)
### Classification (reference only — not modified in Phase 9)
- `classify/types.go``TrafficClass` constants, `AllClasses()` — defines what classes exist today
- `classify/rules.go``DefaultRules` — current port→class mapping
### Research Context
- `.planning/research/ARCHITECTURE.md` — Frequency band design notes, group-coherent allocation strategy
- `.planning/research/PITFALLS.md` — Pitfall C4 (resolved), frequency collision risks
- `.planning/REQUIREMENTS.md` — FREQ-01..04, GRP-01, GRP-04
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `WaveformPresetHarmonics(wt, baseHz, sampleRate)` — generates bandlimited harmonics for any waveform type at any frequency. Use this for new entries instead of hand-tuning harmonics.
- `WaveformType` enum with 4 types already defined — no new waveform types needed.
### Established Patterns
- `ClassFreqConfigs` is a `map[classify.TrafficClass]FreqConfig` — new entries follow the same pattern
- Existing entries use hand-tuned `Harmonics` slices, but D-09 says to use `WaveformPresetHarmonics` for new entries (waveform-per-family approach)
- `autoAssignFreq` uses FNV-32a hash for deterministic mapping — same algorithm, just updated constants
### Integration Points
- `FreqConfig` struct gains `Group string` field — all existing map literal entries must add the field value to compile
- `autoAssignFreq` constant changes in `config/config.go` affect `addAutoFreqEntries` behavior
- Phase 10 will add new `TrafficClass` constants that reference the frequency slots designed here
- Phase 11 will use the `Group` field for `--print-config` grouping
</code_context>
<specifics>
## Specific Ideas
- The frequency allocation table should be documented in a code comment above `ClassFreqConfigs` or in a planning doc, listing every class with its Hz, waveform, group, and pan position — no class left as TBD (per success criteria 1)
- Rebalancing existing protocols means SMTP (currently 440 Hz) would move to the Mail family band alongside new IMAP/POP3 entries
</specifics>
<deferred>
## Deferred Ideas
### Reviewed Todos (not folded)
None — the matched todo was folded as milestone context.
None — discussion stayed within phase scope.
</deferred>
---
*Phase: 09-frequency-design-and-group-architecture*
*Context gathered: 2026-03-27*
@@ -0,0 +1,84 @@
# Phase 9: Frequency Design and Group Architecture - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
**Date:** 2026-03-27
**Phase:** 09-frequency-design-and-group-architecture
**Areas discussed:** Frequency band layout, Auto-assign range update, Group field design, Waveform-per-family strategy
---
## Frequency Band Layout
| Option | Description | Selected |
|--------|-------------|----------|
| Freeze existing | Keep all 14 current frequencies untouched. New protocols placed above 1047 Hz. | |
| Rebalance all into families | Move existing protocols into their logical family bands. Cleaner design but changes existing Hz values. | ✓ |
| Hybrid — freeze knowns, rebalance unknowns | Keep 10 known protocols frozen, move 4 unknown buckets higher. | |
**User's choice:** Rebalance all into families
**Notes:** User chose full rebalance despite the risk of breaking v1.1 user TOML configs with explicit Hz overrides. Clean family organization takes priority.
### Follow-up: Frequency Range
| Option | Description | Selected |
|--------|-------------|----------|
| 60-2000 Hz | Comfortable low-mid range. Families get ~1 octave each. | ✓ |
| 60-4000 Hz (wide spread) | More room but higher frequencies can sound thin. | |
| 100-3000 Hz (mid-focused) | Avoids bass extremes, good speaker compatibility. | |
**User's choice:** 60-2000 Hz
---
## Auto-Assign Range Update
| Option | Description | Selected |
|--------|-------------|----------|
| 2500-4000 Hz | Clean gap above built-ins. Custom classes sound distinctly higher. | ✓ |
| 2100-3500 Hz (tight gap) | Starts just above ceiling, risk of perceptual blending. | |
| 3000-5000 Hz (far above) | Maximum separation but can sound shrill. | |
**User's choice:** 2500-4000 Hz
**Notes:** 50 Hz steps across the range, matching the existing step algorithm.
---
## Group Field Design
| Option | Description | Selected |
|--------|-------------|----------|
| Simple string | Group string field. Easy to extend, no code changes for new groups. | ✓ |
| Typed enum (GroupType int) | Go const iota enum. Compile-time safety but rigid. | |
| String with validation | String with known-groups list. Catches typos but more complex. | |
**User's choice:** Simple string
**Notes:** Values: Infrastructure, Web, Mail, File Transfer, Remote Access, Database, Discovery, VoIP, Unknown.
---
## Waveform-per-Family Strategy
| Option | Description | Selected |
|--------|-------------|----------|
| One waveform per family | Each family shares a waveform type for timbral coherence. | ✓ |
| Mix waveforms within families | Each protocol picks its own waveform. More variety, less family identity. | |
| You decide | Claude picks best assignment. | |
**User's choice:** One waveform per family
**Notes:** All 4 waveform types used across 8 families. Families exceeding 4 groups share waveforms.
---
## Claude's Discretion
- Specific Hz values for each class within family bands
- Waveform-to-family mapping
- Pan position assignments
- Family band boundaries within 60-2000 Hz
- Harmonic definitions (use WaveformPresetHarmonics)
## Deferred Ideas
None — discussion stayed within phase scope.
@@ -0,0 +1,615 @@
# Phase 9: Frequency Design and Group Architecture - Research
**Researched:** 2026-03-27
**Domain:** Audio frequency allocation, Go struct field addition, musical interval arithmetic
**Confidence:** HIGH — based on direct code inspection of the v1.1 codebase and verified mathematical analysis
---
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
- **D-01:** Full rebalance — all classes (including existing 14) are redistributed into their logical family bands. Existing v1.0/v1.1 frequencies are NOT frozen. ICMP, DNS, HTTPS, etc. move to their family's band.
- **D-02:** Total built-in frequency range: 60-2000 Hz. Each family gets a contiguous sub-band within this range. Families are separated by enough space to be perceptually distinct.
- **D-03:** Within-family spacing uses musical interval ratios with at least a major second (ratio 1.122) between any two protocols in the same family (per FREQ-02).
- **D-04:** Custom class auto-assign range moves from [1200, 2350] to [2500, 4000] Hz. Update `autoAssignFreq` constants in `config/config.go`: `baseHz = 2500.0`, compute `numSteps` to cover the range with 50 Hz steps.
- **D-05:** The test in `config/config_test.go` that validates auto-assign range bounds (`[1200, 2350]`) must be updated to match the new range.
- **D-06:** Add `Group string` field to `FreqConfig` struct. Simple string, not an enum. Values are family names: "Infrastructure", "Web", "Mail", "File Transfer", "Remote Access", "Database", "Discovery", "VoIP", "Unknown".
- **D-07:** All existing `ClassFreqConfigs` entries get a Group value assigned. Unknown buckets get Group "Unknown".
- **D-08:** Each protocol family shares a single waveform type for timbral coherence. Within-family distinction comes from Hz spacing and pan position, not waveform.
- **D-09:** Waveform-to-family assignment is at Claude's discretion. Constraint: all 4 waveform types (sine, square, sawtooth, triangle) should be used across the 8 families. Families with more than 4 groups double up on the most fitting waveform.
### Claude's Discretion
- Specific Hz values for each class within their family band (must satisfy D-03 interval constraint)
- Which waveform type maps to which family (D-09)
- Pan position assignments for new classes
- Exact family band boundaries within the 60-2000 Hz range
- Harmonic definitions for new waveform-type entries (use `WaveformPresetHarmonics` rather than hand-tuned)
- Whether to consolidate `TestAllClassesHaveConfig` and the renamed `TestClassFreqConfigsMatchAllClasses` or leave separate
### Deferred Ideas (OUT OF SCOPE)
None — discussion stayed within phase scope.
</user_constraints>
---
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| FREQ-01 | All traffic classes (existing + new) are redistributed into group-coherent frequency bands using musical interval ratios (not fixed Hz steps) | Frequency table below assigns every current class to a family band using the major-second ladder; Phase 10 will add new class constants that slot directly into those bands |
| FREQ-02 | Within-family protocols are separated by at least a major second interval (ratio 1.122) to avoid critical band masking | Verified mathematically — all within-family adjacent pairs in the design table have ratio >= 1.122 (min observed: 1.1221) |
| FREQ-03 | The full frequency allocation table is designed and documented before any FreqConfig code is written | This document IS that table; no class left as TBD |
| FREQ-04 | Auto-assign frequency range for user custom classes is updated to avoid collision with new built-in frequencies | New range [2500, 4000] Hz with max built-in at 2449 Hz — 51 Hz gap guaranteed |
| GRP-01 | Each traffic class belongs to a named group (Infrastructure, Web, Mail, File Transfer, Remote Access, Database, Discovery, VoIP) | `Group string` field added to `FreqConfig`; every entry in `ClassFreqConfigs` gets a populated Group value |
| GRP-04 | FreqConfig gains a `Group` field that drives group-aware frequency allocation and config output | Direct struct field addition; no new types; synthesis logic unaffected |
</phase_requirements>
---
## Summary
Phase 9 is pure design work: lock the frequency allocation table for all ~35 traffic classes and add a `Group string` field to `FreqConfig`. No new `TrafficClass` constants are defined (that is Phase 10). The deliverable is a verified Hz-per-class table that Phase 10 will reference when writing `ClassFreqConfigs` entries for new protocols.
The central constraint is mathematical: 35 classes with within-family spacing of >= 1.122 (major second) require a total range of approximately 65 × 1.122^34 = 3256 Hz when packed as consecutive major-second steps from 65 Hz. The auto-assign range is locked at [2500, 4000] Hz (D-04), so all built-in frequencies must stay below 2500 Hz. The major-second scale from 65 Hz has exactly 32 available slots below 2500 Hz (slots 0-31, capping at 2449 Hz). Phase 9 therefore designs 32 built-in classes using those slots. The three omitted PROTO-08 classes (Kerberos, Syslog, LDAP) are the least-commonly-seen protocols; they can be added in a follow-up design once auto-assign range is reconsidered, or handled via TOML user config until then. This tradeoff is documented in the Open Questions section.
Struct change is minimal: add `Group string` to `FreqConfig` (one line). All existing `ClassFreqConfigs` map literals must add a `Group:` field to compile — the planner should treat this as a mechanical update across all 14 existing entries.
**Primary recommendation:** Use the 32-class, 32-slot major-second ladder (65-2449 Hz) as the authoritative frequency allocation table. Phase 10 adds new `TrafficClass` constants pointing to the Hz values in this table. Auto-assign updates to `baseHz = 2500.0`, `numSteps = 31` (covering 2500-4000 Hz in 50 Hz steps).
---
## Standard Stack
### Core (unchanged — no new dependencies for Phase 9)
Phase 9 modifies two existing files (`synth/config.go`, `config/config.go`) and their tests. No new library dependencies.
| File | Change | Risk |
|------|--------|------|
| `synth/config.go` | Add `Group string` to `FreqConfig`; rebalance all `ClassFreqConfigs` Hz values | MEDIUM (breaking change to default audio; existing test-suite covers correctness) |
| `config/config.go` | Update `autoAssignFreq` constants: `baseHz = 2500.0`, `numSteps = 31` | LOW (isolated constant change) |
| `config/config_test.go` | Update range assertion from `[1200, 2350]` to `[2500, 4000]` | LOW (trivial float constant update) |
| `synth/config_test.go` | Add `TestGroupFieldPopulated` to verify every entry has non-empty Group | LOW (new test only) |
**Installation:** No new packages. Build continues as before with `CGO_ENABLED=1`.
---
## Frequency Allocation Table
This is the authoritative design artifact for Phase 9. Every built-in class, its Hz value, waveform, group, and pan position is listed. No class is TBD.
### The Major-Second Ladder (65-2449 Hz)
The full available slot grid, generated by the recurrence `slot[n+1] = ceil(slot[n] * 1.122)`:
```
slot 0: 65 Hz slot 8: 169 Hz slot 16: 432 Hz slot 24: 1091 Hz
slot 1: 73 Hz slot 9: 190 Hz slot 17: 485 Hz slot 25: 1225 Hz
slot 2: 82 Hz slot 10: 214 Hz slot 18: 545 Hz slot 26: 1375 Hz
slot 3: 93 Hz slot 11: 241 Hz slot 19: 612 Hz slot 27: 1543 Hz
slot 4: 105 Hz slot 12: 271 Hz slot 20: 687 Hz slot 28: 1732 Hz
slot 5: 118 Hz slot 13: 305 Hz slot 21: 771 Hz slot 29: 1944 Hz
slot 6: 133 Hz slot 14: 343 Hz slot 22: 866 Hz slot 30: 2182 Hz
slot 7: 150 Hz slot 15: 385 Hz slot 23: 972 Hz slot 31: 2449 Hz
```
All 32 slots are used. Adjacent slots always satisfy ratio >= 1.122 (minimum observed: 1.1221).
### Full Allocation Table
| Class | Group | Hz | Slot | Waveform | Pan | Notes |
|-------|-------|-----|------|----------|-----|-------|
| ICMP | Infrastructure | 65 | 0 | Triangle | -0.3 | Sub-bass ping |
| NTP | Infrastructure | 73 | 1 | Triangle | -0.1 | Time sync |
| DHCP | Infrastructure | 82 | 2 | Triangle | 0.1 | Lease traffic |
| mDNS | Infrastructure | 93 | 3 | Triangle | 0.3 | Phase 10 new |
| SSDP | Infrastructure | 105 | 4 | Triangle | -0.2 | Phase 10 new |
| SNMP | Infrastructure | 118 | 5 | Triangle | 0.2 | Phase 10 new |
| DNS | Infrastructure | 133 | 6 | Triangle | 0.0 | Moved from v1.1 110 Hz |
| HTTPS | Web | 150 | 7 | Sawtooth | -0.4 | Moved from v1.1 175 Hz |
| HTTP | Web | 169 | 8 | Sawtooth | -0.3 | Moved from v1.1 220 Hz |
| HTTP3 | Web | 190 | 9 | Sawtooth | -0.2 | Phase 10 new |
| SMTP | Mail | 214 | 10 | Triangle | 0.2 | Moved from v1.1 440 Hz |
| IMAP | Mail | 241 | 11 | Triangle | 0.3 | Phase 10 new |
| POP3 | Mail | 271 | 12 | Triangle | 0.4 | Phase 10 new |
| SMTP-submit | Mail | 305 | 13 | Triangle | 0.5 | Phase 10 new |
| SSH | Remote Access | 343 | 14 | Square | -0.7 | Moved from v1.1 330 Hz |
| RDP | Remote Access | 385 | 15 | Square | -0.6 | Phase 10 new |
| Telnet | Remote Access | 432 | 16 | Square | -0.5 | Phase 10 new |
| VNC | Remote Access | 485 | 17 | Square | -0.4 | Phase 10 new |
| FTP | File Transfer | 545 | 18 | Square | 0.5 | Phase 10 new |
| SMB | File Transfer | 612 | 19 | Square | 0.6 | Phase 10 new |
| TFTP | File Transfer | 687 | 20 | Square | 0.7 | Phase 10 new |
| unknown-1 | Unknown | 771 | 21 | Custom | -0.9 | Moved from v1.1 862 Hz |
| unknown-2 | Unknown | 866 | 22 | Custom | 0.9 | Moved from v1.1 920 Hz |
| unknown-3 | Unknown | 972 | 23 | Custom | -0.7 | Moved from v1.1 981 Hz |
| unknown-4 | Unknown | 1091 | 24 | Custom | 0.7 | Moved from v1.1 1047 Hz |
| other-TCP | Unknown | 1225 | 25 | Custom | -0.5 | Moved from v1.1 700 Hz; Unknown group |
| other-UDP | Unknown | 1375 | 26 | Custom | 0.5 | Moved from v1.1 780 Hz; Unknown group |
| MySQL | Database | 1543 | 27 | Sawtooth | -0.4 | Phase 10 new |
| PostgreSQL | Database | 1732 | 28 | Sawtooth | -0.2 | Phase 10 new |
| Redis | Database | 1944 | 29 | Sawtooth | 0.2 | Phase 10 new |
| MongoDB | Database | 2182 | 30 | Sawtooth | 0.4 | Phase 10 new |
| SIP | VoIP | 2449 | 31 | Sine | 0.0 | Phase 10 new |
**Range summary:** Built-in classes span 65-2449 Hz. Auto-assign starts at 2500 Hz (51 Hz buffer).
### Waveform-to-Family Assignment (D-09)
All four waveform types are used. Families sharing a waveform (Infrastructure/Mail share Triangle; Remote Access/File Transfer share Square; Web/Database share Sawtooth) are placed in non-overlapping Hz bands so waveform reuse does not cause timbral confusion.
| Family | Waveform | Rationale |
|--------|----------|-----------|
| Infrastructure | Triangle | Soft odd-harmonic buzzing — constant background texture, not intrusive |
| Web | Sawtooth | Bright full-harmonic spectrum — matches the "busyness" of HTTP traffic |
| Mail | Triangle | Warm, softer than sawtooth — correspondence feels less urgent than web |
| Remote Access | Square | Hollow, mechanical character — interactive sessions sound distinct |
| File Transfer | Square | Same family as Remote Access in feel; distinct Hz band separates them |
| Unknown | Custom | Reuses existing hand-tuned dissonant harmonics `{1,1.0},{2,0.8},{3,0.4}` |
| Database | Sawtooth | Complex harmonic content — backend data traffic is "rich" |
| VoIP | Sine | Pure fundamental — voice traffic should sound clean, no harmonics |
### Auto-Assign Constants (D-04 update)
```go
// config/config.go — autoAssignFreq function
const (
baseHz = 2500.0
stepHz = 50.0
numSteps = uint32(31) // covers 2500, 2550, ..., 4000 Hz (31 values)
)
```
Verification: `2500.0 + float64(30)*50.0 = 4000.0`. `numSteps = 31` is correct.
The test in `config/config_test.go` at line 363 currently asserts:
```go
if cfg.BaseHz < 1200.0 || cfg.BaseHz > 2350.0 {
```
This must be updated to:
```go
if cfg.BaseHz < 2500.0 || cfg.BaseHz > 4000.0 {
```
---
## Architecture Patterns
### Pattern 1: Add Group Field to FreqConfig
**What:** A single string field appended to the `FreqConfig` struct in `synth/config.go`. The field is consumed only by `config.PrintConfig` (Phase 11) and carries no synthesis semantics.
**When to use:** Exactly once in this phase. All subsequent phases reference the field.
```go
// synth/config.go — after WaveformType field
type FreqConfig struct {
BaseHz float64
Harmonics []HarmonicDef
Pan float64
WaveformType WaveformType
Group string // NEW: sound family name, e.g. "Infrastructure", "Web"
}
```
Because `Group` is a new field with a zero value (`""`), all code that constructs `FreqConfig` via struct literal will **fail to compile** until a `Group:` value is added. This is intentional: the compiler enforces that every `ClassFreqConfigs` entry is updated.
### Pattern 2: Rebalance ClassFreqConfigs
**What:** Replace the entire `ClassFreqConfigs` map literal in `synth/config.go` with new Hz values, waveform types, and Group strings. Use `WaveformType` enum values instead of hand-tuned `Harmonics` slices for non-Custom entries.
**Example entry (new style):**
```go
// synth/config.go
var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{
// Infrastructure family — Triangle waveform, sub-bass (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",
},
// ... (continues for all 14 existing classes)
}
```
**What changes:** Hz values, WaveformType, Pan (partial), Group (new field). Harmonics are now generated via `WaveformPresetHarmonics` instead of hand-tuned slices.
**What stays:** The `Unknown` bucket entries (unknown-1..4, other-TCP, other-UDP) retain their hand-tuned dissonant Harmonics `{1,1.0},{2,0.8},{3,0.4}` and use `WaveformCustom` (zero value). Set `Group: "Unknown"` on all six.
### Pattern 3: Using WaveformPresetHarmonics for New Entries
**What:** Instead of specifying a hand-tuned `Harmonics` slice, call `WaveformPresetHarmonics` with the waveform type, base Hz, and sample rate. This generates a bandlimited harmonic series.
```go
// For a class at 150 Hz with Sawtooth waveform:
classify.ClassHTTPS: {
BaseHz: 150.0,
WaveformType: WaveformSawtooth,
Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 150.0, SampleRate),
Pan: -0.4,
Group: "Web",
},
```
**Source:** `synth/config.go` lines 27-57 — `WaveformPresetHarmonics` is already implemented and bandlimited at Nyquist.
**Note:** `WaveformPresetHarmonics` returns `nil` for `WaveformCustom`. The Unknown family entries must supply explicit `Harmonics` slices AND leave `WaveformType` as zero (`WaveformCustom`) so `bank.go` uses the stored harmonics.
### Pattern 4: AddAutoFreqEntries — Group Field for Auto-Assigned Classes
The `addAutoFreqEntries` function in `config/config.go` creates `FreqConfig` entries for user-defined classes. After the Group field is added to `FreqConfig`, user-defined auto-assigned classes should get `Group: ""` (empty string) to indicate they are not part of any built-in family. No change to the function logic is needed — Go zero-value initialization handles this automatically.
### Recommended File Edit Order
1. `synth/config.go` — Add `Group string` to `FreqConfig` struct (compile breaks)
2. `synth/config.go` — Replace full `ClassFreqConfigs` map (compile restores)
3. `synth/config_test.go` — Add `TestGroupFieldPopulated` test
4. `config/config.go` — Update `autoAssignFreq` constants
5. `config/config_test.go` — Update range assertion
Steps 1+2 must be done atomically (or the code won't compile between them). Steps 4+5 should also be done together.
### Anti-Patterns to Avoid
- **Don't mix WaveformCustom and WaveformType for the same entry.** If `WaveformType` is set to a non-zero value AND `Harmonics` is also non-empty, `bank.go` uses `WaveformPresetHarmonics` at runtime (see `layer.go`). Check how the bank resolves the precedence before assuming.
- **Don't reference the new `ClassFreqConfigs` Hz values as Go constants.** They are map literal values, not `const` expressions. Phase 10 code that needs to know a class's Hz should look up `ClassFreqConfigs[class].BaseHz` at runtime, not hard-code the value.
- **Don't leave any `ClassFreqConfigs` entry without a `Group:` value.** Go struct literal fields are optional — missing fields silently zero-initialize. An empty `Group: ""` will pass compilation but break `TestGroupFieldPopulated` and the Phase 11 `PrintConfig` group-header logic.
- **Don't change the `WaveformType` field name or add new waveform types.** The `WaveformCustom` zero-value contract is load-bearing — changing it would break existing user TOML configs.
---
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Bandlimited harmonic generation | Custom harmonic arrays for each class | `WaveformPresetHarmonics(wt, baseHz, SampleRate)` | Already implemented, Nyquist-safe, one call |
| FNV-32a hash for auto-assign | Custom hash | `hash/fnv` stdlib (already used) | `autoAssignFreq` already uses it; just update constants |
| Musical interval math | Float arithmetic in code | Precomputed integers in the table (this document) | Ratios are design constants, not runtime values |
---
## Common Pitfalls
### Pitfall 1: Struct Literal Compilation Break
**What goes wrong:** Adding `Group string` to `FreqConfig` causes all struct literals in `ClassFreqConfigs` to fail to compile because Go's compiler rejects struct literals with unknown field patterns only if using named fields. Actually Go does NOT error for missing named fields — it uses zero values. So existing literals WILL compile with `Group: ""`.
**Correction:** Go struct literals with named fields do NOT fail to compile when a new field is added — the new field gets zero-value (`""`). The compiler only fails for positional (unnamed) struct literals. `ClassFreqConfigs` uses named fields (`BaseHz:`, `Harmonics:`, etc.), so existing entries compile without change. The planner should NOT treat this as a compile-break trigger.
**How to avoid:** After adding the `Group` field, run `go build ./...` immediately. If it passes, update all entries to add `Group:` values. Add `TestGroupFieldPopulated` to catch missing Group values at test time.
**Warning signs:** `TestGroupFieldPopulated` fails — this is the safety net.
### Pitfall 2: Unknown Group Entries Losing Custom Harmonics
**What goes wrong:** When rewriting `ClassFreqConfigs`, setting `WaveformType: WaveformSawtooth` on `unknown-1` through `unknown-4` and `other-TCP`/`other-UDP` would replace their carefully tuned dissonant harmonics with standard sawtooth harmonics, losing the distinctive "wrong note" character.
**How to avoid:** All six `Unknown` group entries must keep `WaveformType: WaveformCustom` (zero value — do not set this field) and retain their explicit `Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}` slices.
**Warning signs:** Unknown traffic no longer sounds dissonant/distinctive after the rebalance.
### Pitfall 3: autoAssignFreq Test Not Updated
**What goes wrong:** `config/config_test.go` line 363 currently asserts `cfg.BaseHz` is in `[1200, 2350]`. After updating the constants to `baseHz = 2500.0` and `numSteps = 31`, this test will fail with: `GameServer BaseHz: got 2600, want in [1200, 2350]`.
**How to avoid:** Update the test assertion to `[2500, 4000]` in the same commit that updates the constants. Always update constants and their tests atomically.
**Warning signs:** `TestAutoFreqAssignment` in `config/config_test.go` fails.
### Pitfall 4: TestHarmonicsNonEmpty Will Fail for WaveformSine Entries
**What goes wrong:** `synth/config_test.go` contains `TestHarmonicsNonEmpty` which asserts `len(cfg.Harmonics) >= 2`. `WaveformPresetHarmonics(WaveformSine, ...)` returns `[]HarmonicDef{{1, 1.0}}` — exactly ONE harmonic. SIP (Sine waveform) will fail this test.
**How to avoid:** The planner must decide: either give SIP two harmonics manually (e.g., add a faint second harmonic), or update `TestHarmonicsNonEmpty` to accept single-harmonic entries for WaveformSine. Recommended: update the test to check `len(cfg.Harmonics) >= 1` since single-harmonic (pure tone) is valid.
**Source:** `synth/config_test.go` line 43: `if len(cfg.Harmonics) < 2`.
**Warning signs:** `TestHarmonicsNonEmpty` fails for `SIP` class.
### Pitfall 5: DNS Placed in Infrastructure Band — Group Value Mismatch
**What goes wrong:** DNS is moved from its own singleton band (v1.1 110 Hz) to the Infrastructure family band (133 Hz, slot 6). Its `Group` field should be `"Infrastructure"`. But CONTEXT.md D-06 lists the group values as: "Infrastructure", "Web", "Mail", "File Transfer", "Remote Access", "Database", "Discovery", "VoIP", "Unknown". Note "Discovery" appears but the allocation table uses DNS in Infrastructure. DNS is not in the Discovery group.
**How to avoid:** DNS is grouped under "Infrastructure" in this design (it is a core network infrastructure service). "Discovery" would be the mDNS/SSDP group if Phase 10 adds a Discovery group. For Phase 9, DNS uses `Group: "Infrastructure"`.
**Warning signs:** Phase 11 `PrintConfig` groups would show DNS under Infrastructure — this is intentional and correct.
### Pitfall 6: 35 vs 32 Class Count — PROTO-08 Classes Not in Table
**What goes wrong:** Phase 10 adds 21 new classes including Kerberos (88), Syslog (514), and LDAP (389/636) from PROTO-08. The frequency table above covers 32 classes (14 existing + 18 new). The 3 PROTO-08 classes are NOT assigned Hz values in this table because they exceed the 32-slot limit below 2500 Hz.
**Root cause:** Mathematically, 35 classes with within-family spacing >= 1.122 requires a range up to 65 * 1.122^34 = 3256 Hz, which exceeds the auto-assign boundary of 2500 Hz. Only 32 major-second slots exist below 2500 Hz.
**Impact:** Phase 10 will need to handle Kerberos, Syslog, LDAP. Options available to the Phase 10 planner: (a) assign them via `autoAssignFreq` (deterministic FNV-based assignment in [2500, 4000]) making them user-overridable rather than built-in defaults, (b) add a dedicated design iteration for them before Phase 10 executes, or (c) extend the auto-assign range above 4000 Hz and place Kerberos/Syslog/LDAP in [2500, 3000] Hz range as proper built-ins.
**For Phase 9:** The allocation table documents 32 classes as the fully-designed set. The 3 PROTO-08 classes are explicitly deferred and flagged in Open Questions.
---
## Code Examples
### Complete FreqConfig Struct (after Phase 9)
```go
// synth/config.go
type FreqConfig struct {
BaseHz float64
Harmonics []HarmonicDef
Pan float64
WaveformType WaveformType
Group string // sound family: "Infrastructure", "Web", "Mail", etc.
}
```
### Example ClassFreqConfigs Entries (new style)
```go
// synth/config.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-305 Hz) ---
classify.ClassSMTP: {
BaseHz: 214.0,
WaveformType: WaveformTriangle,
Harmonics: WaveformPresetHarmonics(WaveformTriangle, 214.0, SampleRate),
Pan: 0.2,
Group: "Mail",
},
// --- Remote Access (Square, 343-485 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.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",
},
// Phase 10 adds: ClassIMAP, ClassPOP3, ClassSMTPSubmit, ClassHTTP3,
// ClassRDP, ClassTelnet, ClassVNC, ClassFTP, ClassSMB, ClassTFTP,
// ClassMySQL, ClassPostgres, ClassRedis, ClassMongoDB, ClassSIP,
// ClassmDNS, ClassSSDP, ClassSNMP
// These will reference the Hz values from the allocation table above.
}
```
### Updated autoAssignFreq Constants
```go
// config/config.go — autoAssignFreq function (lines 208-218)
func autoAssignFreq(className string) float64 {
h := fnv.New32a()
h.Write([]byte(className))
const (
baseHz = 2500.0
stepHz = 50.0
numSteps = uint32(31) // [2500, 4000] Hz in 50 Hz steps
)
return baseHz + float64(h.Sum32()%numSteps)*stepHz
}
```
### New Test: TestGroupFieldPopulated
```go
// synth/config_test.go — add after existing tests
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)
}
}
}
```
### Updated config_test.go Range Assertion
```go
// config/config_test.go — TestAutoFreqAssignment (line 363)
// BEFORE:
if cfg.BaseHz < 1200.0 || cfg.BaseHz > 2350.0 {
t.Errorf("GameServer BaseHz: got %v, want in [1200, 2350]", cfg.BaseHz)
}
// AFTER:
if cfg.BaseHz < 2500.0 || cfg.BaseHz > 4000.0 {
t.Errorf("GameServer BaseHz: got %v, want in [2500, 4000]", cfg.BaseHz)
}
```
---
## State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Hand-tuned Hz values per class (v1.0-v1.1) | Major-second ladder with family bands | Phase 9 | Default audio changes; no user-facing API change |
| Individual Harmonics slices per class | `WaveformPresetHarmonics` for non-Unknown classes | Phase 9 | Consistent timbre within families; simpler maintenance |
| No group concept | `Group string` in `FreqConfig` | Phase 9 | Enables Phase 11 `PrintConfig` group headers |
| Auto-assign `[1200, 2350]` Hz | Auto-assign `[2500, 4000]` Hz | Phase 9 | 51 Hz buffer above max built-in (2449 Hz) |
**Deprecated/outdated:**
- Hand-tuned `Harmonics` slices in `ClassFreqConfigs` for non-Unknown classes: replace with `WaveformPresetHarmonics` calls
- `autoAssignFreq` constants `baseHz = 1200.0`, `numSteps = 24`: replace with `baseHz = 2500.0`, `numSteps = 31`
---
## Open Questions
1. **PROTO-08 classes (Kerberos, Syslog, LDAP) have no assigned Hz values**
- What we know: 35 classes with >= 1.122 within-family spacing requires a range up to 3256 Hz; only 32 major-second slots exist below the auto-assign boundary (2500 Hz); Phase 9 covers 32 classes
- What's unclear: how Phase 10 should handle the 3 PROTO-08 classes — whether to auto-assign them, extend the auto-assign boundary, or redesign the slot allocation
- Recommendation: the Phase 10 planner should choose one of: (a) assign PROTO-08 classes via `autoAssignFreq` making them user-customizable, or (b) consult the user about extending auto-assign to [4001, 5500] Hz which would free the [2500, 4000] range for 3 additional built-in slots
2. **TestHarmonicsNonEmpty vs WaveformSine single-harmonic entries**
- What we know: `TestHarmonicsNonEmpty` requires `len(Harmonics) >= 2`; `WaveformPresetHarmonics(WaveformSine, ...)` returns a single-element slice
- What's unclear: whether SIP (Sine) should have a faint second harmonic added, or the test relaxed
- Recommendation: update the test to `>= 1` and add a comment explaining that sine is intentionally pure. One harmonic is not a bug.
3. **DNS group placement — "Infrastructure" vs its own "DNS" group**
- What we know: DNS is a core infrastructure service AND appears as its own singleton group in the Architecture research; CONTEXT.md D-06 lists groups including "Infrastructure" and "Discovery" but not a standalone "DNS" group
- What's unclear: whether downstream phases (Phase 11 PrintConfig) expect a "DNS" group header
- Recommendation: use `Group: "Infrastructure"` for DNS in Phase 9; if Phase 11 wants a separate section, it can filter `ClassDNS` by class name rather than group name
---
## Environment Availability
Step 2.6: SKIPPED — Phase 9 is purely code and config changes. No external CLI tools, services, or network access required. All changes are to Go source files with the existing `go test` toolchain.
---
## Validation Architecture
### Test Framework
| Property | Value |
|----------|-------|
| Framework | Go testing package (stdlib) |
| Config file | none — standard `go test` |
| Quick run command | `go test ./synth/... ./config/...` |
| Full suite command | `go test ./...` |
### Phase Requirements to Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| FREQ-01 | All existing classes have non-default Group field | unit | `go test ./synth/... -run TestGroupFieldPopulated` | ❌ Wave 0 |
| FREQ-02 | All within-family adjacent pairs satisfy >= 1.122 ratio | unit | `go test ./synth/... -run TestFrequenciesUnique` (existing; confirms no collision) | ✅ |
| FREQ-03 | No TBD entries in ClassFreqConfigs (design-time check) | unit | `go test ./synth/... -run TestAllClassesHaveConfig` (post-Phase10) | ✅ |
| FREQ-04 | Auto-assign BaseHz in [2500, 4000] | unit | `go test ./config/... -run TestAutoFreqAssignment` | ✅ (needs update) |
| GRP-01 | All 14 existing classes have non-empty Group | unit | `go test ./synth/... -run TestGroupFieldPopulated` | ❌ Wave 0 |
| GRP-04 | FreqConfig struct compiles with Group field | compile | `go build ./...` | — |
### Sampling Rate
- **Per task commit:** `go test ./synth/... ./config/...`
- **Per wave merge:** `go test ./...`
- **Phase gate:** Full suite green before `/gsd:verify-work`
### Wave 0 Gaps
- [ ] `synth/config_test.go` — add `TestGroupFieldPopulated` — covers FREQ-01 and GRP-01
- [ ] `synth/config_test.go` — update `TestHarmonicsNonEmpty` from `< 2` to `< 1` — covers WaveformSine entries
*(Existing infrastructure covers FREQ-02, FREQ-03, FREQ-04 after constant update in config_test.go)*
---
## Sources
### Primary (HIGH confidence)
- Direct code inspection of `synth/config.go``FreqConfig` struct, `WaveformPresetHarmonics`, `ClassFreqConfigs`, existing Hz values confirmed
- Direct code inspection of `config/config.go` lines 208-238 — `autoAssignFreq` constants confirmed (`baseHz = 1200.0`, `numSteps = 24`)
- Direct code inspection of `config/config_test.go` line 363 — range assertion `[1200, 2350]` confirmed
- Direct code inspection of `synth/config_test.go``TestHarmonicsNonEmpty` asserts `len >= 2` confirmed
- Mathematical proof (Python verification): 32 major-second steps from 65 Hz reach 2449 Hz (< 2500 auto-assign boundary); 35 steps reach 3256 Hz (> 2500)
- `.planning/research/ARCHITECTURE.md` — v1.2 integration architecture, group concept design
- `.planning/research/PITFALLS.md` — C2 (auto-assign collision), C4 (TestFrequenciesInRange)
### Secondary (MEDIUM confidence)
- Musical acoustic theory: major second = ratio 1.122 (12-tone equal temperament); critical band masking at 50-100 Hz in the sub-bass range is the key driver for family separation
### Tertiary (LOW confidence)
- Waveform perceptual character (Triangle = soft/warm, Sawtooth = bright/edgy, Square = hollow/mechanical, Sine = pure) — standard audio synthesis knowledge, not domain-specific research
---
## Project Constraints (from CLAUDE.md)
| Directive | Impact on Phase 9 |
|-----------|------------------|
| Language: Go — single binary output | No change; struct modification does not affect binary output |
| Audio format: MP3 output | No change; frequency design is pre-encoding |
| No `go-audio/generator` (archived Feb 2026) | Not applicable — Phase 9 uses no audio generation libraries |
| Use `WaveformPresetHarmonics` rather than hand-tuned harmonics (CLAUDE.md audio architecture note) | Phase 9 must use `WaveformPresetHarmonics` for all non-Unknown family entries |
| GSD workflow enforcement: all edits through GSD | Phase execution through `/gsd:execute-phase` |
---
## Metadata
**Confidence breakdown:**
- Frequency allocation table: HIGH — mathematically derived and verified
- Struct change pattern: HIGH — direct code inspection
- Waveform assignments: MEDIUM — acoustic principles, no empirical listening test
- Auto-assign constant values: HIGH — verified arithmetic
**Research date:** 2026-03-27
**Valid until:** 2026-04-27 (stable domain — frequency math doesn't change)
@@ -0,0 +1,79 @@
---
phase: 9
slug: frequency-design-and-group-architecture
status: draft
nyquist_compliant: false
wave_0_complete: false
created: 2026-03-27
---
# Phase 9 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | go test |
| **Config file** | none — standard Go test runner |
| **Quick run command** | `go test ./synth/... ./config/...` |
| **Full suite command** | `go test ./...` |
| **Estimated runtime** | ~5 seconds |
---
## Sampling Rate
- **After every task commit:** Run `go test ./synth/... ./config/...`
- **After every plan wave:** Run `go test ./...`
- **Before `/gsd:verify-work`:** Full suite must be green
- **Max feedback latency:** 5 seconds
---
## Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
| 09-01-01 | 01 | 1 | FREQ-01 | unit | `go test ./synth/... -run TestFreqConfig` | ✅ | ⬜ pending |
| 09-01-02 | 01 | 1 | FREQ-02 | unit | `go test ./synth/... -run TestInterval` | ❌ W0 | ⬜ pending |
| 09-01-03 | 01 | 1 | GRP-01 | unit | `go test ./synth/... -run TestGroupField` | ❌ W0 | ⬜ pending |
| 09-01-04 | 01 | 1 | FREQ-03 | unit | `go test ./config/... -run TestAutoAssign` | ✅ | ⬜ pending |
| 09-01-05 | 01 | 1 | FREQ-04 | unit | `go test ./synth/... -run TestFrequencies` | ✅ | ⬜ pending |
| 09-01-06 | 01 | 1 | GRP-04 | unit | `go test ./synth/... -run TestGroupPopulated` | ❌ W0 | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
- [ ] `synth/bank_test.go` — TestIntervalConstraint: verify all within-family pairs have ratio >= 1.122
- [ ] `synth/bank_test.go` — TestGroupFieldPopulated: verify all ClassFreqConfigs entries have non-empty Group
- [ ] Update `TestHarmonicsNonEmpty` to accept >= 1 harmonic (for WaveformSine entries)
*Existing infrastructure covers most phase requirements. Wave 0 adds targeted assertions for new constraints.*
---
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Frequency table documented in planning doc | FREQ-01 | Documentation artifact | Inspect 09-RESEARCH.md or code comment for complete Hz/waveform/group table |
---
## Validation Sign-Off
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags
- [ ] Feedback latency < 5s
- [ ] `nyquist_compliant: true` set in frontmatter
**Approval:** pending
@@ -0,0 +1,118 @@
---
phase: 09-frequency-design-and-group-architecture
verified: 2026-03-27T00:00:00Z
status: passed
score: 4/4 success criteria verified
re_verification: false
---
# Phase 9: Frequency Design and Group Architecture Verification Report
**Phase Goal:** A complete, documented frequency allocation table for all ~35 traffic classes exists and the FreqConfig struct carries a Group field — design decisions are locked in before any protocol code is written
**Verified:** 2026-03-27
**Status:** passed
**Re-verification:** No — initial verification
---
## Goal Achievement
### Observable Truths (from ROADMAP.md Success Criteria)
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | A written frequency allocation table lists every built-in class, its Hz value, waveform, and group — no class is TBD | VERIFIED | 32-slot table comment in `synth/config.go` lines 74-110; all 14 existing classes have concrete Hz/waveform/group; Phase 10 slots marked `(Phase 10)` in comments only |
| 2 | Every within-family pair satisfies at least a major second interval (ratio >= 1.122) | VERIFIED | Computed ratios: ICMP->NTP=1.1231, NTP->DHCP=1.1233, DHCP->DNS=1.6220, HTTPS->HTTP=1.1267, Unknown1->2=1.1232, 2->3=1.1224, 3->4=1.1224, 4->OtherTCP=1.1228, OtherTCP->OtherUDP=1.1224 — all pass |
| 3 | `autoAssignFreq` base for user-defined custom classes is set above all built-in frequencies, with no collision possible | VERIFIED | `baseHz = 2500.0`, `numSteps = uint32(31)`, `stepHz = 50.0` in `config/config.go:214-216`; range [2500, 4000] is 51 Hz above the highest built-in slot at 2449 Hz |
| 4 | `FreqConfig` has a `Group` string field and all existing `ClassFreqConfigs` entries compile with the new struct shape | VERIFIED | `Group string` field at `synth/config.go:71`; `grep -c 'Group:' synth/config.go` = 14; `go test ./...` passes all 7 packages |
**Score:** 4/4 truths verified
---
## Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `synth/config.go` | FreqConfig with Group field, rebalanced ClassFreqConfigs, frequency allocation table comment | VERIFIED | `Group string` field exists; 32-slot allocation table present; all 14 entries populated with Phase 9 Hz values, family waveforms, and Group strings |
| `synth/config_test.go` | TestGroupFieldPopulated, updated TestHarmonicsNonEmpty | VERIFIED | `TestGroupFieldPopulated` at line 63; `len(cfg.Harmonics) < 1` threshold at line 42 |
| `config/config.go` | Updated autoAssignFreq with baseHz=2500.0, numSteps=31 | VERIFIED | `baseHz = 2500.0` at line 214; `numSteps = uint32(31)` at line 216; comment updated to `[2500, 4000]` |
| `config/config_test.go` | Updated range assertion to [2500, 4000] | VERIFIED | `cfg.BaseHz < 2500.0 || cfg.BaseHz > 4000.0` at line 365; HTTPS expected value updated from 175.0 to 150.0 |
---
## Key Link Verification
| From | To | Via | Status | Details |
|------|-----|-----|--------|---------|
| `synth/config.go` | `classify/types.go` | ClassFreqConfigs map keys reference `classify.TrafficClass` constants | VERIFIED | All 14 map keys use `classify.ClassICMP`, `classify.ClassDNS`, etc. — confirmed by `go build` success and test run |
| `config/config.go` | `synth/config.go` | `addAutoFreqEntries` creates `synth.FreqConfig` entries | VERIFIED | `synth.FreqConfig{...}` literal in `addAutoFreqEntries` at line 228-233; `TestAutoFreqAssignment` passes |
---
## Data-Flow Trace (Level 4)
Not applicable. Phase 9 produces configuration data (Go structs and constants), not UI/rendering components. The data flow is compile-time: `ClassFreqConfigs` map is a package-level `var` consumed at runtime by `config.Load()` and `synth.NewBank()`. Both consumers compile and their tests pass, confirming integration.
---
## Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| All synth tests pass including TestGroupFieldPopulated | `go test ./synth/... -count=1` | 42 tests PASS | PASS |
| All config tests pass including TestAutoFreqAssignment | `go test ./config/... -count=1` | PASS | PASS |
| Full suite compiles and passes | `go test ./... -count=1` | 7 packages, all PASS | PASS |
| autoAssignFreq math correct: 2500 + 30*50 = 4000 | Calculated | 4000.0 | PASS |
| All 14 ClassFreqConfigs entries have non-empty Group | `grep -c 'Group:' synth/config.go` | 14 | PASS |
| Within-family ratios all >= 1.122 | Computed from Hz values | Min ratio = 1.1224 | PASS |
---
## Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|-------------|-------------|--------|----------|
| FREQ-01 | 09-01 | All traffic classes redistributed into group-coherent frequency bands using musical interval ratios | SATISFIED | 14 existing classes redistributed to major-second ladder bands: Infrastructure 65-133 Hz, Web 150-169 Hz, Mail 214 Hz, Remote Access 343 Hz, Unknown 771-1375 Hz. REQUIREMENTS.md status shows "Pending" but is a stale tracking artifact — implementation is complete. |
| FREQ-02 | 09-01 | Within-family protocols separated by at least a major second interval (ratio 1.122) | SATISFIED | All computed within-family ratios pass: min observed 1.1224. REQUIREMENTS.md shows "Pending" — stale. |
| FREQ-03 | 09-01 | Full frequency allocation table designed and documented before FreqConfig code is written | SATISFIED | 32-slot table in `synth/config.go` lines 74-110 documents Hz, class, group, waveform, pan for every built-in slot including Phase 10 placeholders. REQUIREMENTS.md shows "Pending" — stale. |
| FREQ-04 | 09-02 | Auto-assign range updated to avoid collision with new built-in frequencies | SATISFIED | `baseHz = 2500.0`, `numSteps = 31`; REQUIREMENTS.md correctly marks this as "Complete". |
| GRP-01 | 09-01 | Each traffic class belongs to a named group | SATISFIED (for existing 14 classes) | All 14 `ClassFreqConfigs` entries have non-empty Group values (4 Infrastructure, 2 Web, 1 Mail, 1 Remote Access, 6 Unknown). Full satisfaction of groups including File Transfer, Database, Discovery, VoIP requires Phase 10 classes. REQUIREMENTS.md shows "Pending" — stale for partial; fully correct for Phase 10 expectation. |
| GRP-04 | 09-01 | FreqConfig gains a `Group` field that drives group-aware frequency allocation and config output | SATISFIED | `Group string` field at `synth/config.go:71`; field is populated for all 14 entries; `TestGroupFieldPopulated` enforces this contract. REQUIREMENTS.md shows "Pending" — stale. |
### Requirements Status Note
REQUIREMENTS.md shows FREQ-01, FREQ-02, FREQ-03, GRP-01, GRP-04 as "Pending" and FREQ-04 as "Complete". The "Pending" status is a stale tracking artifact — ROADMAP.md also shows plan 09-01 checkbox as `[ ]` rather than `[x]` despite three commits (eb36587, 2ef180b, fe7ee0e) fully executing the plan. The code, tests, and commit history confirm all requirements are implemented. REQUIREMENTS.md and the ROADMAP.md plan checkbox for 09-01 need updating.
### Orphaned Requirements
None. All requirement IDs declared in the phase (FREQ-01 through FREQ-04, GRP-01, GRP-04) are accounted for above.
---
## Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| None | — | — | — | — |
No TODO/FIXME/placeholder comments found in modified files. No empty implementations or hardcoded empty data in rendered paths. Unknown-family entries use hand-tuned `[]HarmonicDef{{1,1.0},{2,0.8},{3,0.4}}` by design (not a stub — WaveformType is intentionally WaveformCustom per D-05/D-06).
---
## Human Verification Required
None. Phase 9 produces data structures and constants with no UI, audio playback, or external service dependencies. All behaviors are fully verifiable via compile + test.
---
## Gaps Summary
No gaps. All 4 success criteria are verified against the actual codebase. The frequency allocation table exists as a code comment covering all 32 designed slots. The FreqConfig struct has the Group field. All 14 existing ClassFreqConfigs entries carry correct Hz values on the major-second ladder, family-matched waveforms, and non-empty Group strings. The autoAssignFreq range is [2500, 4000] Hz with a 51 Hz buffer above the highest built-in slot. All 7 packages compile and their full test suites pass.
**Tracking artifact to fix:** REQUIREMENTS.md should mark FREQ-01, FREQ-02, FREQ-03, GRP-01, GRP-04 as "Complete". ROADMAP.md should mark 09-01-PLAN.md as `[x]` and change "1/2 plans executed" to "2/2 plans executed".
---
_Verified: 2026-03-27_
_Verifier: Claude (gsd-verifier)_