Files
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

34 KiB
Raw Permalink Blame History

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)

// 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.

  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)

// 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 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.goFreqConfig 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.goTestHarmonicsNonEmpty 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)