package oracle import ( "crypto/sha256" "encoding/binary" "encoding/json" "math/rand/v2" "git.mleku.dev/mleku/dendrite/pkg/state" ) // Oracle is the I Ching oracle state machine. It maintains the chain // of readings and produces strategy directives from hexagram states. // // Each reading chains from the previous: the resulting hexagram becomes // the next primary, and 2 bits of PRNG entropy are XOR'd onto each // line to evolve the state. This creates a learning trajectory with // memory — continuity is structural, not random. type Oracle struct { // Current is the active reading being studied. Current *Reading `json:"current"` // History is the chain of all past readings (most recent last). History []*Reading `json:"history"` // Seed is the PRNG seed for line entropy generation. Seed uint64 `json:"seed"` } // New creates an oracle with no reading history (abiogenesis). func New(seed uint64) *Oracle { return &Oracle{Seed: seed} } // FromState restores an oracle from persisted JSON state. func FromState(data []byte) (*Oracle, error) { o := &Oracle{} if err := json.Unmarshal(data, o); err != nil { return nil, err } return o, nil } // Marshal serializes the oracle state for persistence. func (o *Oracle) Marshal() ([]byte, error) { return json.Marshal(o) } // Cast produces a new reading. If a previous reading exists, the // resulting hexagram becomes the primary and 2 bits of PRNG entropy // are XOR'd onto each inherited line state. If no previous reading // exists, a fresh cast is generated from the seed. // // source identifies what data source prompted this reading. // adsrPhase is the dominant ADSR phase of the learning lattice. // gen is the current dendrite generation number. func (o *Oracle) Cast(source string, adsrPhase uint8, gen uint32) *Reading { seq := uint32(0) if o.Current != nil { seq = o.Current.Sequence + 1 } r := &Reading{ Generation: gen, Sequence: seq, Source: source, } rng := deriveRNG(o.Seed, seq, source) if o.Current == nil { // Abiogenesis: full random cast. r.Primary = state.Hexagram(rng.IntN(64)) for i := range 6 { r.Lines[i] = LineState(rng.IntN(4)) } } else { // Chain: inherit resulting hexagram, XOR entropy onto lines. r.Primary = o.Current.Resulting for i := range 6 { prevState := o.Current.Lines[i] // If prev line was changing, it has already changed in the // resulting hexagram. Its new "natural" state is the post- // change value: young, since the transition completed. if prevState.IsChanging() { prevState = prevState.Stabilize() } entropy := LineState(rng.IntN(4)) r.Lines[i] = prevState ^ entropy } } // Compute resulting hexagram from changing lines. r.Resulting = ApplyChangingLines(r.Primary, r.Lines) // Generate strategy directives. r.Directives = GenerateDirectives(r, source, ADSRToStyle(adsrPhase)) // Archive the old reading and install the new one. if o.Current != nil { o.History = append(o.History, o.Current) } o.Current = r // Advance seed for next cast. o.Seed = advanceSeed(o.Seed) return r } // MarkAbsorbed marks the current reading as fully absorbed at the given // generation. Call this when the stability monitor detects oscillation. func (o *Oracle) MarkAbsorbed(gen uint32) { if o.Current != nil { o.Current.Absorbed = true o.Current.AbsorbedAtGen = gen } } // ApplyChangingLines computes the resulting hexagram by flipping all // changing lines in the primary hexagram. func ApplyChangingLines(primary state.Hexagram, lines [6]LineState) state.Hexagram { result := primary for i, ls := range lines { if ls.IsChanging() { isInner := i < 3 bit := uint8(i) if !isInner { bit = uint8(i - 3) } result = result.MoveLine(isInner, bit) } } return result } // deriveRNG creates a deterministic PRNG from the oracle seed, // sequence number, and source identifier. Same inputs always produce // the same random stream. func deriveRNG(seed uint64, seq uint32, source string) *rand.Rand { h := sha256.Sum256([]byte(source)) seedMix := seed ^ binary.BigEndian.Uint64(h[:8]) ^ uint64(seq) return rand.New(rand.NewPCG(seedMix, seedMix^0xcafebabe)) } // advanceSeed advances the seed using SplitMix64. func advanceSeed(prev uint64) uint64 { s := prev + 0x9e3779b97f4a7c15 s = (s ^ (s >> 30)) * 0xbf58476d1ce4e5b9 s = (s ^ (s >> 27)) * 0x94d049bb133111eb return s ^ (s >> 31) }