# 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 (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. --- ## 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 | --- ## 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)