#define CATCH_CONFIG_MAIN #include "../catch.hpp" #include "src/signal_engine/signal_engine.h" #include "src/modulisp/lisp/symbol_intern.h" #include #include #include using namespace sig; namespace { struct Harness { SignalEngine engine; double outputs[MAX_OUTPUTS] = {}; double workspace[MAX_TOTAL_NODES] = {}; Harness() { engine.init_defaults(120.0, 4); set_failure_mode(FailureMode::LkgFallback); } ~Harness() { set_failure_mode(FailureMode::LkgFallback); } EvalResult eval(const std::string& code) { return eval_cold(code.c_str(), (uint32_t)code.size(), engine); } void eval_ok(const std::string& code) { EvalResult r = eval(code); INFO("code: " << code); if (r.kind == EvalResult::Error && r.diagnostic_count > 0) INFO("diagnostic: " << (r.diagnostics[0].message ? r.diagnostics[0].message : "")); REQUIRE(r.kind != EvalResult::Error); } double tick(uint16_t output_index, double t, double dt = 0.001) { double cells[MAX_CELLS]; double inputs[32] = {}; engine.cells.snapshot_values(cells, MAX_CELLS); for (double& value : outputs) value = 0.0; ExecutionContext ctx{t, dt, cells, inputs, engine.cells.data_pool, engine.cells.data_offsets, engine.cells.data_lengths, engine.pool.prev_output_values, outputs, workspace}; execute_all_outputs(engine.pool, ctx); commit_state(engine.pool, workspace); commit_outputs(engine.pool, outputs); return outputs[output_index]; } }; constexpr const char* OVERFLOW = "(a1 (* (* t 1e308) 1e308))"; } // namespace TEST_CASE("First failure has Error health until a finite root establishes LKG", "[health][output][lkg]") { Harness h; REQUIRE(output_health(h.engine.pool, 0) == OutputHealth::Idle); h.eval_ok(OVERFLOW); REQUIRE(h.engine.pool.outputs[0].valid); REQUIRE_FALSE(h.engine.pool.outputs[0].has_lkg); REQUIRE(output_health(h.engine.pool, 0) == OutputHealth::Running); REQUIRE(h.tick(0, 1.0) == Approx(0.0)); REQUIRE_FALSE(h.engine.pool.outputs[0].has_lkg); REQUIRE(output_health(h.engine.pool, 0) == OutputHealth::Error); REQUIRE(h.tick(0, 0.0) == Approx(0.0)); REQUIRE(h.engine.pool.outputs[0].has_lkg); REQUIRE(output_health(h.engine.pool, 0) == OutputHealth::Running); REQUIRE(h.tick(0, 1.0) == Approx(0.0)); REQUIRE(output_health(h.engine.pool, 0) == OutputHealth::Fallback); // Successful replacement clears the superseded program's runtime health // immediately while retaining its finite LKG as a safety net. h.eval_ok("(a1 7)"); REQUIRE(output_health(h.engine.pool, 0) == OutputHealth::Running); REQUIRE((h.engine.pool.runtime_fallback_mask & 1u) == 0); REQUIRE(h.engine.pool.outputs[0].has_lkg); } TEST_CASE("Non-finite named-state update holds value and clears its diagnostic on recovery", "[health][state][runtime]") { Harness h; auto& symbols = SymbolIntern::getInstance(); SymbolID state_symbol = symbols.intern(String("health-state")); h.eval_ok("(define health-rate 1)"); h.eval_ok("(defstate health-state 0 (/ 1 health-rate))"); uint16_t slot = h.engine.cells.cells[state_symbol].data_table_id; REQUIRE(h.engine.state_sources[slot].state_symbol == state_symbol); h.tick(0, 0.0); REQUIRE(h.engine.pool.state_values[slot] == Approx(1.0)); REQUIRE((h.engine.pool.state_update_failure_mask & ((uint64_t)1 << slot)) == 0); h.eval_ok("(define health-rate 0)"); double held = h.engine.pool.state_values[slot]; h.tick(0, 1.0); REQUIRE(h.engine.pool.state_values[slot] == Approx(held)); REQUIRE((h.engine.pool.state_update_failure_mask & ((uint64_t)1 << slot)) != 0); h.eval_ok("(define health-rate 2)"); h.tick(0, 2.0); REQUIRE(h.engine.pool.state_values[slot] == Approx(0.5)); REQUIRE((h.engine.pool.state_update_failure_mask & ((uint64_t)1 << slot)) == 0); } TEST_CASE("Rejected reactive output candidate is attributed and clears on repair", "[health][reactive][output]") { Harness h; auto& symbols = SymbolIntern::getInstance(); SymbolID dep = symbols.intern(String("health-reactive-dep")); h.eval_ok("(define health-reactive-dep 1)"); h.eval_ok("(a1 (+ health-reactive-dep 0.25))"); uint16_t old_root = h.engine.pool.outputs[0].root_node; REQUIRE(h.tick(0, 0.0) == Approx(1.25)); h.eval_ok("(defn health-reactive-dep [x] x)"); const ActiveCompileDiagnostic& active = h.engine.output_compile_diagnostics[0]; REQUIRE(active.active); REQUIRE(active.triggered_by == dep); REQUIRE(active.diagnostic.message != nullptr); REQUIRE(h.engine.pool.outputs[0].root_node == old_root); REQUIRE(h.tick(0, 1.0) == Approx(1.25)); h.eval_ok("(define health-reactive-dep 2)"); REQUIRE_FALSE(h.engine.output_compile_diagnostics[0].active); REQUIRE(h.tick(0, 2.0) == Approx(2.25)); } TEST_CASE("Rejected reactive state update is attributed and keeps its prior writer", "[health][reactive][state]") { Harness h; auto& symbols = SymbolIntern::getInstance(); SymbolID dep = symbols.intern(String("health-state-dep")); SymbolID state_symbol = symbols.intern(String("health-reactive-state")); h.eval_ok("(define health-state-dep 1)"); h.eval_ok("(defstate health-reactive-state 0 (+ health-reactive-state health-state-dep))"); uint16_t slot = h.engine.cells.cells[state_symbol].data_table_id; uint16_t old_root = h.engine.pool.state_update_roots[slot]; h.tick(0, 0.0); REQUIRE(h.engine.pool.state_values[slot] == Approx(1.0)); h.eval_ok("(defn health-state-dep [x] x)"); const ActiveCompileDiagnostic& active = h.engine.state_compile_diagnostics[slot]; REQUIRE(active.active); REQUIRE(active.triggered_by == dep); REQUIRE(active.diagnostic.message != nullptr); REQUIRE(h.engine.pool.state_update_roots[slot] == old_root); h.tick(0, 1.0); REQUIRE(h.engine.pool.state_values[slot] == Approx(2.0)); h.eval_ok("(define health-state-dep 2)"); REQUIRE_FALSE(h.engine.state_compile_diagnostics[slot].active); h.tick(0, 2.0); REQUIRE(h.engine.pool.state_values[slot] == Approx(4.0)); } TEST_CASE("Unassign clears runtime and reactive health with the program", "[health][unassign][transaction]") { Harness h; h.eval_ok("(define health-unassign-dep 1)"); h.eval_ok("(a1 (* health-unassign-dep (* t 1e308)))"); REQUIRE(h.tick(0, 2.0) == Approx(0.0)); REQUIRE((h.engine.pool.runtime_fallback_mask & 1u) != 0); h.eval_ok("(defn health-unassign-dep [x] x)"); REQUIRE(h.engine.output_compile_diagnostics[0].active); h.eval_ok("(unassign a1)"); REQUIRE(output_health(h.engine.pool, 0) == OutputHealth::Idle); REQUIRE((h.engine.pool.runtime_fallback_mask & 1u) == 0); REQUIRE_FALSE(h.engine.output_compile_diagnostics[0].active); } TEST_CASE("State compaction remaps failure health and attributed source together", "[health][state][compaction]") { Harness h; auto& symbols = SymbolIntern::getInstance(); SymbolID dropped = symbols.intern(String("health-remap-dropped")); SymbolID kept = symbols.intern(String("health-remap-kept")); h.eval_ok("(define health-remap-rate 0)"); h.eval_ok("(defstate health-remap-dropped 0 (+ health-remap-dropped 1))"); h.eval_ok("(defstate health-remap-kept 5 (/ 1 health-remap-rate))"); h.eval_ok("(a1 health-remap-dropped)"); h.eval_ok("(a2 health-remap-kept)"); const uint16_t old_kept_slot = h.engine.cells.cells[kept].data_table_id; REQUIRE(old_kept_slot == 1); h.tick(0, 0.0); REQUIRE((h.engine.pool.state_update_failure_mask & ((uint64_t)1 << old_kept_slot)) != 0); // Retiring the lower slot compacts the still-failing state to slot zero. h.eval_ok("(define health-remap-dropped 7)"); REQUIRE(h.engine.cells.cells[dropped].flags == 0); REQUIRE(h.engine.pool.state_slot_count == 1); REQUIRE(h.engine.cells.cells[kept].data_table_id == 0); REQUIRE(h.engine.state_sources[0].state_symbol == kept); REQUIRE((h.engine.pool.state_update_failure_mask & 1u) != 0); REQUIRE((h.engine.pool.state_update_failure_mask & ~uint64_t{1}) == 0); const StateUpdateSource& source = h.engine.state_sources[0]; const char* text = h.engine.arena.read(source.arena_offset); REQUIRE(text != nullptr); REQUIRE(std::string(text, source.arena_length) == "(/ 1 health-remap-rate)"); h.eval_ok("(define health-remap-rate 2)"); h.tick(1, 1.0); REQUIRE(h.engine.pool.state_values[0] == Approx(0.5)); REQUIRE(h.engine.pool.state_update_failure_mask == 0); } TEST_CASE("Live-edit option reorder, rejected variant change, and reclamation are atomic", "[health][live-edit][transaction]") { Harness h; h.eval_ok("(a1 (live-edit :beta :id \"health-mode\" :options [:alpha :beta]))"); REQUIRE(h.engine.pool.live_slot_count == 1); h.engine.pool.set_live_slot_value("health-mode", 1.0); REQUIRE(h.tick(0, 0.0) == Approx(1.0)); h.eval_ok("(a1 (live-edit :alpha :id \"health-mode\" :options [:beta :alpha]))"); const NodePool::LiveSlot before = h.engine.pool.live_slots[0]; REQUIRE(before.variant == NodePool::SlotVariant::Keyword); REQUIRE(before.value == Approx(0.0)); REQUIRE(std::string(before.options[0]) == ":beta"); EvalResult rejected = h.eval( "(a1 (live-edit 0.5 :id \"health-mode\" :min 0 :max 1) 99)"); REQUIRE(rejected.kind == EvalResult::Error); REQUIRE(std::memcmp(&h.engine.pool.live_slots[0], &before, sizeof(before)) == 0); REQUIRE(h.tick(0, 1.0) == Approx(0.0)); h.eval_ok("(unassign a1)"); REQUIRE(h.engine.pool.live_slot_count == 0); h.eval_ok("(a1 (live-edit 0.25 :id \"health-mode\" :min 0 :max 1))"); REQUIRE(h.engine.pool.live_slot_count == 1); REQUIRE(h.engine.pool.live_slots[0].variant == NodePool::SlotVariant::Numeric); REQUIRE(h.engine.pool.live_slots[0].value == Approx(0.25)); } TEST_CASE("One dependency mutation rejects every affected consumer independently", "[health][reactive][multi-consumer]") { Harness h; h.eval_ok("(define health-shared-dep 2)"); h.eval_ok("(a1 (+ health-shared-dep 1))"); h.eval_ok("(a2 (* health-shared-dep 3))"); const uint16_t root_a1 = h.engine.pool.outputs[0].root_node; const uint16_t root_a2 = h.engine.pool.outputs[1].root_node; REQUIRE(h.tick(0, 0.0) == Approx(3.0)); REQUIRE(h.outputs[1] == Approx(6.0)); h.eval_ok("(defn health-shared-dep [x] x)"); REQUIRE(h.engine.output_compile_diagnostics[0].active); REQUIRE(h.engine.output_compile_diagnostics[1].active); REQUIRE(h.engine.pool.outputs[0].root_node == root_a1); REQUIRE(h.engine.pool.outputs[1].root_node == root_a2); REQUIRE(h.tick(0, 1.0) == Approx(3.0)); REQUIRE(h.outputs[1] == Approx(6.0)); } TEST_CASE("Synth-control reactive slots retain LKG and follow artifact lifecycle", "[health][reactive][synth-control]") { Harness h; SymbolID cause = SymbolIntern::getInstance().intern( String("health-synth-dep")); h.eval_ok("(define health-synth-dep 100)"); h.eval_ok("(synth \"osc/sine\" :name \"diag-a\" :freq health-synth-dep :amp health-synth-dep)"); h.eval_ok("(synth \"osc/sine\" :name \"diag-b\" :freq health-synth-dep :amp health-synth-dep)"); REQUIRE(h.engine.synth_graph.control_count() == 4); uint16_t old_roots[MAX_SYNTH_CONTROLS] = {}; for (uint16_t i = 0; i < 4; ++i) { SynthControlChannel& control = h.engine.synth_graph.controls[i]; old_roots[i] = control.root_node; control.lkg_value = 10.0 + i; control.has_lkg = true; } h.eval_ok("(defn health-synth-dep [x] x)"); for (uint16_t i = 0; i < 4; ++i) { const SynthControlChannel& control = h.engine.synth_graph.controls[i]; REQUIRE(control.root_node == old_roots[i]); REQUIRE(control.has_lkg); REQUIRE(control.lkg_value == Approx(10.0 + i)); REQUIRE(control.compile_diagnostic.active()); REQUIRE(control.compile_diagnostic.triggered_by == cause); REQUIRE(control.compile_diagnostic.message != nullptr); } const SynthDeclaration* first_owner = h.engine.synth_graph.declaration_for_control(0); REQUIRE(first_owner != nullptr); REQUIRE(std::string(first_owner->identity) == "diag-a"); const NodeDefParam* first_parameter = h.engine.synth_graph.parameter_for_control(0); const NodeDefParam* second_parameter = h.engine.synth_graph.parameter_for_control(1); REQUIRE(first_parameter != nullptr); REQUIRE(second_parameter != nullptr); REQUIRE(std::string(first_parameter->name) == "freq"); REQUIRE(std::string(second_parameter->name) == "amp"); const SynthDeclaration* third_owner = h.engine.synth_graph.declaration_for_control(2); REQUIRE(third_owner != nullptr); REQUIRE(std::string(third_owner->identity) == "diag-b"); // Direct replacement clears only that declaration's subjects. Dense // artifact order moves the surviving failed declaration ahead of it. h.eval_ok("(synth \"osc/sine\" :name \"diag-a\" :freq 220 :amp 0.2)"); REQUIRE(h.engine.synth_graph.control_count() == 4); first_owner = h.engine.synth_graph.declaration_for_control(0); REQUIRE(first_owner != nullptr); REQUIRE(std::string(first_owner->identity) == "diag-b"); REQUIRE(h.engine.synth_graph.controls[0].compile_diagnostic.active()); REQUIRE(h.engine.synth_graph.controls[1].compile_diagnostic.active()); third_owner = h.engine.synth_graph.declaration_for_control(2); REQUIRE(third_owner != nullptr); REQUIRE(std::string(third_owner->identity) == "diag-a"); REQUIRE_FALSE(h.engine.synth_graph.controls[2].compile_diagnostic.active()); REQUIRE_FALSE(h.engine.synth_graph.controls[3].compile_diagnostic.active()); h.eval_ok("(define health-synth-dep 200)"); REQUIRE_FALSE(h.engine.synth_graph.controls[0].compile_diagnostic.active()); REQUIRE_FALSE(h.engine.synth_graph.controls[1].compile_diagnostic.active()); // Removing one optional control removes its diagnostic slot; full clear // removes every synth-control subject. h.eval_ok("(define health-synth-amp 0.5)"); h.eval_ok("(synth \"osc/sine\" :name \"diag-b\" :freq 330 :amp health-synth-amp)"); h.eval_ok("(defn health-synth-amp [x] x)"); REQUIRE(h.engine.synth_graph.controls[3].compile_diagnostic.active()); h.eval_ok("(synth \"osc/sine\" :name \"diag-b\" :freq 330)"); REQUIRE(h.engine.synth_graph.control_count() == 3); for (uint16_t i = 0; i < 3; ++i) REQUIRE_FALSE(h.engine.synth_graph.controls[i].compile_diagnostic.active()); h.eval_ok("(define health-synth-clear 440)"); h.eval_ok("(synth \"osc/sine\" :name \"diag-clear\" :freq health-synth-clear)"); h.eval_ok("(defn health-synth-clear [x] x)"); REQUIRE(h.engine.synth_graph.controls[3].compile_diagnostic.active()); h.eval_ok("(useq-clear)"); REQUIRE(h.engine.synth_graph.control_count() == 0); }