diff --git a/.planning/phases/09-frequency-design-and-group-architecture/09-RESEARCH.md b/.planning/phases/09-frequency-design-and-group-architecture/09-RESEARCH.md
new file mode 100644
index 0000000..9836ae9
--- /dev/null
+++ b/.planning/phases/09-frequency-design-and-group-architecture/09-RESEARCH.md
@@ -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 (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)