package oracle import ( "testing" "git.mleku.dev/mleku/dendrite/pkg/state" ) func TestAbiogenesis(t *testing.T) { o := New(42) r := o.Cast("self", 0, 1) if r == nil { t.Fatal("Cast returned nil") } if r.Sequence != 0 { t.Errorf("first reading should be sequence 0, got %d", r.Sequence) } if r.Generation != 1 { t.Errorf("expected generation 1, got %d", r.Generation) } if r.Source != "self" { t.Errorf("expected source 'self', got %q", r.Source) } if len(r.Directives) == 0 { t.Error("expected at least one directive") } // Verify resulting hexagram matches applying changing lines. expected := ApplyChangingLines(r.Primary, r.Lines) if r.Resulting != expected { t.Errorf("resulting hexagram %d != expected %d from ApplyChangingLines", r.Resulting, expected) } } func TestChaining(t *testing.T) { o := New(123) r1 := o.Cast("self", 0, 1) r2 := o.Cast("self", 1, 2) // The second reading's primary must be the first reading's resulting. if r2.Primary != r1.Resulting { t.Errorf("chained primary %d != previous resulting %d", r2.Primary, r1.Resulting) } if r2.Sequence != 1 { t.Errorf("expected sequence 1, got %d", r2.Sequence) } // First reading should be in history. if len(o.History) != 1 { t.Fatalf("expected 1 history entry, got %d", len(o.History)) } if o.History[0] != r1 { t.Error("history[0] is not the first reading") } } func TestChain10(t *testing.T) { o := New(999) var readings []*Reading for i := range 10 { r := o.Cast("self", uint8(i%4), uint32(i+1)) readings = append(readings, r) } // Each reading's primary == previous reading's resulting. for i := 1; i < len(readings); i++ { if readings[i].Primary != readings[i-1].Resulting { t.Errorf("reading %d primary %d != reading %d resulting %d", i, readings[i].Primary, i-1, readings[i-1].Resulting) } } // All resulting hexagrams must be valid (0-63). for i, r := range readings { if r.Resulting > 63 { t.Errorf("reading %d resulting hexagram %d > 63", i, r.Resulting) } if r.Primary > 63 { t.Errorf("reading %d primary hexagram %d > 63", i, r.Primary) } } } func TestXORReversibility(t *testing.T) { // XOR is its own inverse: (a ^ b) ^ b == a for a := LineState(0); a < 4; a++ { for b := LineState(0); b < 4; b++ { result := (a ^ b) ^ b if result != a { t.Errorf("XOR not reversible: (%d ^ %d) ^ %d = %d, want %d", a, b, b, result, a) } } } } func TestXORTransitions(t *testing.T) { // Verify the XOR transition semantics. tests := []struct { initial LineState entropy LineState expected LineState desc string }{ {YoungYin, 0b00, YoungYin, "00 entropy preserves state"}, {YoungYang, 0b00, YoungYang, "00 entropy preserves yang"}, {OldYin, 0b00, OldYin, "00 entropy preserves old yin"}, {OldYang, 0b00, OldYang, "00 entropy preserves old yang"}, {YoungYin, 0b01, YoungYang, "01 flips polarity yin→yang"}, {YoungYang, 0b01, YoungYin, "01 flips polarity yang→yin"}, {YoungYin, 0b10, OldYin, "10 flips stability young→old"}, {YoungYang, 0b10, OldYang, "10 flips stability young→old yang"}, {YoungYin, 0b11, OldYang, "11 inverts both"}, {OldYang, 0b11, YoungYin, "11 inverts old yang back"}, } for _, tt := range tests { got := tt.initial ^ tt.entropy if got != tt.expected { t.Errorf("%s: %d ^ %d = %d, want %d", tt.desc, tt.initial, tt.entropy, got, tt.expected) } } } func TestLineStatePredicates(t *testing.T) { if YoungYin.IsChanging() { t.Error("YoungYin should not be changing") } if YoungYang.IsChanging() { t.Error("YoungYang should not be changing") } if !OldYin.IsChanging() { t.Error("OldYin should be changing") } if !OldYang.IsChanging() { t.Error("OldYang should be changing") } if YoungYin.IsYang() { t.Error("YoungYin should not be yang") } if !YoungYang.IsYang() { t.Error("YoungYang should be yang") } if OldYin.IsYang() { t.Error("OldYin should not be yang") } if !OldYang.IsYang() { t.Error("OldYang should be yang") } } func TestStabilize(t *testing.T) { if OldYin.Stabilize() != YoungYin { t.Errorf("OldYin.Stabilize() = %d, want YoungYin (%d)", OldYin.Stabilize(), YoungYin) } if OldYang.Stabilize() != YoungYang { t.Errorf("OldYang.Stabilize() = %d, want YoungYang (%d)", OldYang.Stabilize(), YoungYang) } // Young states stabilize to themselves. if YoungYin.Stabilize() != YoungYin { t.Error("YoungYin.Stabilize() should be identity") } if YoungYang.Stabilize() != YoungYang { t.Error("YoungYang.Stabilize() should be identity") } } func TestApplyChangingLines(t *testing.T) { // No changing lines → resulting == primary. hex := state.Hex(state.Heaven, state.Earth) // 101 inner, 000 outer lines := [6]LineState{YoungYang, YoungYin, YoungYang, YoungYin, YoungYin, YoungYin} result := ApplyChangingLines(hex, lines) if result != hex { t.Errorf("no changing lines: got %d, want %d", result, hex) } // Single changing inner line 0 (bonding bit). lines[0] = OldYang // yang becoming yin → flip inner bit 0 result = ApplyChangingLines(hex, lines) expected := hex.MoveLine(true, 0) if result != expected { t.Errorf("changing inner line 0: got %d, want %d", result, expected) } // Single changing outer line 3 (outer bit 0 = bonding). lines[0] = YoungYang // reset lines[3] = OldYin // yin becoming yang → flip outer bit 0 result = ApplyChangingLines(hex, lines) expected = hex.MoveLine(false, 0) if result != expected { t.Errorf("changing outer line 3: got %d, want %d", result, expected) } } func TestChangingLines(t *testing.T) { r := &Reading{ Lines: [6]LineState{YoungYin, OldYang, YoungYang, OldYin, YoungYin, OldYang}, } cl := r.ChangingLines() expected := []int{1, 3, 5} if len(cl) != len(expected) { t.Fatalf("ChangingLines length %d, want %d", len(cl), len(expected)) } for i, v := range cl { if v != expected[i] { t.Errorf("ChangingLines[%d] = %d, want %d", i, v, expected[i]) } } } func TestDeterminism(t *testing.T) { // Same seed + source + sequence should produce identical readings. o1 := New(777) r1 := o1.Cast("self", 0, 1) o2 := New(777) r2 := o2.Cast("self", 0, 1) if r1.Primary != r2.Primary { t.Error("determinism failed: different primaries") } if r1.Lines != r2.Lines { t.Error("determinism failed: different lines") } if r1.Resulting != r2.Resulting { t.Error("determinism failed: different resulting") } } func TestMarshalRoundtrip(t *testing.T) { o := New(42) o.Cast("self", 0, 1) o.Cast("forage", 1, 2) o.Cast("nostr", 2, 3) data, err := o.Marshal() if err != nil { t.Fatalf("Marshal: %v", err) } o2, err := FromState(data) if err != nil { t.Fatalf("FromState: %v", err) } if o2.Seed != o.Seed { t.Errorf("seed mismatch: %d vs %d", o2.Seed, o.Seed) } if o2.Current.Sequence != o.Current.Sequence { t.Errorf("current sequence mismatch: %d vs %d", o2.Current.Sequence, o.Current.Sequence) } if len(o2.History) != len(o.History) { t.Errorf("history length mismatch: %d vs %d", len(o2.History), len(o.History)) } } func TestTrigramMapping(t *testing.T) { // All 8 trigrams should map to distinct domain and intention types. trigrams := []state.Trigram{ state.Earth, state.Thunder, state.Water, state.Lake, state.Fire, state.Heaven, state.Wind, state.Mountain, } domains := make(map[DomainType]bool) intentions := make(map[IntentionType]bool) for _, tri := range trigrams { d := TrigramToDomain(tri) if domains[d] { t.Errorf("duplicate domain %d for trigram %d", d, tri) } domains[d] = true i := TrigramToIntention(tri) if intentions[i] { t.Errorf("duplicate intention %d for trigram %d", i, tri) } intentions[i] = true } if len(domains) != 8 { t.Errorf("expected 8 distinct domains, got %d", len(domains)) } if len(intentions) != 8 { t.Errorf("expected 8 distinct intentions, got %d", len(intentions)) } } func TestDirectiveGeneration(t *testing.T) { o := New(42) r := o.Cast("forage", 0, 1) if len(r.Directives) == 0 { t.Fatal("expected at least one directive") } // First directive should be primary (ChangingBit == -1). if r.Directives[0].ChangingBit != -1 { t.Errorf("first directive ChangingBit = %d, want -1", r.Directives[0].ChangingBit) } // Number of directives should be 1 (primary) + number of changing lines. changingCount := len(r.ChangingLines()) expected := 1 + changingCount if len(r.Directives) != expected { t.Errorf("directive count %d, want %d (1 primary + %d changing)", len(r.Directives), expected, changingCount) } // Forage source should produce search-type directives. for i, d := range r.Directives { if d.Type != DirectiveSearch { t.Errorf("directive %d type %d, want DirectiveSearch for forage source", i, d.Type) } } } func TestSelfDirectiveType(t *testing.T) { o := New(42) r := o.Cast("self", 0, 1) for i, d := range r.Directives { if d.Type != DirectiveWalkWeight { t.Errorf("directive %d type %d, want DirectiveWalkWeight for self source", i, d.Type) } } }