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>
34 KiB
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
autoAssignFreqconstants inconfig/config.go:baseHz = 2500.0, computenumStepsto cover the range with 50 Hz steps. - D-05: The test in
config/config_test.gothat validates auto-assign range bounds ([1200, 2350]) must be updated to match the new range. - D-06: Add
Group stringfield toFreqConfigstruct. Simple string, not an enum. Values are family names: "Infrastructure", "Web", "Mail", "File Transfer", "Remote Access", "Database", "Discovery", "VoIP", "Unknown". - D-07: All existing
ClassFreqConfigsentries 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
WaveformPresetHarmonicsrather than hand-tuned) - Whether to consolidate
TestAllClassesHaveConfigand the renamedTestClassFreqConfigsMatchAllClassesor 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 | 214 | 10 | Triangle | 0.2 | Moved from v1.1 440 Hz | |
| IMAP | 241 | 11 | Triangle | 0.3 | Phase 10 new | |
| POP3 | 271 | 12 | Triangle | 0.4 | Phase 10 new | |
| SMTP-submit | 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 |
| 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)
// 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:
if cfg.BaseHz < 1200.0 || cfg.BaseHz > 2350.0 {
This must be updated to:
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.
// 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):
// 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.
// 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
synth/config.go— AddGroup stringtoFreqConfigstruct (compile breaks)synth/config.go— Replace fullClassFreqConfigsmap (compile restores)synth/config_test.go— AddTestGroupFieldPopulatedtestconfig/config.go— UpdateautoAssignFreqconstantsconfig/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
WaveformTypeis set to a non-zero value ANDHarmonicsis also non-empty,bank.gousesWaveformPresetHarmonicsat runtime (seelayer.go). Check how the bank resolves the precedence before assuming. - Don't reference the new
ClassFreqConfigsHz values as Go constants. They are map literal values, notconstexpressions. Phase 10 code that needs to know a class's Hz should look upClassFreqConfigs[class].BaseHzat runtime, not hard-code the value. - Don't leave any
ClassFreqConfigsentry without aGroup:value. Go struct literal fields are optional — missing fields silently zero-initialize. An emptyGroup: ""will pass compilation but breakTestGroupFieldPopulatedand the Phase 11PrintConfiggroup-header logic. - Don't change the
WaveformTypefield name or add new waveform types. TheWaveformCustomzero-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)
// 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)
// 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
// 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
// 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
// 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
Harmonicsslices inClassFreqConfigsfor non-Unknown classes: replace withWaveformPresetHarmonicscalls autoAssignFreqconstantsbaseHz = 1200.0,numSteps = 24: replace withbaseHz = 2500.0,numSteps = 31
Open Questions
-
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
autoAssignFreqmaking 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
-
TestHarmonicsNonEmpty vs WaveformSine single-harmonic entries
- What we know:
TestHarmonicsNonEmptyrequireslen(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
>= 1and add a comment explaining that sine is intentionally pure. One harmonic is not a bug.
- What we know:
-
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 filterClassDNSby 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— addTestGroupFieldPopulated— covers FREQ-01 and GRP-01synth/config_test.go— updateTestHarmonicsNonEmptyfrom< 2to< 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—FreqConfigstruct,WaveformPresetHarmonics,ClassFreqConfigs, existing Hz values confirmed - Direct code inspection of
config/config.golines 208-238 —autoAssignFreqconstants confirmed (baseHz = 1200.0,numSteps = 24) - Direct code inspection of
config/config_test.goline 363 — range assertion[1200, 2350]confirmed - Direct code inspection of
synth/config_test.go—TestHarmonicsNonEmptyassertslen >= 2confirmed - 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)