main.go raw
1 // Command mindsicle is the bootstrapper — thaws a frozen lattice, emits Go
2 // source, compiles, and runs the result.
3 //
4 // Usage:
5 //
6 // mindsicle frozen.json thaw → emit → repair → compile → run
7 // mindsicle -emit frozen.json thaw → emit only (no compile/run)
8 // mindsicle -out DIR frozen.json set output directory
9 // mindsicle -auto -repo . autonomous mode: iterate until walk exhausts repo
10 package main
11
12 import (
13 "bytes"
14 "context"
15 "flag"
16 "fmt"
17 "os"
18 "os/exec"
19 "os/signal"
20 "path/filepath"
21 "strings"
22 "time"
23
24 "git.mleku.dev/mleku/dendrite/pkg/axiom"
25 "git.mleku.dev/mleku/dendrite/pkg/emit"
26 "git.mleku.dev/mleku/dendrite/pkg/enzyme"
27 "git.mleku.dev/mleku/dendrite/pkg/ewma"
28 "git.mleku.dev/mleku/dendrite/pkg/fitness"
29 "git.mleku.dev/mleku/dendrite/pkg/grow"
30 "git.mleku.dev/mleku/dendrite/pkg/hexagram"
31 "git.mleku.dev/mleku/dendrite/pkg/lattice"
32 "git.mleku.dev/mleku/dendrite/pkg/memory"
33 "git.mleku.dev/mleku/dendrite/pkg/mindsicle"
34 "git.mleku.dev/mleku/dendrite/pkg/ratio"
35 "git.mleku.dev/mleku/dendrite/pkg/spore"
36 "git.mleku.dev/mleku/dendrite/pkg/walk"
37 )
38
39 // tagConstraint is the simplest constraint: admits elements with matching type.
40 type tagConstraint struct{ tag string }
41
42 func (c tagConstraint) Tag() string { return c.tag }
43 func (c tagConstraint) Admits(e axiom.Element) bool { return e.Type() == c.tag }
44
45 func constraintFactory(tag string) axiom.Constraint { return tagConstraint{tag} }
46
47 var goRoot string
48
49 func init() {
50 goRoot = os.Getenv("GOROOT")
51 if goRoot == "" {
52 goRoot = "/home/mleku/go"
53 }
54 }
55
56 func main() {
57 // Shared flags.
58 outDir := flag.String("out", "_output", "output directory")
59
60 // Bootstrap mode flags.
61 emitOnly := flag.Bool("emit", false, "emit Go source without compiling or running")
62 maxPasses := flag.Int("passes", 5, "max compile-repair passes")
63
64 // Autonomous mode flags.
65 autoMode := flag.Bool("auto", false, "autonomous mode: iterate until ergodic walk exhausts repo")
66 repoDir := flag.String("repo", ".", "repository root for ergodic walk")
67 ewmaWindow := flag.Int("ewma-window", 10, "EWMA smoothing window")
68 crossings := flag.Int("crossings", 6, "reversal threshold for oscillation detection")
69 seed := flag.Uint64("seed", 0, "PRNG seed for ergodic walk (0 = time-based)")
70 memDir := flag.String("memory-dir", "", "persistent memory database (default: <out>/memory)")
71 feedPct := flag.Int("feed-pct", 50, "feed-back threshold percentage")
72 maxEpochs := flag.Int("max-epochs", 0, "maximum epochs (0 = unlimited)")
73
74 flag.Parse()
75
76 if *autoMode {
77 runAuto(*repoDir, *outDir, *memDir, *ewmaWindow, *crossings, *seed, *feedPct, *maxEpochs)
78 return
79 }
80
81 // --- Bootstrap mode ---
82 args := flag.Args()
83 if len(args) < 1 {
84 fmt.Fprintf(os.Stderr, "usage: mindsicle [flags] frozen.json\n")
85 fmt.Fprintf(os.Stderr, " mindsicle -auto -repo .\n")
86 os.Exit(1)
87 }
88 runBootstrap(args[0], *outDir, *emitOnly, *maxPasses)
89 }
90
91 func runBootstrap(inputFile, outDir string, emitOnly bool, maxPasses int) {
92 // Read the mindsicle.
93 f, err := os.Open(inputFile)
94 if err != nil {
95 fmt.Fprintf(os.Stderr, "open: %v\n", err)
96 os.Exit(1)
97 }
98 m, err := mindsicle.ReadMindsicle(f)
99 f.Close()
100 if err != nil {
101 fmt.Fprintf(os.Stderr, "read mindsicle: %v\n", err)
102 os.Exit(1)
103 }
104 fmt.Printf("mindsicle: %d nodes, version %d, frozen at %s\n",
105 len(m.Nodes), m.Version, m.FrozenAt.Format("2006-01-02 15:04:05"))
106
107 // Thaw.
108 l := m.Thaw(constraintFactory)
109 fmt.Printf("thaw: %d nodes live\n", l.Size())
110
111 // Harvest and emit.
112 files := emit.Harvest(l)
113 var allFrags []emit.Fragment
114 for _, frags := range files {
115 allFrags = append(allFrags, frags...)
116 }
117 fmt.Printf("harvest: %d fragments\n", len(allFrags))
118
119 var source strings.Builder
120 if err := emit.EmitGo(allFrags, &source); err != nil {
121 fmt.Fprintf(os.Stderr, "emit: %v\n", err)
122 os.Exit(1)
123 }
124
125 os.MkdirAll(outDir, 0o755)
126
127 base := filepath.Base(inputFile)
128 base = strings.TrimSuffix(base, filepath.Ext(base))
129 goFile := filepath.Join(outDir, base+".go")
130
131 if emitOnly {
132 if err := os.WriteFile(goFile, []byte(source.String()), 0o644); err != nil {
133 fmt.Fprintf(os.Stderr, "write: %v\n", err)
134 os.Exit(1)
135 }
136 fmt.Printf("emit: %s (%d bytes)\n", goFile, source.Len())
137 return
138 }
139
140 repaired, err := emit.CompileAndRepair(source.String(), goRoot, maxPasses)
141 if err != nil {
142 fmt.Fprintf(os.Stderr, "repair: %v\n", err)
143 os.WriteFile(goFile, []byte(source.String()), 0o644)
144 fmt.Fprintf(os.Stderr, "unrepaired source written to %s\n", goFile)
145 os.Exit(1)
146 }
147
148 if err := os.WriteFile(goFile, []byte(repaired), 0o644); err != nil {
149 fmt.Fprintf(os.Stderr, "write: %v\n", err)
150 os.Exit(1)
151 }
152 fmt.Printf("repair: %s (%d bytes)\n", goFile, len(repaired))
153
154 binFile := filepath.Join(outDir, base)
155 if err := fitness.CompileTo(goFile, binFile, goRoot); err != nil {
156 fmt.Fprintf(os.Stderr, "compile: %v\n", err)
157 os.Exit(1)
158 }
159 fmt.Printf("compile: %s\n", binFile)
160
161 fmt.Println("--- running offspring ---")
162 cmd := exec.Command(binFile)
163 cmd.Stdout = os.Stdout
164 cmd.Stderr = os.Stderr
165 if err := cmd.Run(); err != nil {
166 fmt.Fprintf(os.Stderr, "run: %v\n", err)
167 os.Exit(1)
168 }
169 fmt.Println("--- offspring done ---")
170 }
171
172 // runAuto implements the autonomous driver loop.
173 func runAuto(repoDir, outDir, memDir string, ewmaWindow, crossingThreshold int, prngSeed uint64, feedPct, maxEpochs int) {
174 if memDir == "" {
175 memDir = filepath.Join(outDir, "memory")
176 }
177 os.MkdirAll(outDir, 0o755)
178 os.MkdirAll(memDir, 0o755)
179
180 // Open persistent memory.
181 mem, err := memory.Open(memDir)
182 if err != nil {
183 fmt.Fprintf(os.Stderr, "memory open: %v\n", err)
184 os.Exit(1)
185 }
186 defer mem.Close()
187
188 // PRNG seed.
189 if prngSeed == 0 {
190 prngSeed = uint64(time.Now().UnixNano())
191 }
192 fmt.Printf("seed: %d\n", prngSeed)
193
194 // Try resuming walker state from checkpoint.
195 var walker *walk.Walker
196 var epochNum uint32
197 var genNum uint32
198 currentSeed := prngSeed
199
200 if cp, err := mem.LoadWalkerCheckpoint(); err == nil {
201 manifest := &walk.Manifest{Files: cp.Files, Root: cp.Root, Seed: cp.Seed}
202 walker = walk.Resume(manifest, cp.Position)
203 epochNum = cp.Epoch
204 genNum = cp.GenNum
205 currentSeed = cp.Seed
206 fmt.Printf("resume: epoch %d, position %d/%d, gen %d\n",
207 epochNum, cp.Position, len(cp.Files), genNum)
208 } else {
209 // Build ergodic walk manifest.
210 manifest, err := walk.Build(repoDir, prngSeed, walk.DefaultExclude)
211 if err != nil {
212 fmt.Fprintf(os.Stderr, "walk build: %v\n", err)
213 os.Exit(1)
214 }
215 fmt.Printf("manifest: %d files in %s\n", len(manifest.Files), repoDir)
216 if len(manifest.Files) == 0 {
217 fmt.Fprintf(os.Stderr, "no source files found\n")
218 os.Exit(1)
219 }
220 walker = walk.NewWalker(manifest)
221 }
222
223 // Try to resume lattice state.
224 var l *lattice.Lattice
225
226 // Check for existing mindsicle in args or memory.
227 args := flag.Args()
228 if len(args) > 0 {
229 // Thaw from provided mindsicle file.
230 f, err := os.Open(args[0])
231 if err != nil {
232 fmt.Fprintf(os.Stderr, "open mindsicle: %v\n", err)
233 os.Exit(1)
234 }
235 m, err := mindsicle.ReadMindsicle(f)
236 f.Close()
237 if err != nil {
238 fmt.Fprintf(os.Stderr, "read mindsicle: %v\n", err)
239 os.Exit(1)
240 }
241 l = m.Thaw(constraintFactory)
242 fmt.Printf("thaw: %d nodes from %s\n", l.Size(), args[0])
243 } else {
244 // Try loading latest mindsicle from memory.
245 gen, data, err := mem.LatestMindsicle()
246 if err == nil && len(data) > 0 {
247 m, err := mindsicle.ReadMindsicle(bytes.NewReader(data))
248 if err == nil {
249 l = m.Thaw(constraintFactory)
250 fmt.Printf("thaw: %d nodes from memory gen %d\n", l.Size(), gen)
251 }
252 }
253 }
254
255 if l == nil {
256 // Abiogenesis — start with empty lattice with code-aware sites.
257 l = abiogenesis()
258 fmt.Printf("abiogenesis: %d nodes\n", l.Size())
259 }
260
261 // Create oscillation detector.
262 detector := ewma.NewDetector(ewmaWindow, 0, crossingThreshold)
263
264 // Try restoring detector state.
265 _, ewmaData, err := mem.LoadLatestEWMAState()
266 if err == nil && len(ewmaData) > 0 {
267 restored, err := ewma.UnmarshalDetector(ewmaData)
268 if err == nil {
269 detector = restored
270 fmt.Printf("ewma: restored detector state\n")
271 }
272 }
273
274 // Set up signal handler for graceful shutdown.
275 sigCh := make(chan os.Signal, 1)
276 signal.Notify(sigCh, os.Interrupt)
277
278 feedThreshold := ratio.New(int64(feedPct), 100)
279
280 // Feed first file and track which file is being processed.
281 solution := make(chan axiom.Element, 1024)
282 currentFile := feedFile(walker, solution)
283
284 // Stability-triggered feeding state.
285 const stabilityCooldown uint32 = 5
286 var lastStabilityFeedGen uint32
287 stabilityMinSustain := ratio.New(9, 10) // 90%
288 stabilityMaxYoung := ratio.New(5, 100) // 5%
289
290 // Per-epoch generation counter for safety limit.
291 epochStartGen := genNum
292
293 fmt.Printf("=== autonomous loop (epoch %d) ===\n", epochNum)
294
295 for {
296 select {
297 case <-sigCh:
298 fmt.Println("\ninterrupt — freezing state...")
299 freezeState(l, genNum, outDir, mem, detector, walker, epochNum)
300 return
301 default:
302 }
303
304 genNum++
305 fmt.Printf("\n--- gen %d (epoch %d, walk: %s, %d remaining) ---\n",
306 genNum, epochNum, walker.Progress(), walker.Remaining())
307
308 // Run one generation. Oscillation is now endogenous — the engine
309 // breathes on its own schedule. The detector's state is used only
310 // for diagnostic logging.
311 rawCount, accretedCount, adsrDist := runAutoGeneration(l, solution, genNum, mem)
312
313 fmt.Printf("gen %d: raw=%d accreted=%d ratio=%.1f%%\n",
314 genNum, rawCount, accretedCount,
315 float64(accretedCount)*100/max64(float64(rawCount), 1))
316
317 // Record per-file accretion score.
318 if currentFile != "" && mem != nil {
319 mem.RecordFileScore(currentFile, rawCount, accretedCount)
320 }
321
322 // Feed into oscillation detector.
323 oscillating := detector.Observe(rawCount, accretedCount)
324
325 if oscillating {
326 fmt.Printf("oscillation detected (reversals=%d) — ", detector.Reversals)
327 detector.Reset()
328
329 // Check if we should feed lattice back into itself.
330 if rawCount > 0 {
331 accretedRatio := ratio.New(accretedCount, rawCount)
332 if accretedRatio.Greater(feedThreshold) || accretedRatio.Equal(feedThreshold) {
333 fmt.Printf("self-feeding (accreted %.0f%% >= %d%%)\n",
334 accretedRatio.Float64()*100, feedPct)
335 feedLatticeIntoItself(l, solution)
336 } else {
337 fmt.Println("below feed threshold")
338 }
339 }
340
341 // Feed next file — start new epoch if walk exhausted.
342 if walker.Done() {
343 epochNum++
344 if maxEpochs > 0 && epochNum >= uint32(maxEpochs) {
345 fmt.Println("max epochs reached")
346 break
347 }
348 walker, currentSeed = startNewEpoch(repoDir, currentSeed, mem)
349 fmt.Printf("\n=== epoch %d (%d files, seed=%d) ===\n",
350 epochNum, len(walker.Manifest.Files), currentSeed)
351 epochStartGen = genNum
352 detector.Reset()
353 }
354 currentFile = feedFile(walker, solution)
355 lastStabilityFeedGen = genNum // reset stability cooldown
356 } else if genNum >= lastStabilityFeedGen+stabilityCooldown {
357 // Stability-triggered feeding: material absorbed, ready for more.
358 if checkStability(adsrDist, mem, stabilityMinSustain, stabilityMaxYoung) {
359 fmt.Println("stability reached — feeding next file")
360 if walker.Done() {
361 epochNum++
362 if maxEpochs > 0 && epochNum >= uint32(maxEpochs) {
363 fmt.Println("max epochs reached")
364 break
365 }
366 walker, currentSeed = startNewEpoch(repoDir, currentSeed, mem)
367 fmt.Printf("\n=== epoch %d (%d files, seed=%d) ===\n",
368 epochNum, len(walker.Manifest.Files), currentSeed)
369 epochStartGen = genNum
370 }
371 currentFile = feedFile(walker, solution)
372 lastStabilityFeedGen = genNum
373 detector.Reset() // new material invalidates EWMA history
374 }
375 }
376
377 // Periodic freeze (every 10 generations).
378 if genNum%10 == 0 {
379 freezeState(l, genNum, outDir, mem, detector, walker, epochNum)
380 }
381
382 // Per-epoch safety limit: force epoch transition if stuck.
383 if walker.Done() && !oscillating {
384 if genNum-epochStartGen > uint32(len(walker.Manifest.Files)*20) {
385 epochNum++
386 if maxEpochs > 0 && epochNum >= uint32(maxEpochs) {
387 fmt.Println("max epochs reached (safety limit)")
388 break
389 }
390 fmt.Println("epoch safety limit — starting new epoch")
391 walker, currentSeed = startNewEpoch(repoDir, currentSeed, mem)
392 fmt.Printf("\n=== epoch %d (%d files, seed=%d) ===\n",
393 epochNum, len(walker.Manifest.Files), currentSeed)
394 epochStartGen = genNum
395 detector.Reset()
396 currentFile = feedFile(walker, solution)
397 lastStabilityFeedGen = genNum
398 }
399 }
400 }
401
402 // Final freeze.
403 fmt.Println("\n=== final freeze ===")
404 freezeState(l, genNum, outDir, mem, detector, walker, epochNum)
405 fmt.Printf("autonomous run complete: %d generations, %d epochs, %d/%d files in current epoch\n",
406 genNum, epochNum+1, walker.Position, len(walker.Manifest.Files))
407 }
408
409 // abiogenesis creates an initial lattice with code-aware constraint sites.
410 func abiogenesis() *lattice.Lattice {
411 l := lattice.New()
412 codeTags := map[string]int{
413 "literal": 16, "ident": 12, "func": 12, "method": 12, "type": 12,
414 "field": 8, "import": 8, "package": 8,
415 "struct": 6, "interface": 6, "comment": 6, "file": 6,
416 "assign": 6, "return": 6, "if": 6, "for": 6,
417 "word": 16, "punct": 4,
418 "select": 4, "switch": 4, "go": 4, "send": 4,
419 "expr": 6, "defer": 4, "decl": 4, "branch": 3, "case": 4, "comm": 3,
420 "directive": 3, "var": 6,
421 }
422
423 var allNodes []*lattice.Node
424 for tag, count := range codeTags {
425 for range count {
426 n := l.AddNode([]axiom.Constraint{tagConstraint{tag}})
427 n.SetEnergy(true)
428 allNodes = append(allNodes, n)
429 }
430 }
431
432 // Connect in a ring with cross-links.
433 for i, n := range allNodes {
434 l.Connect(n, allNodes[(i+1)%len(allNodes)])
435 if i%4 == 0 && i+7 < len(allNodes) {
436 l.Connect(n, allNodes[i+7])
437 }
438 }
439
440 return l
441 }
442
443 // runAutoGeneration runs one generation on the live lattice.
444 // Returns raw element count and accreted (bonded) count.
445 // oscillating indicates whether the EWMA detector sees sustained reversals;
446 // when true, the hexagram engine halves the sustain threshold so more
447 // Sustain-phase nodes destabilize into Release.
448 func runAutoGeneration(l *lattice.Lattice, solution chan axiom.Element, genNum uint32, mem *memory.DB) (rawCount, accretedCount int64, adsrDist [4]int) {
449 growDuration := 3 * time.Second
450 engineDuration := 2 * time.Second
451
452 // === GROWTH ===
453 ctx, cancel := context.WithTimeout(context.Background(), growDuration)
454 defer cancel()
455
456 growEvents := make(chan grow.Event, 512)
457 go func() {
458 grow.Run(ctx, l, solution, grow.Config{
459 MaxSteps: 500,
460 Workers: 4,
461 }, growEvents)
462 close(growEvents)
463 }()
464
465 var bondRecords []memory.BondRecord
466 for ev := range growEvents {
467 rawCount++ // every event is one element attempt
468 switch ev.Type {
469 case grow.EventBonded:
470 accretedCount++
471 if mem != nil && ev.Element != nil {
472 bondRecords = append(bondRecords, memory.BondRecord{
473 Tag: ev.Element.Type(),
474 SiteID: uint32(ev.NodeID),
475 })
476 }
477 }
478 }
479
480 // === SELF-GOVERNANCE ===
481 engineCtx, engineCancel := context.WithTimeout(context.Background(), engineDuration)
482 defer engineCancel()
483
484 engineEvents := make(chan hexagram.Event, 512)
485 go func() {
486 hexagram.RunEngine(engineCtx, l, hexagram.EngineConfig{
487 Interval: 16 * time.Millisecond, // 2^4 ms — epoch-aligns with dissolve at 10^2 ms
488 Solution: solution,
489 MaxNewSites: 32,
490 MinOccupancy: ratio.New(2, 5),
491 SustainThreshold: ratio.New(4, 10),
492 Oscillating: false,
493 }, engineEvents)
494 close(engineEvents)
495 }()
496
497 opCounts := make(map[hexagram.Op]int)
498 for ev := range engineEvents {
499 opCounts[ev.Op]++
500 if ev.Op == hexagram.OpAccrete {
501 accretedCount++
502 }
503 }
504
505 // Report engine ops.
506 opNames := map[hexagram.Op]string{
507 hexagram.OpNone: "none", hexagram.OpAccrete: "accrete",
508 hexagram.OpDissolve: "dissolve", hexagram.OpNucleate: "nucleate",
509 hexagram.OpPrune: "prune", hexagram.OpStrengthen: "strengthen",
510 hexagram.OpExplore: "explore", hexagram.OpCollapse: "collapse",
511 hexagram.OpRecycle: "recycle",
512 }
513 fmt.Print(" engine: ")
514 for op, count := range opCounts {
515 name := opNames[op]
516 if name == "" {
517 name = fmt.Sprintf("op_%d", op)
518 }
519 fmt.Printf("%s=%d ", name, count)
520 }
521 fmt.Println()
522
523 // Record to memory.
524 if mem != nil {
525 mem.RecordBonds(genNum, bondRecords)
526 hexOps := make(map[byte]uint32, len(opCounts))
527 for op, count := range opCounts {
528 hexOps[byte(op)] = uint32(count)
529 }
530 mem.RecordHexagramOps(genNum, hexOps)
531
532 // Health snapshot.
533 occupied := 0
534 total := l.Size()
535 for i := range total {
536 if l.Node(lattice.NodeID(i)).Occupied() {
537 occupied++
538 }
539 }
540 mem.RecordHealth(genNum, uint32(occupied), uint32(total), ratio.Zero)
541 }
542
543 // Report ADSR phase distribution.
544 total := l.Size()
545 for i := range total {
546 n := l.Node(lattice.NodeID(i))
547 if n.Occupied() {
548 age := n.Age()
549 if age < 4 {
550 adsrDist[age]++
551 }
552 }
553 }
554 fmt.Printf(" adsr: A=%d D=%d S=%d R=%d\n",
555 adsrDist[0], adsrDist[1], adsrDist[2], adsrDist[3])
556
557 // Record ADSR to memory.
558 if mem != nil {
559 var u32 [4]uint32
560 for i := range 4 {
561 u32[i] = uint32(adsrDist[i])
562 }
563 mem.RecordADSR(genNum, u32)
564 }
565
566 // Sporulate.
567 sp := spore.Extract(l)
568 if sp != nil {
569 fmt.Printf(" spore: occupied=%d/%d types=%d\n",
570 sp.Occupied, sp.TotalNodes, len(sp.TypeSignature))
571 }
572
573 return rawCount, accretedCount, adsrDist
574 }
575
576 // feedFile opens the next file from the walker and feeds its elements
577 // into the solution channel. Returns the relative path of the file fed,
578 // or "" if the walker is exhausted.
579 func feedFile(walker *walk.Walker, solution chan axiom.Element) string {
580 ch, ok := walker.DigestNext()
581 if !ok {
582 return ""
583 }
584 var filePath string
585 if walker.Position > 0 && walker.Position <= len(walker.Manifest.Files) {
586 filePath = walker.Manifest.Files[walker.Position-1]
587 }
588 go func() {
589 for elem := range ch {
590 solution <- elem
591 }
592 }()
593 if filePath != "" {
594 fmt.Printf(" feed: %s\n", filePath)
595 }
596 return filePath
597 }
598
599 // feedLatticeIntoItself harvests occupied elements from the lattice,
600 // decomposes their string values through Text enzyme, and feeds
601 // the resulting tokens back into the solution channel.
602 func feedLatticeIntoItself(l *lattice.Lattice, solution chan axiom.Element) {
603 var texts []string
604 for i := range l.Size() {
605 n := l.Node(lattice.NodeID(i))
606 if !n.Occupied() {
607 continue
608 }
609 val := n.Occupant().Value()
610 if s, ok := val.(string); ok && len(s) > 0 {
611 texts = append(texts, s)
612 }
613 }
614
615 if len(texts) == 0 {
616 return
617 }
618
619 combined := strings.Join(texts, " ")
620 fmt.Printf(" self-feed: %d elements, %d bytes\n", len(texts), len(combined))
621
622 go func() {
623 ch := enzyme.Text{}.Digest(strings.NewReader(combined))
624 for elem := range ch {
625 if elem.Type() == "space" {
626 continue
627 }
628 solution <- elem
629 }
630 }()
631 }
632
633 // startNewEpoch re-scans the repository with a new seed and weighted
634 // permutation based on historical file accretion scores.
635 func startNewEpoch(repoDir string, prevSeed uint64, mem *memory.DB) (*walk.Walker, uint64) {
636 newSeed := walk.DeriveNextSeed(prevSeed)
637
638 scoresByHash, _ := mem.LoadFileScores()
639 if len(scoresByHash) > 0 {
640 // Do a plain scan first to get the file list for weight computation.
641 plain, err := walk.Build(repoDir, newSeed, walk.DefaultExclude)
642 if err == nil && len(plain.Files) > 0 {
643 weights := computeFileWeights(plain.Files, scoresByHash)
644 weighted, err := walk.BuildWeighted(repoDir, newSeed, walk.DefaultExclude, weights, 2.0)
645 if err == nil {
646 return walk.NewWalker(weighted), newSeed
647 }
648 }
649 }
650
651 // Fallback: uniform shuffle.
652 manifest, _ := walk.Build(repoDir, newSeed, walk.DefaultExclude)
653 return walk.NewWalker(manifest), newSeed
654 }
655
656 // computeFileWeights converts per-file accretion scores into weights for
657 // the Efraimidis-Spirakis weighted permutation.
658 // Weight = 2.0 - accretionRate, range [1.0, 2.0].
659 // Files with no history get 2.0 (maximum priority).
660 func computeFileWeights(files []string, scoresByHash map[[8]byte][2]int64) map[string]float64 {
661 weights := make(map[string]float64, len(files))
662 for _, f := range files {
663 h := memory.TagHash(f)
664 scores, ok := scoresByHash[h]
665 if !ok || scores[1] == 0 {
666 weights[f] = 2.0
667 continue
668 }
669 rate := float64(scores[0]) / float64(scores[1])
670 if rate > 1.0 {
671 rate = 1.0
672 }
673 if rate < 0.0 {
674 rate = 0.0
675 }
676 weights[f] = 2.0 - rate
677 }
678 return weights
679 }
680
681 // checkStability returns true when the lattice has fully absorbed its current
682 // material: high Sustain fraction, low young (Attack+Decay) fraction, and
683 // neither occupancy nor fitness still rising.
684 func checkStability(adsrDist [4]int, mem *memory.DB,
685 minSustain, maxYoung ratio.Ratio) bool {
686
687 occupied := adsrDist[0] + adsrDist[1] + adsrDist[2] + adsrDist[3]
688 if occupied == 0 {
689 return false
690 }
691
692 sustainFrac := ratio.New(int64(adsrDist[2]), int64(occupied))
693 youngFrac := ratio.New(int64(adsrDist[0]+adsrDist[1]), int64(occupied))
694
695 if sustainFrac.Less(minSustain) {
696 return false
697 }
698 if maxYoung.Less(youngFrac) {
699 return false
700 }
701
702 // Check cross-generational trends from memory.
703 if mem == nil {
704 return true
705 }
706 digest := mem.WalkDigest(nil, 5)
707 if digest == nil {
708 return true // <2 gens of data — trust ADSR alone
709 }
710 if digest.OccupancyTrend == memory.TrendRising {
711 return false
712 }
713 if digest.FitnessTrend == memory.TrendRising {
714 return false
715 }
716
717 return true
718 }
719
720 // freezeState persists the current lattice, detector, and walker state.
721 func freezeState(l *lattice.Lattice, genNum uint32, outDir string, mem *memory.DB,
722 detector *ewma.OscillationDetector, walker *walk.Walker, epochNum uint32) {
723
724 sp := spore.Extract(l)
725 m := mindsicle.Freeze(l, sp)
726
727 // Write to file.
728 fileName := filepath.Join(outDir, fmt.Sprintf("dendrite.gen%d.mindsicle", genNum))
729 var buf bytes.Buffer
730 m.WriteTo(&buf)
731 os.WriteFile(fileName, buf.Bytes(), 0o644)
732 fmt.Printf(" freeze: %s (%d bytes)\n", fileName, buf.Len())
733
734 // Write to memory DB.
735 if mem != nil {
736 var mbuf bytes.Buffer
737 m.WriteTo(&mbuf)
738 mem.RecordMindsicle(genNum, mbuf.Bytes())
739
740 // Persist detector state.
741 detectorData, err := detector.Marshal()
742 if err == nil {
743 mem.RecordEWMAState(genNum, detectorData)
744 }
745
746 // Persist walker checkpoint for exact resume.
747 mem.RecordWalkerCheckpoint(memory.WalkerCheckpoint{
748 Epoch: epochNum,
749 Seed: walker.Manifest.Seed,
750 Position: walker.Position,
751 GenNum: genNum,
752 Files: walker.Manifest.Files,
753 Root: walker.Manifest.Root,
754 })
755 }
756 }
757
758 func max64(a, b float64) float64 {
759 if a > b {
760 return a
761 }
762 return b
763 }
764