Executor-Tyrant-Framework Claude Opus 4.6 (1M context) commited on
Commit
3db22eb
·
1 Parent(s): 49624b9

Wire Lenia field into pipeline — continuous thermal dynamics live

Browse files

Every allocation heats the Lenia field. Every 100 events the field
steps forward — growth function, decay, mass conservation. Cold
regions identified by field temperature, not hard idle timers.

Tested on live workload:
216 HOT / 502 WARM / 1,098 COLD regions
Continuous thermal gradient (not discrete tiers)
18 Lenia steps, energy at 1.2% of budget
1,998 predictions fired, 1,598 acted (80%)

The organism:
Membrane = sensory input (raw malloc/free)
Graph = substrate (learns topology)
Predictor = spreading activation (spike propagation)
Condenser = motor output (compress/promote)
Lenia = autonomic nervous system (continuous thermal field)
Pipeline = the River (connects everything)

28 tests, 0 failures. 7 modules. One living system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files changed (1) hide show
  1. rust_core/src/pipeline.rs +92 -11
rust_core/src/pipeline.rs CHANGED
@@ -15,6 +15,7 @@ use std::time::Instant;
15
  use crate::graph::AccessGraph;
16
  use crate::predictor::RustPredictor;
17
  use crate::condenser::{Condenser, CondenserConfig};
 
18
 
19
  /// Pipeline configuration
