From f4ad40ef19186f38147a8a5a0d2e8375138f15b9 Mon Sep 17 00:00:00 2001 From: Markus Graf Date: Fri, 27 Mar 2026 13:59:28 +0100 Subject: [PATCH] =?UTF-8?q?docs(09):=20research=20phase=20domain=20?= =?UTF-8?q?=E2=80=94=20frequency=20allocation=20table=20and=20group=20arch?= =?UTF-8?q?itecture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../09-RESEARCH.md | 615 ++++++++++++++++++ 1 file changed, 615 insertions(+) create mode 100644 .planning/phases/09-frequency-design-and-group-architecture/09-RESEARCH.md 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)