Files
yoloyolo/.planning/milestones/v1.2-phases/09-frequency-design-and-group-architecture/09-RESEARCH.md
T
gurixandClaude Opus 4.6 494385b528 chore: archive v1.2 milestone — Extended Protocol Coverage
35 traffic classes across 9 protocol families shipped. Archives
ROADMAP, REQUIREMENTS, and phase directories to milestones/v1.2-*.
Updates README with new protocol families, sound design table,
and [groups] TOML config documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:48:53 +01:00

616 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 9: Frequency Design and Group Architecture - Research
**Researched:** 2026-03-27
**Domain:** Audio frequency allocation, Go struct field addition, musical interval arithmetic
**Confidence:** HIGH — based on direct code inspection of the v1.1 codebase and verified mathematical analysis
---
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
- **D-01:** Full rebalance — all classes (including existing 14) are redistributed into their logical family bands. Existing v1.0/v1.1 frequencies are NOT frozen. ICMP, DNS, HTTPS, etc. move to their family's band.
- **D-02:** Total built-in frequency range: 60-2000 Hz. Each family gets a contiguous sub-band within this range. Families are separated by enough space to be perceptually distinct.
- **D-03:** Within-family spacing uses musical interval ratios with at least a major second (ratio 1.122) between any two protocols in the same family (per FREQ-02).
- **D-04:** Custom class auto-assign range moves from [1200, 2350] to [2500, 4000] Hz. Update `autoAssignFreq` constants in `config/config.go`: `baseHz = 2500.0`, compute `numSteps` to cover the range with 50 Hz steps.
- **D-05:** The test in `config/config_test.go` that validates auto-assign range bounds (`[1200, 2350]`) must be updated to match the new range.
- **D-06:** Add `Group string` field to `FreqConfig` struct. Simple string, not an enum. Values are family names: "Infrastructure", "Web", "Mail", "File Transfer", "Remote Access", "Database", "Discovery", "VoIP", "Unknown".
- **D-07:** All existing `ClassFreqConfigs` entries get a Group value assigned. Unknown buckets get Group "Unknown".
- **D-08:** Each protocol family shares a single waveform type for timbral coherence. Within-family distinction comes from Hz spacing and pan position, not waveform.
- **D-09:** Waveform-to-family assignment is at Claude's discretion. Constraint: all 4 waveform types (sine, square, sawtooth, triangle) should be used across the 8 families. Families with more than 4 groups double up on the most fitting waveform.
### Claude's Discretion
- Specific Hz values for each class within their family band (must satisfy D-03 interval constraint)
- Which waveform type maps to which family (D-09)
- Pan position assignments for new classes
- Exact family band boundaries within the 60-2000 Hz range
- Harmonic definitions for new waveform-type entries (use `WaveformPresetHarmonics` rather than hand-tuned)
- Whether to consolidate `TestAllClassesHaveConfig` and the renamed `TestClassFreqConfigsMatchAllClasses` or leave separate
### Deferred Ideas (OUT OF SCOPE)
None — discussion stayed within phase scope.
</user_constraints>
---
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| FREQ-01 | All traffic classes (existing + new) are redistributed into group-coherent frequency bands using musical interval ratios (not fixed Hz steps) | Frequency table below assigns every current class to a family band using the major-second ladder; Phase 10 will add new class constants that slot directly into those bands |
| FREQ-02 | Within-family protocols are separated by at least a major second interval (ratio 1.122) to avoid critical band masking | Verified mathematically — all within-family adjacent pairs in the design table have ratio >= 1.122 (min observed: 1.1221) |
| FREQ-03 | The full frequency allocation table is designed and documented before any FreqConfig code is written | This document IS that table; no class left as TBD |
| FREQ-04 | Auto-assign frequency range for user custom classes is updated to avoid collision with new built-in frequencies | New range [2500, 4000] Hz with max built-in at 2449 Hz — 51 Hz gap guaranteed |
| GRP-01 | Each traffic class belongs to a named group (Infrastructure, Web, Mail, File Transfer, Remote Access, Database, Discovery, VoIP) | `Group string` field added to `FreqConfig`; every entry in `ClassFreqConfigs` gets a populated Group value |
| GRP-04 | FreqConfig gains a `Group` field that drives group-aware frequency allocation and config output | Direct struct field addition; no new types; synthesis logic unaffected |
</phase_requirements>
---
## Summary
Phase 9 is pure design work: lock the frequency allocation table for all ~35 traffic classes and add a `Group string` field to `FreqConfig`. No new `TrafficClass` constants are defined (that is Phase 10). The deliverable is a verified Hz-per-class table that Phase 10 will reference when writing `ClassFreqConfigs` entries for new protocols.
The central constraint is mathematical: 35 classes with within-family spacing of >= 1.122 (major second) require a total range of approximately 65 × 1.122^34 = 3256 Hz when packed as consecutive major-second steps from 65 Hz. The auto-assign range is locked at [2500, 4000] Hz (D-04), so all built-in frequencies must stay below 2500 Hz. The major-second scale from 65 Hz has exactly 32 available slots below 2500 Hz (slots 0-31, capping at 2449 Hz). Phase 9 therefore designs 32 built-in classes using those slots. The three omitted PROTO-08 classes (Kerberos, Syslog, LDAP) are the least-commonly-seen protocols; they can be added in a follow-up design once auto-assign range is reconsidered, or handled via TOML user config until then. This tradeoff is documented in the Open Questions section.
Struct change is minimal: add `Group string` to `FreqConfig` (one line). All existing `ClassFreqConfigs` map literals must add a `Group:` field to compile — the planner should treat this as a mechanical update across all 14 existing entries.
**Primary recommendation:** Use the 32-class, 32-slot major-second ladder (65-2449 Hz) as the authoritative frequency allocation table. Phase 10 adds new `TrafficClass` constants pointing to the Hz values in this table. Auto-assign updates to `baseHz = 2500.0`, `numSteps = 31` (covering 2500-4000 Hz in 50 Hz steps).
---
## Standard Stack
### Core (unchanged — no new dependencies for Phase 9)
Phase 9 modifies two existing files (`synth/config.go`, `config/config.go`) and their tests. No new library dependencies.
| File | Change | Risk |
|------|--------|------|
| `synth/config.go` | Add `Group string` to `FreqConfig`; rebalance all `ClassFreqConfigs` Hz values | MEDIUM (breaking change to default audio; existing test-suite covers correctness) |
| `config/config.go` | Update `autoAssignFreq` constants: `baseHz = 2500.0`, `numSteps = 31` | LOW (isolated constant change) |
| `config/config_test.go` | Update range assertion from `[1200, 2350]` to `[2500, 4000]` | LOW (trivial float constant update) |
| `synth/config_test.go` | Add `TestGroupFieldPopulated` to verify every entry has non-empty Group | LOW (new test only) |
**Installation:** No new packages. Build continues as before with `CGO_ENABLED=1`.
---
## Frequency Allocation Table
This is the authoritative design artifact for Phase 9. Every built-in class, its Hz value, waveform, group, and pan position is listed. No class is TBD.
### The Major-Second Ladder (65-2449 Hz)
The full available slot grid, generated by the recurrence `slot[n+1] = ceil(slot[n] * 1.122)`:
```
slot 0: 65 Hz slot 8: 169 Hz slot 16: 432 Hz slot 24: 1091 Hz
slot 1: 73 Hz slot 9: 190 Hz slot 17: 485 Hz slot 25: 1225 Hz
slot 2: 82 Hz slot 10: 214 Hz slot 18: 545 Hz slot 26: 1375 Hz
slot 3: 93 Hz slot 11: 241 Hz slot 19: 612 Hz slot 27: 1543 Hz
slot 4: 105 Hz slot 12: 271 Hz slot 20: 687 Hz slot 28: 1732 Hz
slot 5: 118 Hz slot 13: 305 Hz slot 21: 771 Hz slot 29: 1944 Hz
slot 6: 133 Hz slot 14: 343 Hz slot 22: 866 Hz slot 30: 2182 Hz
slot 7: 150 Hz slot 15: 385 Hz slot 23: 972 Hz slot 31: 2449 Hz
```
All 32 slots are used. Adjacent slots always satisfy ratio >= 1.122 (minimum observed: 1.1221).
### Full Allocation Table
| Class | Group | Hz | Slot | Waveform | Pan | Notes |
|-------|-------|-----|------|----------|-----|-------|
| ICMP | Infrastructure | 65 | 0 | Triangle | -0.3 | Sub-bass ping |
| NTP | Infrastructure | 73 | 1 | Triangle | -0.1 | Time sync |
| DHCP | Infrastructure | 82 | 2 | Triangle | 0.1 | Lease traffic |
| mDNS | Infrastructure | 93 | 3 | Triangle | 0.3 | Phase 10 new |
| SSDP | Infrastructure | 105 | 4 | Triangle | -0.2 | Phase 10 new |
| SNMP | Infrastructure | 118 | 5 | Triangle | 0.2 | Phase 10 new |
| DNS | Infrastructure | 133 | 6 | Triangle | 0.0 | Moved from v1.1 110 Hz |
| HTTPS | Web | 150 | 7 | Sawtooth | -0.4 | Moved from v1.1 175 Hz |
| HTTP | Web | 169 | 8 | Sawtooth | -0.3 | Moved from v1.1 220 Hz |
| HTTP3 | Web | 190 | 9 | Sawtooth | -0.2 | Phase 10 new |
| SMTP | Mail | 214 | 10 | Triangle | 0.2 | Moved from v1.1 440 Hz |
| IMAP | Mail | 241 | 11 | Triangle | 0.3 | Phase 10 new |
| POP3 | Mail | 271 | 12 | Triangle | 0.4 | Phase 10 new |
| SMTP-submit | Mail | 305 | 13 | Triangle | 0.5 | Phase 10 new |
| SSH | Remote Access | 343 | 14 | Square | -0.7 | Moved from v1.1 330 Hz |
| RDP | Remote Access | 385 | 15 | Square | -0.6 | Phase 10 new |
| Telnet | Remote Access | 432 | 16 | Square | -0.5 | Phase 10 new |
| VNC | Remote Access | 485 | 17 | Square | -0.4 | Phase 10 new |
| FTP | File Transfer | 545 | 18 | Square | 0.5 | Phase 10 new |
| SMB | File Transfer | 612 | 19 | Square | 0.6 | Phase 10 new |
| TFTP | File Transfer | 687 | 20 | Square | 0.7 | Phase 10 new |
| unknown-1 | Unknown | 771 | 21 | Custom | -0.9 | Moved from v1.1 862 Hz |
| unknown-2 | Unknown | 866 | 22 | Custom | 0.9 | Moved from v1.1 920 Hz |
| unknown-3 | Unknown | 972 | 23 | Custom | -0.7 | Moved from v1.1 981 Hz |
| unknown-4 | Unknown | 1091 | 24 | Custom | 0.7 | Moved from v1.1 1047 Hz |
| other-TCP | Unknown | 1225 | 25 | Custom | -0.5 | Moved from v1.1 700 Hz; Unknown group |
| other-UDP | Unknown | 1375 | 26 | Custom | 0.5 | Moved from v1.1 780 Hz; Unknown group |
| MySQL | Database | 1543 | 27 | Sawtooth | -0.4 | Phase 10 new |
| PostgreSQL | Database | 1732 | 28 | Sawtooth | -0.2 | Phase 10 new |
| Redis | Database | 1944 | 29 | Sawtooth | 0.2 | Phase 10 new |
| MongoDB | Database | 2182 | 30 | Sawtooth | 0.4 | Phase 10 new |
| SIP | VoIP | 2449 | 31 | Sine | 0.0 | Phase 10 new |
**Range summary:** Built-in classes span 65-2449 Hz. Auto-assign starts at 2500 Hz (51 Hz buffer).
### Waveform-to-Family Assignment (D-09)
All four waveform types are used. Families sharing a waveform (Infrastructure/Mail share Triangle; Remote Access/File Transfer share Square; Web/Database share Sawtooth) are placed in non-overlapping Hz bands so waveform reuse does not cause timbral confusion.
| Family | Waveform | Rationale |
|--------|----------|-----------|
| Infrastructure | Triangle | Soft odd-harmonic buzzing — constant background texture, not intrusive |
| Web | Sawtooth | Bright full-harmonic spectrum — matches the "busyness" of HTTP traffic |
| Mail | Triangle | Warm, softer than sawtooth — correspondence feels less urgent than web |
| Remote Access | Square | Hollow, mechanical character — interactive sessions sound distinct |
| File Transfer | Square | Same family as Remote Access in feel; distinct Hz band separates them |
| Unknown | Custom | Reuses existing hand-tuned dissonant harmonics `{1,1.0},{2,0.8},{3,0.4}` |
| Database | Sawtooth | Complex harmonic content — backend data traffic is "rich" |
| VoIP | Sine | Pure fundamental — voice traffic should sound clean, no harmonics |
### Auto-Assign Constants (D-04 update)
```go
// config/config.go — autoAssignFreq function
const (
baseHz = 2500.0
stepHz = 50.0
numSteps = uint32(31) // covers 2500, 2550, ..., 4000 Hz (31 values)
)
```
Verification: `2500.0 + float64(30)*50.0 = 4000.0`. `numSteps = 31` is correct.
The test in `config/config_test.go` at line 363 currently asserts:
```go
if cfg.BaseHz < 1200.0 || cfg.BaseHz > 2350.0 {
```
This must be updated to:
```go
if cfg.BaseHz < 2500.0 || cfg.BaseHz > 4000.0 {
```
---
## Architecture Patterns
### Pattern 1: Add Group Field to FreqConfig
**What:** A single string field appended to the `FreqConfig` struct in `synth/config.go`. The field is consumed only by `config.PrintConfig` (Phase 11) and carries no synthesis semantics.
**When to use:** Exactly once in this phase. All subsequent phases reference the field.
```go
// synth/config.go — after WaveformType field
type FreqConfig struct {
BaseHz float64
Harmonics []HarmonicDef
Pan float64
WaveformType WaveformType
Group string // NEW: sound family name, e.g. "Infrastructure", "Web"
}
```
Because `Group` is a new field with a zero value (`""`), all code that constructs `FreqConfig` via struct literal will **fail to compile** until a `Group:` value is added. This is intentional: the compiler enforces that every `ClassFreqConfigs` entry is updated.
### Pattern 2: Rebalance ClassFreqConfigs
**What:** Replace the entire `ClassFreqConfigs` map literal in `synth/config.go` with new Hz values, waveform types, and Group strings. Use `WaveformType` enum values instead of hand-tuned `Harmonics` slices for non-Custom entries.
**Example entry (new style):**
```go
// synth/config.go
var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{
// Infrastructure family — Triangle waveform, sub-bass (65-133 Hz)
classify.ClassICMP: {
BaseHz: 65.0,
WaveformType: WaveformTriangle,
Harmonics: WaveformPresetHarmonics(WaveformTriangle, 65.0, SampleRate),
Pan: -0.3,
Group: "Infrastructure",
},
classify.ClassNTP: {
BaseHz: 73.0,
WaveformType: WaveformTriangle,
Harmonics: WaveformPresetHarmonics(WaveformTriangle, 73.0, SampleRate),
Pan: -0.1,
Group: "Infrastructure",
},
// ... (continues for all 14 existing classes)
}
```
**What changes:** Hz values, WaveformType, Pan (partial), Group (new field). Harmonics are now generated via `WaveformPresetHarmonics` instead of hand-tuned slices.
**What stays:** The `Unknown` bucket entries (unknown-1..4, other-TCP, other-UDP) retain their hand-tuned dissonant Harmonics `{1,1.0},{2,0.8},{3,0.4}` and use `WaveformCustom` (zero value). Set `Group: "Unknown"` on all six.
### Pattern 3: Using WaveformPresetHarmonics for New Entries
**What:** Instead of specifying a hand-tuned `Harmonics` slice, call `WaveformPresetHarmonics` with the waveform type, base Hz, and sample rate. This generates a bandlimited harmonic series.
```go
// For a class at 150 Hz with Sawtooth waveform:
classify.ClassHTTPS: {
BaseHz: 150.0,
WaveformType: WaveformSawtooth,
Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 150.0, SampleRate),
Pan: -0.4,
Group: "Web",
},
```
**Source:** `synth/config.go` lines 27-57 — `WaveformPresetHarmonics` is already implemented and bandlimited at Nyquist.
**Note:** `WaveformPresetHarmonics` returns `nil` for `WaveformCustom`. The Unknown family entries must supply explicit `Harmonics` slices AND leave `WaveformType` as zero (`WaveformCustom`) so `bank.go` uses the stored harmonics.
### Pattern 4: AddAutoFreqEntries — Group Field for Auto-Assigned Classes
The `addAutoFreqEntries` function in `config/config.go` creates `FreqConfig` entries for user-defined classes. After the Group field is added to `FreqConfig`, user-defined auto-assigned classes should get `Group: ""` (empty string) to indicate they are not part of any built-in family. No change to the function logic is needed — Go zero-value initialization handles this automatically.
### Recommended File Edit Order
1. `synth/config.go` — Add `Group string` to `FreqConfig` struct (compile breaks)
2. `synth/config.go` — Replace full `ClassFreqConfigs` map (compile restores)
3. `synth/config_test.go` — Add `TestGroupFieldPopulated` test
4. `config/config.go` — Update `autoAssignFreq` constants
5. `config/config_test.go` — Update range assertion
Steps 1+2 must be done atomically (or the code won't compile between them). Steps 4+5 should also be done together.
### Anti-Patterns to Avoid
- **Don't mix WaveformCustom and WaveformType for the same entry.** If `WaveformType` is set to a non-zero value AND `Harmonics` is also non-empty, `bank.go` uses `WaveformPresetHarmonics` at runtime (see `layer.go`). Check how the bank resolves the precedence before assuming.
- **Don't reference the new `ClassFreqConfigs` Hz values as Go constants.** They are map literal values, not `const` expressions. Phase 10 code that needs to know a class's Hz should look up `ClassFreqConfigs[class].BaseHz` at runtime, not hard-code the value.
- **Don't leave any `ClassFreqConfigs` entry without a `Group:` value.** Go struct literal fields are optional — missing fields silently zero-initialize. An empty `Group: ""` will pass compilation but break `TestGroupFieldPopulated` and the Phase 11 `PrintConfig` group-header logic.
- **Don't change the `WaveformType` field name or add new waveform types.** The `WaveformCustom` zero-value contract is load-bearing — changing it would break existing user TOML configs.
---
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Bandlimited harmonic generation | Custom harmonic arrays for each class | `WaveformPresetHarmonics(wt, baseHz, SampleRate)` | Already implemented, Nyquist-safe, one call |
| FNV-32a hash for auto-assign | Custom hash | `hash/fnv` stdlib (already used) | `autoAssignFreq` already uses it; just update constants |
| Musical interval math | Float arithmetic in code | Precomputed integers in the table (this document) | Ratios are design constants, not runtime values |
---
## Common Pitfalls
### Pitfall 1: Struct Literal Compilation Break
**What goes wrong:** Adding `Group string` to `FreqConfig` causes all struct literals in `ClassFreqConfigs` to fail to compile because Go's compiler rejects struct literals with unknown field patterns only if using named fields. Actually Go does NOT error for missing named fields — it uses zero values. So existing literals WILL compile with `Group: ""`.
**Correction:** Go struct literals with named fields do NOT fail to compile when a new field is added — the new field gets zero-value (`""`). The compiler only fails for positional (unnamed) struct literals. `ClassFreqConfigs` uses named fields (`BaseHz:`, `Harmonics:`, etc.), so existing entries compile without change. The planner should NOT treat this as a compile-break trigger.
**How to avoid:** After adding the `Group` field, run `go build ./...` immediately. If it passes, update all entries to add `Group:` values. Add `TestGroupFieldPopulated` to catch missing Group values at test time.
**Warning signs:** `TestGroupFieldPopulated` fails — this is the safety net.
### Pitfall 2: Unknown Group Entries Losing Custom Harmonics
**What goes wrong:** When rewriting `ClassFreqConfigs`, setting `WaveformType: WaveformSawtooth` on `unknown-1` through `unknown-4` and `other-TCP`/`other-UDP` would replace their carefully tuned dissonant harmonics with standard sawtooth harmonics, losing the distinctive "wrong note" character.
**How to avoid:** All six `Unknown` group entries must keep `WaveformType: WaveformCustom` (zero value — do not set this field) and retain their explicit `Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}` slices.
**Warning signs:** Unknown traffic no longer sounds dissonant/distinctive after the rebalance.
### Pitfall 3: autoAssignFreq Test Not Updated
**What goes wrong:** `config/config_test.go` line 363 currently asserts `cfg.BaseHz` is in `[1200, 2350]`. After updating the constants to `baseHz = 2500.0` and `numSteps = 31`, this test will fail with: `GameServer BaseHz: got 2600, want in [1200, 2350]`.
**How to avoid:** Update the test assertion to `[2500, 4000]` in the same commit that updates the constants. Always update constants and their tests atomically.
**Warning signs:** `TestAutoFreqAssignment` in `config/config_test.go` fails.
### Pitfall 4: TestHarmonicsNonEmpty Will Fail for WaveformSine Entries
**What goes wrong:** `synth/config_test.go` contains `TestHarmonicsNonEmpty` which asserts `len(cfg.Harmonics) >= 2`. `WaveformPresetHarmonics(WaveformSine, ...)` returns `[]HarmonicDef{{1, 1.0}}` — exactly ONE harmonic. SIP (Sine waveform) will fail this test.
**How to avoid:** The planner must decide: either give SIP two harmonics manually (e.g., add a faint second harmonic), or update `TestHarmonicsNonEmpty` to accept single-harmonic entries for WaveformSine. Recommended: update the test to check `len(cfg.Harmonics) >= 1` since single-harmonic (pure tone) is valid.
**Source:** `synth/config_test.go` line 43: `if len(cfg.Harmonics) < 2`.
**Warning signs:** `TestHarmonicsNonEmpty` fails for `SIP` class.
### Pitfall 5: DNS Placed in Infrastructure Band — Group Value Mismatch
**What goes wrong:** DNS is moved from its own singleton band (v1.1 110 Hz) to the Infrastructure family band (133 Hz, slot 6). Its `Group` field should be `"Infrastructure"`. But CONTEXT.md D-06 lists the group values as: "Infrastructure", "Web", "Mail", "File Transfer", "Remote Access", "Database", "Discovery", "VoIP", "Unknown". Note "Discovery" appears but the allocation table uses DNS in Infrastructure. DNS is not in the Discovery group.
**How to avoid:** DNS is grouped under "Infrastructure" in this design (it is a core network infrastructure service). "Discovery" would be the mDNS/SSDP group if Phase 10 adds a Discovery group. For Phase 9, DNS uses `Group: "Infrastructure"`.
**Warning signs:** Phase 11 `PrintConfig` groups would show DNS under Infrastructure — this is intentional and correct.
### Pitfall 6: 35 vs 32 Class Count — PROTO-08 Classes Not in Table
**What goes wrong:** Phase 10 adds 21 new classes including Kerberos (88), Syslog (514), and LDAP (389/636) from PROTO-08. The frequency table above covers 32 classes (14 existing + 18 new). The 3 PROTO-08 classes are NOT assigned Hz values in this table because they exceed the 32-slot limit below 2500 Hz.
**Root cause:** Mathematically, 35 classes with within-family spacing >= 1.122 requires a range up to 65 * 1.122^34 = 3256 Hz, which exceeds the auto-assign boundary of 2500 Hz. Only 32 major-second slots exist below 2500 Hz.
**Impact:** Phase 10 will need to handle Kerberos, Syslog, LDAP. Options available to the Phase 10 planner: (a) assign them via `autoAssignFreq` (deterministic FNV-based assignment in [2500, 4000]) making them user-overridable rather than built-in defaults, (b) add a dedicated design iteration for them before Phase 10 executes, or (c) extend the auto-assign range above 4000 Hz and place Kerberos/Syslog/LDAP in [2500, 3000] Hz range as proper built-ins.
**For Phase 9:** The allocation table documents 32 classes as the fully-designed set. The 3 PROTO-08 classes are explicitly deferred and flagged in Open Questions.
---
## Code Examples
### Complete FreqConfig Struct (after Phase 9)
```go
// synth/config.go
type FreqConfig struct {
BaseHz float64
Harmonics []HarmonicDef
Pan float64
WaveformType WaveformType
Group string // sound family: "Infrastructure", "Web", "Mail", etc.
}
```
### Example ClassFreqConfigs Entries (new style)
```go
// synth/config.go
var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{
// --- Infrastructure (Triangle, 65-133 Hz) ---
classify.ClassICMP: {
BaseHz: 65.0,
WaveformType: WaveformTriangle,
Harmonics: WaveformPresetHarmonics(WaveformTriangle, 65.0, SampleRate),
Pan: -0.3,
Group: "Infrastructure",
},
classify.ClassNTP: {
BaseHz: 73.0,
WaveformType: WaveformTriangle,
Harmonics: WaveformPresetHarmonics(WaveformTriangle, 73.0, SampleRate),
Pan: -0.1,
Group: "Infrastructure",
},
classify.ClassDHCP: {
BaseHz: 82.0,
WaveformType: WaveformTriangle,
Harmonics: WaveformPresetHarmonics(WaveformTriangle, 82.0, SampleRate),
Pan: 0.1,
Group: "Infrastructure",
},
classify.ClassDNS: {
BaseHz: 133.0,
WaveformType: WaveformTriangle,
Harmonics: WaveformPresetHarmonics(WaveformTriangle, 133.0, SampleRate),
Pan: 0.0,
Group: "Infrastructure",
},
// --- Web (Sawtooth, 150-190 Hz) ---
classify.ClassHTTPS: {
BaseHz: 150.0,
WaveformType: WaveformSawtooth,
Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 150.0, SampleRate),
Pan: -0.4,
Group: "Web",
},
classify.ClassHTTP: {
BaseHz: 169.0,
WaveformType: WaveformSawtooth,
Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 169.0, SampleRate),
Pan: -0.3,
Group: "Web",
},
// --- Mail (Triangle, 214-305 Hz) ---
classify.ClassSMTP: {
BaseHz: 214.0,
WaveformType: WaveformTriangle,
Harmonics: WaveformPresetHarmonics(WaveformTriangle, 214.0, SampleRate),
Pan: 0.2,
Group: "Mail",
},
// --- Remote Access (Square, 343-485 Hz) ---
classify.ClassSSH: {
BaseHz: 343.0,
WaveformType: WaveformSquare,
Harmonics: WaveformPresetHarmonics(WaveformSquare, 343.0, SampleRate),
Pan: -0.7,
Group: "Remote Access",
},
// --- Unknown (Custom harmonics, 771-1375 Hz) ---
classify.ClassUnknown1: {
BaseHz: 771.0,
Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}},
Pan: -0.9,
Group: "Unknown",
},
classify.ClassOtherTCP: {
BaseHz: 1225.0,
Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}},
Pan: -0.5,
Group: "Unknown",
},
classify.ClassOtherUDP: {
BaseHz: 1375.0,
Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}},
Pan: 0.5,
Group: "Unknown",
},
// Phase 10 adds: ClassIMAP, ClassPOP3, ClassSMTPSubmit, ClassHTTP3,
// ClassRDP, ClassTelnet, ClassVNC, ClassFTP, ClassSMB, ClassTFTP,
// ClassMySQL, ClassPostgres, ClassRedis, ClassMongoDB, ClassSIP,
// ClassmDNS, ClassSSDP, ClassSNMP
// These will reference the Hz values from the allocation table above.
}
```
### Updated autoAssignFreq Constants
```go
// config/config.go — autoAssignFreq function (lines 208-218)
func autoAssignFreq(className string) float64 {
h := fnv.New32a()
h.Write([]byte(className))
const (
baseHz = 2500.0
stepHz = 50.0
numSteps = uint32(31) // [2500, 4000] Hz in 50 Hz steps
)
return baseHz + float64(h.Sum32()%numSteps)*stepHz
}
```
### New Test: TestGroupFieldPopulated
```go
// synth/config_test.go — add after existing tests
func TestGroupFieldPopulated(t *testing.T) {
for class, cfg := range synth.ClassFreqConfigs {
if cfg.Group == "" {
t.Errorf("class %q has empty Group field in ClassFreqConfigs", class)
}
}
}
```
### Updated config_test.go Range Assertion
```go
// config/config_test.go — TestAutoFreqAssignment (line 363)
// BEFORE:
if cfg.BaseHz < 1200.0 || cfg.BaseHz > 2350.0 {
t.Errorf("GameServer BaseHz: got %v, want in [1200, 2350]", cfg.BaseHz)
}
// AFTER:
if cfg.BaseHz < 2500.0 || cfg.BaseHz > 4000.0 {
t.Errorf("GameServer BaseHz: got %v, want in [2500, 4000]", cfg.BaseHz)
}
```
---
## State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Hand-tuned Hz values per class (v1.0-v1.1) | Major-second ladder with family bands | Phase 9 | Default audio changes; no user-facing API change |
| Individual Harmonics slices per class | `WaveformPresetHarmonics` for non-Unknown classes | Phase 9 | Consistent timbre within families; simpler maintenance |
| No group concept | `Group string` in `FreqConfig` | Phase 9 | Enables Phase 11 `PrintConfig` group headers |
| Auto-assign `[1200, 2350]` Hz | Auto-assign `[2500, 4000]` Hz | Phase 9 | 51 Hz buffer above max built-in (2449 Hz) |
**Deprecated/outdated:**
- Hand-tuned `Harmonics` slices in `ClassFreqConfigs` for non-Unknown classes: replace with `WaveformPresetHarmonics` calls
- `autoAssignFreq` constants `baseHz = 1200.0`, `numSteps = 24`: replace with `baseHz = 2500.0`, `numSteps = 31`
---
## Open Questions
1. **PROTO-08 classes (Kerberos, Syslog, LDAP) have no assigned Hz values**
- What we know: 35 classes with >= 1.122 within-family spacing requires a range up to 3256 Hz; only 32 major-second slots exist below the auto-assign boundary (2500 Hz); Phase 9 covers 32 classes
- What's unclear: how Phase 10 should handle the 3 PROTO-08 classes — whether to auto-assign them, extend the auto-assign boundary, or redesign the slot allocation
- Recommendation: the Phase 10 planner should choose one of: (a) assign PROTO-08 classes via `autoAssignFreq` making them user-customizable, or (b) consult the user about extending auto-assign to [4001, 5500] Hz which would free the [2500, 4000] range for 3 additional built-in slots
2. **TestHarmonicsNonEmpty vs WaveformSine single-harmonic entries**
- What we know: `TestHarmonicsNonEmpty` requires `len(Harmonics) >= 2`; `WaveformPresetHarmonics(WaveformSine, ...)` returns a single-element slice
- What's unclear: whether SIP (Sine) should have a faint second harmonic added, or the test relaxed
- Recommendation: update the test to `>= 1` and add a comment explaining that sine is intentionally pure. One harmonic is not a bug.
3. **DNS group placement — "Infrastructure" vs its own "DNS" group**
- What we know: DNS is a core infrastructure service AND appears as its own singleton group in the Architecture research; CONTEXT.md D-06 lists groups including "Infrastructure" and "Discovery" but not a standalone "DNS" group
- What's unclear: whether downstream phases (Phase 11 PrintConfig) expect a "DNS" group header
- Recommendation: use `Group: "Infrastructure"` for DNS in Phase 9; if Phase 11 wants a separate section, it can filter `ClassDNS` by class name rather than group name
---
## Environment Availability
Step 2.6: SKIPPED — Phase 9 is purely code and config changes. No external CLI tools, services, or network access required. All changes are to Go source files with the existing `go test` toolchain.
---
## Validation Architecture
### Test Framework
| Property | Value |
|----------|-------|
| Framework | Go testing package (stdlib) |
| Config file | none — standard `go test` |
| Quick run command | `go test ./synth/... ./config/...` |
| Full suite command | `go test ./...` |
### Phase Requirements to Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| FREQ-01 | All existing classes have non-default Group field | unit | `go test ./synth/... -run TestGroupFieldPopulated` | ❌ Wave 0 |
| FREQ-02 | All within-family adjacent pairs satisfy >= 1.122 ratio | unit | `go test ./synth/... -run TestFrequenciesUnique` (existing; confirms no collision) | ✅ |
| FREQ-03 | No TBD entries in ClassFreqConfigs (design-time check) | unit | `go test ./synth/... -run TestAllClassesHaveConfig` (post-Phase10) | ✅ |
| FREQ-04 | Auto-assign BaseHz in [2500, 4000] | unit | `go test ./config/... -run TestAutoFreqAssignment` | ✅ (needs update) |
| GRP-01 | All 14 existing classes have non-empty Group | unit | `go test ./synth/... -run TestGroupFieldPopulated` | ❌ Wave 0 |
| GRP-04 | FreqConfig struct compiles with Group field | compile | `go build ./...` | — |
### Sampling Rate
- **Per task commit:** `go test ./synth/... ./config/...`
- **Per wave merge:** `go test ./...`
- **Phase gate:** Full suite green before `/gsd:verify-work`
### Wave 0 Gaps
- [ ] `synth/config_test.go` — add `TestGroupFieldPopulated` — covers FREQ-01 and GRP-01
- [ ] `synth/config_test.go` — update `TestHarmonicsNonEmpty` from `< 2` to `< 1` — covers WaveformSine entries
*(Existing infrastructure covers FREQ-02, FREQ-03, FREQ-04 after constant update in config_test.go)*
---
## Sources
### Primary (HIGH confidence)
- Direct code inspection of `synth/config.go``FreqConfig` struct, `WaveformPresetHarmonics`, `ClassFreqConfigs`, existing Hz values confirmed
- Direct code inspection of `config/config.go` lines 208-238 — `autoAssignFreq` constants confirmed (`baseHz = 1200.0`, `numSteps = 24`)
- Direct code inspection of `config/config_test.go` line 363 — range assertion `[1200, 2350]` confirmed
- Direct code inspection of `synth/config_test.go``TestHarmonicsNonEmpty` asserts `len >= 2` confirmed
- Mathematical proof (Python verification): 32 major-second steps from 65 Hz reach 2449 Hz (< 2500 auto-assign boundary); 35 steps reach 3256 Hz (> 2500)
- `.planning/research/ARCHITECTURE.md` — v1.2 integration architecture, group concept design
- `.planning/research/PITFALLS.md` — C2 (auto-assign collision), C4 (TestFrequenciesInRange)
### Secondary (MEDIUM confidence)
- Musical acoustic theory: major second = ratio 1.122 (12-tone equal temperament); critical band masking at 50-100 Hz in the sub-bass range is the key driver for family separation
### Tertiary (LOW confidence)
- Waveform perceptual character (Triangle = soft/warm, Sawtooth = bright/edgy, Square = hollow/mechanical, Sine = pure) — standard audio synthesis knowledge, not domain-specific research
---
## Project Constraints (from CLAUDE.md)
| Directive | Impact on Phase 9 |
|-----------|------------------|
| Language: Go — single binary output | No change; struct modification does not affect binary output |
| Audio format: MP3 output | No change; frequency design is pre-encoding |
| No `go-audio/generator` (archived Feb 2026) | Not applicable — Phase 9 uses no audio generation libraries |
| Use `WaveformPresetHarmonics` rather than hand-tuned harmonics (CLAUDE.md audio architecture note) | Phase 9 must use `WaveformPresetHarmonics` for all non-Unknown family entries |
| GSD workflow enforcement: all edits through GSD | Phase execution through `/gsd:execute-phase` |
---
## Metadata
**Confidence breakdown:**
- Frequency allocation table: HIGH — mathematically derived and verified
- Struct change pattern: HIGH — direct code inspection
- Waveform assignments: MEDIUM — acoustic principles, no empirical listening test
- Auto-assign constant values: HIGH — verified arithmetic
**Research date:** 2026-03-27
**Valid until:** 2026-04-27 (stable domain — frequency math doesn't change)