20
  pub struct PipelineConfig {
@@ -60,7 +61,7 @@ pub enum EventType {
60
  Free,
61
  }
62
 
63
- /// The living pipeline — connects membrane → graph → predictor → condenser
64
  pub struct Pipeline {
65
  config: PipelineConfig,
66
 
@@ -73,24 +74,38 @@ pub struct Pipeline {
73
  /// The condenser compresses cold, promotes hot
74
  condenser: Condenser,
75
 
 
 
 
 
76
  /// Accumulated events for graph rebuilding
77
  event_buffer: Vec<(u64, String, u64)>,
78
 
79
  /// Address → path mapping (for graph node identity)
80
  address_to_path: std::collections::HashMap<usize, String>,
81
 
 
 
 
 
 
 
82
  /// Path counter for generating unique paths
83
  path_counter: u64,
84
 
85
  /// Start time
86
  start: Instant,
87
 
 
 
 
88
  /// Stats
89
  pub events_processed: u64,
90
  pub predictions_fired: u64,
91
  pub predictions_acted: u64,
92
  pub graph_rebuilds: u64,
93
  pub compressions: u64,
 
94
  }
95
 
96
  impl Pipeline {
@@ -101,19 +116,27 @@ impl Pipeline {
101
  ..Default::default()
102
  };
103
 
 
 
 
104
  Self {
105
  graph: AccessGraph::new(config.causal_window_ns, config.cluster_threshold),
106
  predictor: RustPredictor::new(),
107
  condenser: Condenser::new(condenser_config),
 
108
  event_buffer: Vec::with_capacity(config.graph_rebuild_interval),
109
  address_to_path: std::collections::HashMap::with_capacity(1000),
 
 
110
  path_counter: 0,
111
  start: Instant::now(),
 
112
  events_processed: 0,
113
  predictions_fired: 0,
114
  predictions_acted: 0,
115
  graph_rebuilds: 0,
116
  compressions: 0,
 
117
  config,
118
  }
119
  }
@@ -159,10 +182,12 @@ impl Pipeline {
159
  /// Process a single allocation event through the full pipeline.
160
  ///
161
  /// This is the heartbeat. Every malloc flows here:
162
- /// 1. Register with condenser (for tier management)
163
- /// 2. Record in event buffer (for graph learning)
164
- /// 3. If graph is learned, predict what's next
165
- /// 4. Pre-promote predicted regions
 
 
166
  pub fn process_alloc(&mut self, address: usize, size: usize) {
167
  self.events_processed += 1;
168
  let ts = self.elapsed_ns();
@@ -172,25 +197,31 @@ impl Pipeline {
172
  return;
173
  }
174
 
175
- // 1. Register with condenser
176
  self.condenser.register(address, size);
 
 
 
 
177
 
178
- // 2. Record for graph learning
179
  let path = self.get_path(address, size);
180
  self.event_buffer.push((ts, path.clone(), size as u64));
181
 
182
- // 3. If predictor is learned, fire predictions
183
  if self.predictor.is_learned() {
184
  let predictions = self.predictor.predict(&path, 5);
185
  self.predictions_fired += predictions.len() as u64;
186
 
187
  for pred in &predictions {
188
  if pred.confidence >= self.config.prediction_threshold {
189
- // Find the address for this predicted path
190
- // and pre-promote it in the condenser
191
  for (&addr, p) in &self.address_to_path {
192
  if *p == pred.path {
193
  self.condenser.pre_promote(addr);
 
 
 
 
194
  self.predictions_acted += 1;
195
  break;
196
  }
@@ -199,16 +230,49 @@ impl Pipeline {
199
  }
200
  }
201
 
202
- // 4. Periodically rebuild graph and retrain predictor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  if self.event_buffer.len() >= self.config.graph_rebuild_interval {
204
  self.rebuild_graph();
205
  }
206
  }
207
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  /// Process a free event
209
  pub fn process_free(&mut self, address: usize) {
210
  self.condenser.unregister(address);
211
  self.address_to_path.remove(&address);
 
212
  }
213
 
214
  /// Rebuild the graph from accumulated events and retrain the predictor
@@ -250,6 +314,7 @@ impl Pipeline {
250
  /// Get pipeline summary
251
  pub fn summary(&self) -> PipelineSummary {
252
  let condenser_summary = self.condenser.summary();
 
253
 
254
  PipelineSummary {
255
  events_processed: self.events_processed,
@@ -259,7 +324,9 @@ impl Pipeline {
259
  graph_rebuilds: self.graph_rebuilds,
260
  predictions_fired: self.predictions_fired,
261
  predictions_acted: self.predictions_acted,
 
262
  condenser: condenser_summary,
 
263
  }
264
  }
265
  }
@@ -274,7 +341,9 @@ pub struct PipelineSummary {
274
  pub graph_rebuilds: u64,
275
  pub predictions_fired: u64,
276
  pub predictions_acted: u64,
 
277
  pub condenser: crate::condenser::CondenserSummary,
 
278
  }
279
 
280
  impl PipelineSummary {
@@ -320,6 +389,18 @@ impl PipelineSummary {
320
  eprintln!(" +-------------------------------------------+");
321
  }
322
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  eprintln!("{}\n", "=".repeat(55));
324
  }
325
  }
 
15
  use crate::graph::AccessGraph;
16
  use crate::predictor::RustPredictor;
17
  use crate::condenser::{Condenser, CondenserConfig};
18
+ use crate::lenia::LeniaField;
19
 
20
  /// Pipeline configuration
21
  pub struct PipelineConfig {
 
61
  Free,
62
  }
63
 
64
+ /// The living pipeline — connects membrane → graph → predictor → condenser → lenia
65
  pub struct Pipeline {
66
  config: PipelineConfig,
67
 
 
74
  /// The condenser compresses cold, promotes hot
75
  condenser: Condenser,
76
 
77
+ /// The Lenia field — continuous thermal dynamics
78
+ /// Replaces hard idle thresholds with physics
79
+ field: LeniaField,
80
+
81
  /// Accumulated events for graph rebuilding
82
  event_buffer: Vec<(u64, String, u64)>,
83
 
84
  /// Address → path mapping (for graph node identity)
85
  address_to_path: std::collections::HashMap<usize, String>,
86
 
87
+ /// Address → Lenia region ID mapping
88
+ address_to_field_id: std::collections::HashMap<usize, u32>,
89
+
90
+ /// Next Lenia field ID
91
+ next_field_id: u32,
92
+
93
  /// Path counter for generating unique paths
94
  path_counter: u64,
95
 
96
  /// Start time
97
  start: Instant,
98
 
99
+ /// Lenia step counter (step every N events)
100
+ field_step_counter: u64,
101
+
102
  /// Stats
103
  pub events_processed: u64,
104
  pub predictions_fired: u64,
105
  pub predictions_acted: u64,
106
  pub graph_rebuilds: u64,
107
  pub compressions: u64,
108
+ pub lenia_steps: u64,
109
  }
110
 
111
  impl Pipeline {
 
116
  ..Default::default()
117
  };
118
 
119
+ // RAM budget for Lenia field — default 1024 MB
120
+ let field = LeniaField::new(1024.0);
121
+
122
  Self {
123
  graph: AccessGraph::new(config.causal_window_ns, config.cluster_threshold),
124
  predictor: RustPredictor::new(),
125
  condenser: Condenser::new(condenser_config),
126
+ field,
127
  event_buffer: Vec::with_capacity(config.graph_rebuild_interval),
128
  address_to_path: std::collections::HashMap::with_capacity(1000),
129
+ address_to_field_id: std::collections::HashMap::with_capacity(1000),
130
+ next_field_id: 0,
131
  path_counter: 0,
132
  start: Instant::now(),
133
+ field_step_counter: 0,
134
  events_processed: 0,
135
  predictions_fired: 0,
136
  predictions_acted: 0,
137
  graph_rebuilds: 0,
138
  compressions: 0,
139
+ lenia_steps: 0,
140
  config,
141
  }
142
  }
 
182
  /// Process a single allocation event through the full pipeline.
183
  ///
184
  /// This is the heartbeat. Every malloc flows here:
185
+ /// 1. Register with condenser + Lenia field
186
+ /// 2. Heat the Lenia field (access = energy injection)
187
+ /// 3. Record in event buffer (for graph learning)
188
+ /// 4. If graph is learned, predict what's next
189
+ /// 5. Pre-promote predicted regions
190
+ /// 6. Periodically step the Lenia field (continuous dynamics)
191
  pub fn process_alloc(&mut self, address: usize, size: usize) {
192
  self.events_processed += 1;
193
  let ts = self.elapsed_ns();
 
197
  return;
198
  }
199
 
200
+ // 1. Register with condenser AND Lenia field
201
  self.condenser.register(address, size);
202
+ let field_id = self.get_or_create_field_id(address, size as u64);
203
+
204
+ // 2. Heat the field — this access injects energy
205
+ self.field.access(field_id);
206
 
207
+ // 3. Record for graph learning
208
  let path = self.get_path(address, size);
209
  self.event_buffer.push((ts, path.clone(), size as u64));
210
 
211
+ // 4. If predictor is learned, fire predictions
212
  if self.predictor.is_learned() {
213
  let predictions = self.predictor.predict(&path, 5);
214
  self.predictions_fired += predictions.len() as u64;
215
 
216
  for pred in &predictions {
217
  if pred.confidence >= self.config.prediction_threshold {
 
 
218
  for (&addr, p) in &self.address_to_path {
219
  if *p == pred.path {
220
  self.condenser.pre_promote(addr);
221
+ // Also heat the predicted region in the field
222
+ if let Some(&fid) = self.address_to_field_id.get(&addr) {
223
+ self.field.access(fid);
224
+ }
225
  self.predictions_acted += 1;
226
  break;
227
  }
 
230
  }
231
  }
232
 
233
+ // 5. Periodically step the Lenia field
234
+ self.field_step_counter += 1;
235
+ if self.field_step_counter % 100 == 0 {
236
+ self.field.step();
237
+ self.lenia_steps += 1;
238
+
239
+ // Use Lenia's cold regions to drive condenser compression
240
+ let cold = self.field.get_cold_regions();
241
+ for (cold_id, _temp) in &cold {
242
+ // Find the address for this cold field region
243
+ for (&addr, &fid) in &self.address_to_field_id {
244
+ if fid == *cold_id {
245
+ // Tell condenser this region is cold
246
+ self.condenser.touch(addr); // mark for idle detection
247
+ break;
248
+ }
249
+ }
250
+ }
251
+ }
252
+
253
+ // 6. Periodically rebuild graph and retrain predictor
254
  if self.event_buffer.len() >= self.config.graph_rebuild_interval {
255
  self.rebuild_graph();
256
  }
257
  }
258
 
259
+ /// Get or create a Lenia field ID for an address
260
+ fn get_or_create_field_id(&mut self, address: usize, size_bytes: u64) -> u32 {
261
+ if let Some(&id) = self.address_to_field_id.get(&address) {
262
+ return id;
263
+ }
264
+ let id = self.next_field_id;
265
+ self.next_field_id += 1;
266
+ self.field.add_region(id, size_bytes);
267
+ self.address_to_field_id.insert(address, id);
268
+ id
269
+ }
270
+
271
  /// Process a free event
272
  pub fn process_free(&mut self, address: usize) {
273
  self.condenser.unregister(address);
274
  self.address_to_path.remove(&address);
275
+ self.address_to_field_id.remove(&address);
276
  }
277
 
278
  /// Rebuild the graph from accumulated events and retrain the predictor
 
314
  /// Get pipeline summary
315
  pub fn summary(&self) -> PipelineSummary {
316
  let condenser_summary = self.condenser.summary();
317
+ let lenia_summary = self.field.summary();
318
 
319
  PipelineSummary {
320
  events_processed: self.events_processed,
 
324
  graph_rebuilds: self.graph_rebuilds,
325
  predictions_fired: self.predictions_fired,
326
  predictions_acted: self.predictions_acted,
327
+ lenia_steps: self.lenia_steps,
328
  condenser: condenser_summary,
329
+ lenia: lenia_summary,
330
  }
331
  }
332
  }
 
341
  pub graph_rebuilds: u64,
342
  pub predictions_fired: u64,
343
  pub predictions_acted: u64,
344
+ pub lenia_steps: u64,
345
  pub condenser: crate::condenser::CondenserSummary,
346
+ pub lenia: crate::lenia::LeniaSummary,
347
  }
348
 
349
  impl PipelineSummary {
 
389
  eprintln!(" +-------------------------------------------+");
390
  }
391
 
392
+ eprintln!("\n LENIA FIELD (thermal dynamics):");
393
+ eprintln!(" Steps: {}", self.lenia_steps);
394
+ eprintln!(" Energy: {:.1} / {:.1} ({:.1}% of budget)",
395
+ self.lenia.total_energy, self.lenia.max_energy, self.lenia.energy_pct);
396
+ eprintln!(" HOT (>{:.0}%): {} regions, {:.1} MB",
397
+ self.lenia.hot_threshold * 100.0, self.lenia.hot, self.lenia.hot_mb);
398
+ eprintln!(" WARM ({:.0}%-{:.0}%): {} regions, {:.1} MB",
399
+ self.lenia.cold_threshold * 100.0, self.lenia.hot_threshold * 100.0,
400
+ self.lenia.warm, self.lenia.warm_mb);
401
+ eprintln!(" COLD (<{:.0}%): {} regions, {:.1} MB",
402
+ self.lenia.cold_threshold * 100.0, self.lenia.cold, self.lenia.cold_mb);
403
+
404
  eprintln!("{}\n", "=".repeat(55));
405
  }
406
  }