Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 688f24b4c1 | |||
| d777936ee4 | |||
| 4a57b4faa8 | |||
| 0ee82d9e91 | |||
| 9526bda507 | |||
| fe820928b0 | |||
| 7a1501d097 |
+33
-1
@@ -1134,6 +1134,11 @@ fn route_faculty(path: String, faculty: String) -> String {
|
|||||||
fn route_boundary_proof(method: String, path: String, body: String) -> String {
|
fn route_boundary_proof(method: String, path: String, body: String) -> String {
|
||||||
return "{\"op\":\"boundary_proof\",\"body_instrumentation\":\"none\",\"seam\":\"@manager -> engram_boundary_beat auto-injected\"}"
|
return "{\"op\":\"boundary_proof\",\"body_instrumentation\":\"none\",\"seam\":\"@manager -> engram_boundary_beat auto-injected\"}"
|
||||||
}
|
}
|
||||||
|
// ── GROUNDING: an attribute of the RELATION, and the relation's weight is a
|
||||||
|
// VECTOR (factual, relational, associative, polarity, provenance, timestamp).
|
||||||
|
// /api/ground READS it — it never writes. /api/ground/record is the write,
|
||||||
|
// named as one, and it consolidates only on a consequential + salient move.
|
||||||
|
// /api/ground/trajectory reads the supersession chain as a time series.
|
||||||
fn route_ground(method: String, path: String, body: String) -> String {
|
fn route_ground(method: String, path: String, body: String) -> String {
|
||||||
let claim: String = json_get_string(body, "claim")
|
let claim: String = json_get_string(body, "claim")
|
||||||
let evidence: String = json_get_string(body, "evidence")
|
let evidence: String = json_get_string(body, "evidence")
|
||||||
@@ -1142,12 +1147,31 @@ fn route_ground(method: String, path: String, body: String) -> String {
|
|||||||
if str_eq(evidence, "") { return err_json("missing evidence") }
|
if str_eq(evidence, "") { return err_json("missing evidence") }
|
||||||
return engram_ground_json(claim, evidence, for_whom)
|
return engram_ground_json(claim, evidence, for_whom)
|
||||||
}
|
}
|
||||||
|
fn route_ground_record(method: String, path: String, body: String) -> String {
|
||||||
|
let claim: String = json_get_string(body, "claim")
|
||||||
|
let evidence: String = json_get_string(body, "evidence")
|
||||||
|
let provenance: String = json_get_string(body, "provenance")
|
||||||
|
let floor: String = json_get_string(body, "floor")
|
||||||
|
if str_eq(claim, "") { return err_json("missing claim") }
|
||||||
|
if str_eq(evidence, "") { return err_json("missing evidence") }
|
||||||
|
return engram_ground_record_json(claim, evidence, provenance, floor)
|
||||||
|
}
|
||||||
|
fn route_ground_trajectory(method: String, path: String, body: String) -> String {
|
||||||
|
let claim: String = query_param(path, "claim")
|
||||||
|
let evidence: String = query_param(path, "evidence")
|
||||||
|
if str_eq(claim, "") { return err_json("missing claim") }
|
||||||
|
if str_eq(evidence, "") { return err_json("missing evidence") }
|
||||||
|
return engram_ground_trajectory_json(claim, evidence)
|
||||||
|
}
|
||||||
fn route_assert(method: String, path: String, body: String) -> String {
|
fn route_assert(method: String, path: String, body: String) -> String {
|
||||||
let claim: String = query_param(path, "claim")
|
let claim: String = query_param(path, "claim")
|
||||||
if str_eq(claim, "") { return err_json("missing claim") }
|
if str_eq(claim, "") { return err_json("missing claim") }
|
||||||
let for_whom: String = query_param(path, "for_whom")
|
let for_whom: String = query_param(path, "for_whom")
|
||||||
let floor: String = query_param(path, "floor")
|
let floor: String = query_param(path, "floor")
|
||||||
return engram_assert_json(claim, for_whom, floor)
|
// Both floors. A well-evidenced claim does not earn the right to be asserted
|
||||||
|
// regardless of whether it means the right thing. rel_floor defaults to floor.
|
||||||
|
let rel_floor: String = query_param(path, "rel_floor")
|
||||||
|
return engram_assert_json(claim, for_whom, floor, rel_floor)
|
||||||
}
|
}
|
||||||
fn route_attend(method: String, path: String, body: String) -> String {
|
fn route_attend(method: String, path: String, body: String) -> String {
|
||||||
let node: String = json_get_string(body, "node")
|
let node: String = json_get_string(body, "node")
|
||||||
@@ -1904,6 +1928,14 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/plan") {
|
if str_eq(method, "GET") && str_starts_with(clean, "/api/plan") {
|
||||||
return route_faculty(path, "plan")
|
return route_faculty(path, "plan")
|
||||||
}
|
}
|
||||||
|
// Order matters: the more specific paths must be tested before the /api/ground
|
||||||
|
// prefix match below, which would otherwise swallow them.
|
||||||
|
if str_eq(method, "POST") && str_starts_with(clean, "/api/ground/record") {
|
||||||
|
return route_ground_record(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(method, "GET") && str_starts_with(clean, "/api/ground/trajectory") {
|
||||||
|
return route_ground_trajectory(method, path, body)
|
||||||
|
}
|
||||||
if str_eq(method, "POST") && str_starts_with(clean, "/api/ground") {
|
if str_eq(method, "POST") && str_starts_with(clean, "/api/ground") {
|
||||||
return route_ground(method, path, body)
|
return route_ground(method, path, body)
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Build + RUN the §7 GROUNDING-VECTOR tests (engram_cognition.c): the one decay
|
||||||
|
# model, the consequence gate, and the stored/derived split. Closed-form
|
||||||
|
# constructed cases — no server, no store, no network. Pure C11 (stdlib + libm).
|
||||||
|
# Standalone — NOT folded through elc. Two passes:
|
||||||
|
# 1. PERF — optimised (-O2, no sanitizer): the functional gate.
|
||||||
|
# 2. SAFETY — ASan + UBSan on the same suite.
|
||||||
|
#
|
||||||
|
# NEGATIVE CONTROL (invariant §8.6 — no test without one). Every symbol this
|
||||||
|
# suite exercises (cog_decay_factor, cog_grounding_significant,
|
||||||
|
# cog_significance_inherent, CogGrounding, CogProvClass) is introduced by the
|
||||||
|
# change under test, so the suite does not COMPILE against the pre-change source.
|
||||||
|
# To reproduce:
|
||||||
|
# git show origin/dev:lang/runtime/engram_cognition.h > /tmp/pre/engram_cognition.h
|
||||||
|
# git show origin/dev:lang/runtime/engram_cognition.c > /tmp/pre/engram_cognition.c
|
||||||
|
# cc -I/tmp/pre engram/test/test_grounding_vector.c /tmp/pre/engram_cognition.c ...
|
||||||
|
# => error: unknown type name 'CogGrounding'; no binary produced.
|
||||||
|
set -e
|
||||||
|
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
RT="$HERE/../../lang/runtime"
|
||||||
|
CC=${CC:-cc}
|
||||||
|
SRC="$HERE/test_grounding_vector.c $RT/engram_cognition.c $RT/engram_reason.c $RT/engram_geometry.c $RT/engram_store.c $RT/engram_vindex.c"
|
||||||
|
WARN="-std=c11 -Wall -Wextra"
|
||||||
|
# engram_store.c declares emit_log as a WEAK symbol and null-checks it, which is
|
||||||
|
# how a test links the store without the EL runtime. Darwin's ld does not resolve
|
||||||
|
# an undefined weak symbol at static-link time, so it must be allowed explicitly.
|
||||||
|
# (The pre-existing runners in this directory — run_verify_tests.sh among them —
|
||||||
|
# do not do this and therefore fail to link on macOS. Unrelated to this change.)
|
||||||
|
LDX=""
|
||||||
|
[ "$(uname -s)" = "Darwin" ] && LDX="-Wl,-U,_emit_log"
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
|
||||||
|
echo "### PASS 1: PERF (optimised, un-sanitised) — functional gate"
|
||||||
|
$CC $WARN -O2 -I"$RT" $SRC -lm -lpthread $LDX -o "$TMP/perf"
|
||||||
|
"$TMP/perf"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "### PASS 2: SAFETY (ASan/UBSan)"
|
||||||
|
$CC $WARN -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer -I"$RT" $SRC -lm -lpthread $LDX -o "$TMP/safe"
|
||||||
|
ASAN_OPTIONS=${ASAN_OPTIONS:-detect_leaks=0} UBSAN_OPTIONS=halt_on_error=1 "$TMP/safe"
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
/* test_grounding_vector.c — deterministic tests for §7: the one decay model, the
|
||||||
|
* consequence gate, and the stored/derived split. Links engram_cognition.c
|
||||||
|
* directly; no server, no store, no network. See run_grounding_vector_tests.sh.
|
||||||
|
*
|
||||||
|
* NEGATIVE CONTROL (invariant §8.6). Every symbol exercised here —
|
||||||
|
* cog_decay_factor, cog_grounding_significant, cog_significance_inherent,
|
||||||
|
* CogGrounding, CogProvClass — is introduced by the change under test, so this
|
||||||
|
* suite does not COMPILE against the pre-change source, let alone pass. The
|
||||||
|
* runner documents the exact reproduction.
|
||||||
|
*/
|
||||||
|
#include "engram_cognition.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
static int fails = 0;
|
||||||
|
static void ok(int cond, const char* what) {
|
||||||
|
printf(" %-62s %s\n", what, cond ? "PASS" : "*** FAIL ***");
|
||||||
|
if (!cond) fails++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The decay formula exactly as el_runtime.c carried it before the move, so the
|
||||||
|
* refactor can be shown to be bit-identical rather than merely similar. */
|
||||||
|
static double old_engram_temporal_decay(long long age_ms, long long activation_count,
|
||||||
|
double temporal_decay_rate) {
|
||||||
|
if (age_ms <= 0) return 1.0;
|
||||||
|
double lambda = (temporal_decay_rate > 0.0) ? temporal_decay_rate : 0.693147;
|
||||||
|
double age_hours = (double)age_ms / 3600000.0;
|
||||||
|
double t_half = 168.0 * (1.0 + log(1.0 + (double)activation_count));
|
||||||
|
double factor = exp(-lambda * age_hours / t_half);
|
||||||
|
if (factor < 0.25) factor = 0.25;
|
||||||
|
return factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CogGrounding base(void) {
|
||||||
|
CogGrounding g; memset(&g, 0, sizeof g);
|
||||||
|
g.present = 1;
|
||||||
|
g.factual = 0.60; g.relational = 0.60;
|
||||||
|
g.factual_now = 0.60; g.relational_now = 0.60;
|
||||||
|
g.associative = 0.1; g.polarity = 1.0;
|
||||||
|
g.prov = COG_PROV_TOLD;
|
||||||
|
g.fac_proj = 1.0; g.rel_proj = 1.0;
|
||||||
|
g.cos_angle = 0.9; g.agreement = 1;
|
||||||
|
g.ts = 1000; g.seq = 1; g.reinforcements = 3;
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
const double F = 0.5, R = 0.5;
|
||||||
|
|
||||||
|
printf("\n== 1. DECAY IS THE ONE MODEL, AND IT IS BIT-IDENTICAL TO WHAT IT REPLACED ==\n");
|
||||||
|
{
|
||||||
|
long long ages[] = {0, 3600000LL, 86400000LL, 7*86400000LL, 30*86400000LL, 365*86400000LL};
|
||||||
|
int allsame = 1;
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
for (int ac = 0; ac < 4; ac++) {
|
||||||
|
long long acs[] = {0, 1, 10, 1000};
|
||||||
|
double a = cog_decay_factor(ages[i], (double)acs[ac], 0.0);
|
||||||
|
double b = old_engram_temporal_decay(ages[i], acs[ac], 0.0);
|
||||||
|
if (a != b) allsame = 0;
|
||||||
|
}
|
||||||
|
ok(allsame, "cog_decay_factor == the pre-move engram_temporal_decay (24 pts)");
|
||||||
|
ok(cog_decay_factor(0, 0, 0.0) == 1.0, "age 0 -> no decay");
|
||||||
|
}
|
||||||
|
printf("\n DECAY OVER ELAPSED TIME (reinforcements = 0, default rate):\n");
|
||||||
|
printf(" %10s %10s\n", "elapsed", "decay");
|
||||||
|
{
|
||||||
|
struct { const char* label; long long ms; } pts[] = {
|
||||||
|
{"0", 0LL},
|
||||||
|
{"1 hour", 3600000LL},
|
||||||
|
{"1 day", 86400000LL},
|
||||||
|
{"3 days", 3LL*86400000LL},
|
||||||
|
{"7 days", 7LL*86400000LL},
|
||||||
|
{"14 days", 14LL*86400000LL},
|
||||||
|
{"30 days", 30LL*86400000LL},
|
||||||
|
{"90 days", 90LL*86400000LL},
|
||||||
|
};
|
||||||
|
double prev = 2.0; int monotone = 1;
|
||||||
|
for (unsigned i = 0; i < sizeof pts / sizeof pts[0]; i++) {
|
||||||
|
double d = cog_decay_factor(pts[i].ms, 0, 0.0);
|
||||||
|
printf(" %10s %10.6f\n", pts[i].label, d);
|
||||||
|
if (d > prev) monotone = 0;
|
||||||
|
prev = d;
|
||||||
|
}
|
||||||
|
ok(monotone, "decay is monotone non-increasing in elapsed time");
|
||||||
|
ok(fabs(cog_decay_factor(7LL*86400000LL, 0, 0.0) - 0.5) < 1e-6,
|
||||||
|
"7 days at zero reinforcements == exactly one half-life (0.5)");
|
||||||
|
ok(cog_decay_factor(7LL*86400000LL, 100, 0.0) > cog_decay_factor(7LL*86400000LL, 0, 0.0),
|
||||||
|
"reinforcement slows ageing (Lindy term)");
|
||||||
|
ok(cog_decay_factor(3650LL*86400000LL, 0, 0.0) == 0.25,
|
||||||
|
"floor is a preference not a cliff: bottoms out at 0.25");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n== 2. CONSEQUENCE GATE: EVERY TRIGGER, AND NO EPSILON ANYWHERE ==\n");
|
||||||
|
{
|
||||||
|
CogGrounding p = base(), n = base();
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_NONE,
|
||||||
|
"identical vectors -> NONE (a re-read must not consolidate)");
|
||||||
|
|
||||||
|
n = base(); n.factual = 0.9999; n.factual_now = 0.9999;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_NONE,
|
||||||
|
"factual 0.60 -> 0.9999 without crossing the floor -> NONE");
|
||||||
|
|
||||||
|
n = base(); n.relational = 0.5001; n.relational_now = 0.5001;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_NONE,
|
||||||
|
"relational 0.60 -> 0.5001, still above floor -> NONE");
|
||||||
|
|
||||||
|
n = base(); n.factual_now = 0.4999;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_FACTUAL_FLOOR,
|
||||||
|
"a 0.1001 drop that CROSSES the floor -> FACTUAL_FLOOR");
|
||||||
|
|
||||||
|
n = base(); n.relational_now = 0.4999;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_RELATIONAL_FLOOR,
|
||||||
|
"relational crossing its floor -> RELATIONAL_FLOOR");
|
||||||
|
|
||||||
|
n = base(); n.cos_angle = -0.05; n.agreement = -1;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_AGREEMENT_FLIP,
|
||||||
|
"agreement +1 -> -1 -> AGREEMENT_FLIP");
|
||||||
|
|
||||||
|
n = base(); n.fac_proj = -0.2;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_DIRECTION_REVERSAL,
|
||||||
|
"factual gradient reverses -> DIRECTION_REVERSAL");
|
||||||
|
|
||||||
|
n = base(); n.rel_proj = -0.2;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_DIRECTION_REVERSAL,
|
||||||
|
"relational gradient reverses -> DIRECTION_REVERSAL");
|
||||||
|
|
||||||
|
n = base(); n.polarity = -1.0;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_POLARITY_FLIP,
|
||||||
|
"support -> contradiction -> POLARITY_FLIP (inherent)");
|
||||||
|
|
||||||
|
n = base(); n.polarity = 0.0;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_POLARITY_FLIP,
|
||||||
|
"support -> ignorance (zero) -> POLARITY_FLIP: not the same state");
|
||||||
|
|
||||||
|
n = base(); n.prov = COG_PROV_OBSERVED;
|
||||||
|
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_PROVENANCE_CHANGE,
|
||||||
|
"told -> observed -> PROVENANCE_CHANGE (inherent)");
|
||||||
|
|
||||||
|
CogGrounding fresh; memset(&fresh, 0, sizeof fresh);
|
||||||
|
ok(cog_grounding_significant(&fresh, &n, F, R) == COG_SIG_FIRST_RECORD,
|
||||||
|
"no prior version -> FIRST_RECORD");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n== 3. INHERENT MOVES BYPASS THE SALIENCE GATE ==\n");
|
||||||
|
ok(cog_significance_inherent(COG_SIG_POLARITY_FLIP), "polarity flip is inherent");
|
||||||
|
ok(cog_significance_inherent(COG_SIG_PROVENANCE_CHANGE), "provenance change is inherent");
|
||||||
|
ok(cog_significance_inherent(COG_SIG_FIRST_RECORD), "first record is inherent");
|
||||||
|
ok(!cog_significance_inherent(COG_SIG_FACTUAL_FLOOR), "a floor crossing is NOT inherent");
|
||||||
|
ok(!cog_significance_inherent(COG_SIG_NONE), "NONE is not inherent");
|
||||||
|
|
||||||
|
printf("\n== 4. THE STORED/DERIVED SPLIT: DERIVED VALUES ARE NEVER SERIALIZED ==\n");
|
||||||
|
{
|
||||||
|
CogGrounding g = base();
|
||||||
|
g.decay = 0.3333; g.factual_now = 0.1234; g.relational_now = 0.2345;
|
||||||
|
g.associative_now = 0.4567; g.age_ms = 999999; g.stale = 1;
|
||||||
|
char* m = cog_grounding_metadata("pre-existing=keepme", &g);
|
||||||
|
ok(m != NULL, "serializer returns a document");
|
||||||
|
ok(m && strstr(m, "pre-existing=keepme"), "pre-existing edge metadata preserved verbatim");
|
||||||
|
ok(m && strstr(m, "GRD1"), "GRD1 magic present");
|
||||||
|
ok(m && !strstr(m, "0.3333"), "decay is NOT stored");
|
||||||
|
ok(m && !strstr(m, "0.1234"), "factual_now is NOT stored");
|
||||||
|
ok(m && !strstr(m, "0.2345"), "relational_now is NOT stored");
|
||||||
|
ok(m && !strstr(m, "0.4567"), "associative_now is NOT stored");
|
||||||
|
ok(m && !strstr(m, "999999"), "age is NOT stored");
|
||||||
|
ok(m && strstr(m, "told"), "provenance class IS stored");
|
||||||
|
ok(m && strstr(m, "0.6"), "the factual/relational dimensions ARE stored");
|
||||||
|
if (m) { printf("\n --- serialized GRD1 block ---\n%s -----------------------------\n", m); }
|
||||||
|
free(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n%s (%d failure%s)\n\n", fails ? "SOME TESTS FAILED" : "ALL TESTS PASSED",
|
||||||
|
fails, fails == 1 ? "" : "s");
|
||||||
|
return fails ? 1 : 0;
|
||||||
|
}
|
||||||
+54
-25
@@ -13,7 +13,7 @@
|
|||||||
// relations add edges. Every node enters with PROVENANCE + grounding-level
|
// relations add edges. Every node enters with PROVENANCE + grounding-level
|
||||||
// + stewardship class from the moment of entry.
|
// + stewardship class from the moment of entry.
|
||||||
//
|
//
|
||||||
// transduce_manifold() is THE single mechanism — one function, polymorphic, with no
|
// transduce_bytes() is THE single mechanism — one function, polymorphic, with no
|
||||||
// content-type branch inside it. It does not ask whether a payload is
|
// content-type branch inside it. It does not ask whether a payload is
|
||||||
// prose, structured data, or raw/opaque bytes (audio, or anything else);
|
// prose, structured data, or raw/opaque bytes (audio, or anything else);
|
||||||
// it runs one boundary-scan-with-fixed-window-fallback chunking algorithm
|
// it runs one boundary-scan-with-fixed-window-fallback chunking algorithm
|
||||||
@@ -401,25 +401,54 @@ fn head80(s: String) -> String {
|
|||||||
// truncates at the first embedded NUL, which is routine in real binary
|
// truncates at the first embedded NUL, which is routine in real binary
|
||||||
// bytes) is a MECHANICAL fidelity concern that belongs to whatever produced
|
// bytes) is a MECHANICAL fidelity concern that belongs to whatever produced
|
||||||
// `source` (see ingest_file's file_source_string below) — not a
|
// `source` (see ingest_file's file_source_string below) — not a
|
||||||
// content-type judgment made in here. transduce_manifold() never learns whether a
|
// content-type judgment made in here. transduce_bytes() never learns whether a
|
||||||
// chunk is plain text or a base64-encoded raw-byte window; every chunk is
|
// chunk is plain text or a base64-encoded raw-byte window; every chunk is
|
||||||
// handled identically either way.
|
// handled identically either way.
|
||||||
// RENAMED transduce -> transduce_manifold (2026-08-16). Two reasons, and the
|
// NAMING, CORRECTED 2026-08-16 (second pass). This function was renamed
|
||||||
// first is not the interesting one:
|
// `transduce` -> `transduce_bytes` earlier the same day, on the reasoning
|
||||||
|
// that it "was never signal->geometry — it chunks already-extracted content
|
||||||
|
// and PACKS it into a node+edge manifold, one layer up, and it had taken the
|
||||||
|
// name that belongs to the primitive underneath it."
|
||||||
//
|
//
|
||||||
// 1. Mechanical: `transduce` is now a LANGUAGE primitive in el_runtime.h
|
// THAT REASONING WAS BACKWARDS, and it is worth recording why rather than
|
||||||
// (transduce(signal, modality) -> Geometry). Every El `fn name(...)`
|
// quietly re-renaming. Producing a node+edge manifold is not a layer above
|
||||||
// compiles to a global C symbol with that exact name, so keeping this
|
// transduction — it IS transduction. Transduction is not conversion. When you
|
||||||
// name here is a hard `conflicting types for 'transduce'` compile error
|
// take in music you do not store the song as one discrete geometry; you break
|
||||||
// the moment ingest.c links el_runtime.c. Measured, not anticipated.
|
// it into its component parts and store the geometry of each along with the
|
||||||
|
// relations between them. The song is the structure of those relations.
|
||||||
|
// Signal -> one vector is the operation UNDERNEATH transduction, and its name
|
||||||
|
// is encoding, or geometry. So the layer that was doing it right got renamed
|
||||||
|
// out of the way so the layer doing it wrong could have the name.
|
||||||
//
|
//
|
||||||
// 2. Actual: this function was never signal->geometry. It chunks already-
|
// The primitive has since been corrected: `transduce(signal, modality)` now
|
||||||
// extracted content and PACKS it into a node+edge manifold — a real
|
// returns a Manifold — components plus relations — not a Geometry
|
||||||
// operation, but one layer up, and it had taken the name that belongs to
|
// (el_runtime.c, "Manifold"). The two layers are therefore doing the SAME KIND
|
||||||
// the primitive underneath it. `transduce` is where a signal becomes
|
// of thing, and the inversion dissolves rather than needing to be re-argued.
|
||||||
// geometry; `transduce_manifold` is where extracted content becomes
|
//
|
||||||
// structure. Nothing about this function's behaviour changed.
|
// What is left is a real distinction, and it is about MODALITY, not layering:
|
||||||
fn transduce_manifold(nodes: [String], edges: [String], source: String,
|
//
|
||||||
|
// * `transduce(signal, modality)` dispatches to a realizer that KNOWS the
|
||||||
|
// modality and can name its components — for audio: pitch, interval,
|
||||||
|
// rhythm, harmonic function.
|
||||||
|
// * `transduce_bytes` below is the OPAQUE-BYTES realizer: the decomposition
|
||||||
|
// available to a reader that knows nothing about what it is reading. It
|
||||||
|
// still yields components and relations (chunk nodes; contains / precedes
|
||||||
|
// / section_of edges), which is why it is transduction and not packing. It
|
||||||
|
// just cuts on the only structure visible without understanding — byte
|
||||||
|
// boundaries — so its components are positional rather than meaningful.
|
||||||
|
// That is a LIMITATION of this realizer, not the definition of the
|
||||||
|
// operation.
|
||||||
|
//
|
||||||
|
// The name is suffixed by its modality, not demoted to a lesser layer. Keeping
|
||||||
|
// a distinct symbol is also still mechanically required: every El `fn name`
|
||||||
|
// compiles to a global C symbol, so reusing `transduce` here is a hard
|
||||||
|
// `conflicting types` error the moment ingest.c links el_runtime.c.
|
||||||
|
//
|
||||||
|
// WHERE THIS SHOULD GO: this function should become a registered realizer
|
||||||
|
// returning a real Manifold, so ingest rides the same primitive as every other
|
||||||
|
// modality instead of carrying a parallel implementation. Not done here.
|
||||||
|
// Nothing about this function's behaviour changed in this pass.
|
||||||
|
fn transduce_bytes(nodes: [String], edges: [String], source: String,
|
||||||
prov: String, ground: String, steward: String,
|
prov: String, ground: String, steward: String,
|
||||||
root_lid: String, root_title: String) -> [String] {
|
root_lid: String, root_title: String) -> [String] {
|
||||||
let tagbase: String = "prov:" + prov + " ground:" + ground + " steward:" + steward
|
let tagbase: String = "prov:" + prov + " ground:" + ground + " steward:" + steward
|
||||||
@@ -546,8 +575,8 @@ fn default_steward() -> String {
|
|||||||
// trustworthy verbatim. When they don't (silent truncation happened),
|
// trustworthy verbatim. When they don't (silent truncation happened),
|
||||||
// rebuild the payload as base64-encoded fixed-size windows read directly
|
// rebuild the payload as base64-encoded fixed-size windows read directly
|
||||||
// off disk (fs_read_b64_chunk — binary-safe in C), joined with the same
|
// off disk (fs_read_b64_chunk — binary-safe in C), joined with the same
|
||||||
// "\n\n" boundary marker transduce_manifold()'s generic scan already looks for, so
|
// "\n\n" boundary marker transduce_bytes()'s generic scan already looks for, so
|
||||||
// transduce_manifold() sees one ordinary boundary-delimited payload and runs its one
|
// transduce_bytes() sees one ordinary boundary-delimited payload and runs its one
|
||||||
// algorithm on it exactly as it would on prose — it never learns that a
|
// algorithm on it exactly as it would on prose — it never learns that a
|
||||||
// fidelity problem occurred upstream, let alone why.
|
// fidelity problem occurred upstream, let alone why.
|
||||||
fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
||||||
@@ -556,7 +585,7 @@ fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
|||||||
// 3072 raw bytes -> 4096 base64 chars (3 divides evenly into base64's
|
// 3072 raw bytes -> 4096 base64 chars (3 divides evenly into base64's
|
||||||
// 3-byte/4-char ratio); keeps each resulting node's content a clean,
|
// 3-byte/4-char ratio); keeps each resulting node's content a clean,
|
||||||
// bounded, low-kilobytes unit, same order of magnitude as the fixed
|
// bounded, low-kilobytes unit, same order of magnitude as the fixed
|
||||||
// fallback window in transduce_manifold() itself.
|
// fallback window in transduce_bytes() itself.
|
||||||
let win: Int = 3072
|
let win: Int = 3072
|
||||||
let out: String = ""
|
let out: String = ""
|
||||||
let off: Int = 0
|
let off: Int = 0
|
||||||
@@ -576,7 +605,7 @@ fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ingest one file -> report JSON. Uniform for every file regardless of
|
// ingest one file -> report JSON. Uniform for every file regardless of
|
||||||
// extension or content — transduce_manifold() decides nothing about content-type, so
|
// extension or content — transduce_bytes() decides nothing about content-type, so
|
||||||
// neither does this function; it only decides whether the raw bytes made it
|
// neither does this function; it only decides whether the raw bytes made it
|
||||||
// through the read intact (file_source_string), which is a fidelity
|
// through the read intact (file_source_string), which is a fidelity
|
||||||
// question, not a format one.
|
// question, not a format one.
|
||||||
@@ -588,14 +617,14 @@ fn ingest_file(path: String) -> String {
|
|||||||
return "{\"error\":\"empty or unreadable\",\"path\":" + j_q(path) + "}"
|
return "{\"error\":\"empty or unreadable\",\"path\":" + j_q(path) + "}"
|
||||||
}
|
}
|
||||||
let prov: String = "file:" + path
|
let prov: String = "file:" + path
|
||||||
let packed: [String] = transduce_manifold(el_list_empty(), el_list_empty(),
|
let packed: [String] = transduce_bytes(el_list_empty(), el_list_empty(),
|
||||||
source, prov, default_ground(), default_steward(),
|
source, prov, default_ground(), default_steward(),
|
||||||
"doc:" + basename(path), basename(path))
|
"doc:" + basename(path), basename(path))
|
||||||
return merge_packed(packed)
|
return merge_packed(packed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ingest a directory: walk one level, ingest every file found, aggregate.
|
// ingest a directory: walk one level, ingest every file found, aggregate.
|
||||||
// No extension filter — transduce_manifold() handles any payload uniformly now, so
|
// No extension filter — transduce_bytes() handles any payload uniformly now, so
|
||||||
// there is no content-type gate at the directory boundary either.
|
// there is no content-type gate at the directory boundary either.
|
||||||
fn ingest_dir(path: String) -> String {
|
fn ingest_dir(path: String) -> String {
|
||||||
let entries: [String] = fs_list(path)
|
let entries: [String] = fs_list(path)
|
||||||
@@ -630,7 +659,7 @@ fn ingest_dir(path: String) -> String {
|
|||||||
fn ingest_url(url: String) -> String {
|
fn ingest_url(url: String) -> String {
|
||||||
let body: String = http_get(url)
|
let body: String = http_get(url)
|
||||||
if str_eq(body, "") { return "{\"error\":\"empty fetch\",\"url\":" + j_q(url) + "}" }
|
if str_eq(body, "") { return "{\"error\":\"empty fetch\",\"url\":" + j_q(url) + "}" }
|
||||||
let packed: [String] = transduce_manifold(el_list_empty(), el_list_empty(),
|
let packed: [String] = transduce_bytes(el_list_empty(), el_list_empty(),
|
||||||
body, "url:" + url, "extracted", "public-web",
|
body, "url:" + url, "extracted", "public-web",
|
||||||
"url:" + url, url)
|
"url:" + url, url)
|
||||||
return merge_packed(packed)
|
return merge_packed(packed)
|
||||||
@@ -645,7 +674,7 @@ fn ingest_llm(query: String) -> String {
|
|||||||
let resp: String = http_post_json("http://127.0.0.1:11434/api/generate", body)
|
let resp: String = http_post_json("http://127.0.0.1:11434/api/generate", body)
|
||||||
let answer: String = json_get_string(resp, "response")
|
let answer: String = json_get_string(resp, "response")
|
||||||
if str_eq(answer, "") { return "{\"error\":\"no model response\"}" }
|
if str_eq(answer, "") { return "{\"error\":\"no model response\"}" }
|
||||||
let packed: [String] = transduce_manifold(el_list_empty(), el_list_empty(),
|
let packed: [String] = transduce_bytes(el_list_empty(), el_list_empty(),
|
||||||
answer, "llm:" + model + ":" + query, "candidate-provisional", "guide-provisional",
|
answer, "llm:" + model + ":" + query, "candidate-provisional", "guide-provisional",
|
||||||
"llm:" + query, "guide answer: " + query)
|
"llm:" + query, "guide answer: " + query)
|
||||||
return merge_packed(packed)
|
return merge_packed(packed)
|
||||||
@@ -697,7 +726,7 @@ fn ingest_stream(path: String) -> String {
|
|||||||
// It is NOT a content-type flag: it says nothing about what's inside the
|
// It is NOT a content-type flag: it says nothing about what's inside the
|
||||||
// bytes once fetched, and none of the five ingest_* functions it selects
|
// bytes once fetched, and none of the five ingest_* functions it selects
|
||||||
// among interpret their payload differently by content shape anymore —
|
// among interpret their payload differently by content shape anymore —
|
||||||
// they all hand off to the single, format-agnostic transduce_manifold(). The old
|
// they all hand off to the single, format-agnostic transduce_bytes(). The old
|
||||||
// "structured" value (a caller-declared alias for "file", used only to hint
|
// "structured" value (a caller-declared alias for "file", used only to hint
|
||||||
// the now-removed JSON-vs-prose branch) is gone along with that branch.
|
// the now-removed JSON-vs-prose branch) is gone along with that branch.
|
||||||
let kind: String = env("INGEST_KIND")
|
let kind: String = env("INGEST_KIND")
|
||||||
|
|||||||
@@ -73,6 +73,17 @@ When you add a C builtin (verbatim-emit recipe — the El name is emitted as the
|
|||||||
2. Add a `__`-prefixed thin wrapper in `el_seed.c` and declare it in `el_seed.h`.
|
2. Add a `__`-prefixed thin wrapper in `el_seed.c` and declare it in `el_seed.h`.
|
||||||
3. Add the name to `builtin_arity` in `el-compiler/src/codegen.el` — add **both** the plain and `__`-prefixed spellings.
|
3. Add the name to `builtin_arity` in `el-compiler/src/codegen.el` — add **both** the plain and `__`-prefixed spellings.
|
||||||
4. Rebuild the elc binary (see below) and confirm the self-host fixpoint is byte-identical.
|
4. Rebuild the elc binary (see below) and confirm the self-host fixpoint is byte-identical.
|
||||||
|
5. **Prove it with a NEGATIVE CONTROL.** Show the test FAILING on a build without your change, then passing with it. A test that has never been seen to fail has proven nothing.
|
||||||
|
|
||||||
|
> **Step 5 is not optional, and step 4 does not cover it.** The fixpoint proves the *compiler reproduces itself*. It says nothing whatsoever about whether your builtin works. A recipe ending at "byte-identical" reads as complete while having verified nothing about the thing just added — which is why this file, until 2026-08-16, produced builtins with no tests at all.
|
||||||
|
>
|
||||||
|
> Measured cost of the omission (2026-08-16): `engram_node_set_emb`, `engram_curiosity_json` and `dream_set_handler` were all added in one session with zero tests. Separately, a UTF-8 fix was written, tested, and **the test passed on the unpatched build too** — the defect was elsewhere entirely, and only building the pre-fix binary exposed it. Without a negative control that fix would have merged as verified.
|
||||||
|
>
|
||||||
|
> Two shapes that pass while proving nothing, both hit the same day:
|
||||||
|
> - A test that never exercises your change (the route supplied a default that bypassed the code under test).
|
||||||
|
> - An induction that loses a race. `curl --max-time` on a large response left *both* builds alive; only `SO_LINGER 0` — a genuine RST, so the peer is provably gone — reproduced the failure. Six of ten attempts is not a control.
|
||||||
|
>
|
||||||
|
> Before every probe, confirm **your** process bound the port (`lsof -nP -iTCP:<port>`, match the PID). A stale instance answering on the port has silently produced false results here more than once, and `pkill -f` does not reliably match an argv like `./engram`.
|
||||||
|
|
||||||
Worked example: the `engram_assert_json` (op_assert seam) and `engram_node_full_in`/`engram_connect_in` (purview write-side) primitives added 2026-08-15 follow exactly this recipe.
|
Worked example: the `engram_assert_json` (op_assert seam) and `engram_node_full_in`/`engram_connect_in` (purview write-side) primitives added 2026-08-15 follow exactly this recipe.
|
||||||
|
|
||||||
|
|||||||
+183
-168
@@ -1,67 +1,33 @@
|
|||||||
// transduce.el — geometry as a first-class El value, and a realizer written
|
// transduce.el — transduction decomposes a signal into components and the
|
||||||
// in El. Runnable: this is the worked example for the transduce surface, and
|
// relations between them. Runnable: this is the worked example for the
|
||||||
// it doubles as an executable proof because it checks every claim it makes.
|
// transduce surface, and it exits non-zero if any claim in it stops being true.
|
||||||
//
|
//
|
||||||
// elc lang/examples/transduce.el > transduce.c
|
// elc lang/examples/transduce.el > transduce.c
|
||||||
// cc -std=c11 -O2 -I lang/runtime -o transduce transduce.c \
|
// cc -std=c11 -O2 -I lang/runtime -o transduce transduce.c \
|
||||||
// lang/runtime/el_runtime.c lang/runtime/el_seed.c \
|
// lang/runtime/el_runtime.c lang/runtime/el_seed.c \
|
||||||
// lang/runtime/engram_*.c -lcurl -lpthread -lm
|
// lang/runtime/engram_store.c lang/runtime/engram_vindex.c \
|
||||||
|
// lang/runtime/engram_cognition.c lang/runtime/engram_geometry.c \
|
||||||
|
// lang/runtime/engram_reason.c lang/runtime/engram_verify.c \
|
||||||
|
// -lcurl -lpthread -lm
|
||||||
// ./transduce # exits 0 only if every check passes
|
// ./transduce # exits 0 only if every check passes
|
||||||
//
|
//
|
||||||
// (A `test "..."` form of the same checks lives in
|
// It writes to an IN-MEMORY engram (leave ENGRAM_STORE unset) and contacts no
|
||||||
// lang/tests/native/test_transduce.el, for when the native harness is
|
// server. The same claims are asserted by the native harness in
|
||||||
// repaired — the shipped elc currently emits calls to __el_reg_count and
|
// lang/tests/native/test_transduce.el.
|
||||||
// friends without emitting their definitions, which breaks every native test
|
|
||||||
// equally, test_math.el included. Verified 2026-08-16, unrelated to this work.)
|
|
||||||
//
|
//
|
||||||
// WHY THIS EXISTS. Until 2026-08-16 no El ingest path could carry a vector:
|
// WHAT CHANGED, AND WHY IT MATTERS. #144 shipped
|
||||||
// nodes took text, and geometry was DERIVED from that text. Text was the
|
// `transduce(signal, modality) -> Geometry`: one vector per signal. That made
|
||||||
// mandatory entry medium, so any non-text modality had to be DESCRIBED in
|
// transduction a CONVERSION — take a thing, encode it, store a position — and
|
||||||
// prose first and the geometry we reasoned over was the geometry OF THE
|
// what a conversion returns is a fingerprint. A fingerprint can be matched and
|
||||||
// DESCRIPTION, not of the signal. Two things fix that, and both are shown
|
// ranked, and that is all it can ever do. It cannot be decomposed, cannot have
|
||||||
// below: geometry is a VALUE that carries its own width, and a REALIZER is an
|
// one part grounded while another is not, and cannot be contradicted in one
|
||||||
// ordinary El function — so admitting a new modality never requires a runtime
|
// part while holding in another, because it has no parts.
|
||||||
// patch.
|
|
||||||
//
|
//
|
||||||
// COMPARISON DISCIPLINE (measured, not stylistic): elc lowers `a == b`
|
// A song is not a point. It decomposes into pitch, interval, rhythm, harmonic
|
||||||
// numerically only when both operand NAMES are in the per-function int-name
|
// function — components, each with its own geometry, plus the relations among
|
||||||
// set that `let x: Int` populates. A bare `f(x) == 0` is not a registered
|
// them. THE SONG IS THE STRUCTURE OF THE RELATIONS. transduce now returns a
|
||||||
// name and lowers to str_eq — strcmp on two integers as pointers. `<` and `>`
|
// Manifold, and a realizer's job is to say what its modality's components ARE.
|
||||||
// lower directly with no inference, so truthiness is written `> 0` / `< 1`.
|
|
||||||
|
|
||||||
// ── A realizer, written entirely in El ──────────────────────────────────────
|
|
||||||
// Not in the runtime. Not known to the compiler. Registered by NAME and
|
|
||||||
// dispatched to through transduce(). That is the whole claim.
|
|
||||||
fn tone_realizer(signal: String) -> Geometry {
|
|
||||||
let g: Geometry = geometry_new(4)
|
|
||||||
let n: Int = str_len(signal)
|
|
||||||
let a: Int = geometry_set(g, 0, int_to_float(n))
|
|
||||||
let b: Int = geometry_set(g, 1, int_to_float(n * 2))
|
|
||||||
let c: Int = geometry_set(g, 2, int_to_float(n * 3))
|
|
||||||
let d: Int = geometry_set(g, 3, int_to_float(n * 4))
|
|
||||||
g
|
|
||||||
}
|
|
||||||
|
|
||||||
// A second modality, to show the registry keys on modality rather than just
|
|
||||||
// returning whatever was registered last.
|
|
||||||
fn pulse_realizer(signal: String) -> Geometry {
|
|
||||||
let g: Geometry = geometry_new(2)
|
|
||||||
let a: Int = geometry_set(g, 0, 1.0)
|
|
||||||
let b: Int = geometry_set(g, 1, 0.0)
|
|
||||||
g
|
|
||||||
}
|
|
||||||
|
|
||||||
// A deliberately BROKEN realizer: returns something that is not a Geometry.
|
|
||||||
fn bogus_realizer(signal: String) -> Geometry {
|
|
||||||
return 12345
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fails FAST rather than accumulating a count, for a measured reason: a first
|
|
||||||
// cut wrote `let fails: Int = fails + check(...)` and `+` lowered to STRING
|
|
||||||
// CONCAT, because elc dispatches `+` on whether both operands are known-Int and
|
|
||||||
// a user-defined fn call is not — so the counter printed 4343632752, a pointer.
|
|
||||||
// Nothing was wrong with the checks; the tally was lying. Exiting at the first
|
|
||||||
// failure needs no arithmetic at all, so there is nothing left to get wrong.
|
|
||||||
fn check(ok: Int, label: String) -> Int {
|
fn check(ok: Int, label: String) -> Int {
|
||||||
if ok > 0 {
|
if ok > 0 {
|
||||||
println(" ok " + label)
|
println(" ok " + label)
|
||||||
@@ -84,128 +50,177 @@ fn eq_int(a: Int, b: Int) -> Int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── A DECOMPOSING realizer, written entirely in El ──────────────────────────
|
||||||
|
// "tone" signals are note letters, e.g. "CEG". This does NOT return one vector
|
||||||
|
// for the chord. It returns the PARTS — one component per note, one per
|
||||||
|
// interval between adjacent notes — and the relations that make those parts a
|
||||||
|
// chord rather than an unordered bag of pitches.
|
||||||
|
//
|
||||||
|
// The interval is deliberately a COMPONENT, not a field on a note. An interval
|
||||||
|
// is a thing with its own geometry belonging to neither endpoint; modelling it
|
||||||
|
// as an attribute of one of them is the same collapse, one level down.
|
||||||
|
fn tone_realizer(signal: String) -> Manifold {
|
||||||
|
let m: Manifold = manifold_new()
|
||||||
|
let n: Int = str_len(signal)
|
||||||
|
let i: Int = 0
|
||||||
|
while i < n {
|
||||||
|
let code: Int = str_char_code(signal, i)
|
||||||
|
let g: Geometry = geometry_new(2)
|
||||||
|
let s0: Int = geometry_set(g, 0, int_to_float(code))
|
||||||
|
let s1: Int = geometry_set(g, 1, int_to_float(i))
|
||||||
|
let idx: Int = manifold_add(m, "note:" + int_to_str(i), "pitch", g)
|
||||||
|
let f: Int = geometry_free(g)
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
let j: Int = 1
|
||||||
|
while j < n {
|
||||||
|
let a: Int = str_char_code(signal, j - 1)
|
||||||
|
let b: Int = str_char_code(signal, j)
|
||||||
|
let lo: String = "note:" + int_to_str(j - 1)
|
||||||
|
let hi: String = "note:" + int_to_str(j)
|
||||||
|
let key: String = "interval:" + int_to_str(j - 1) + "-" + int_to_str(j)
|
||||||
|
let g: Geometry = geometry_new(1)
|
||||||
|
let s: Int = geometry_set(g, 0, int_to_float(b - a))
|
||||||
|
let idx: Int = manifold_add(m, key, "interval", g)
|
||||||
|
let f: Int = geometry_free(g)
|
||||||
|
let e1: Int = manifold_relate(m, key, "spans", lo, 0.9)
|
||||||
|
let e2: Int = manifold_relate(m, key, "spans", hi, 0.9)
|
||||||
|
let e3: Int = manifold_relate(m, lo, "sounds_before", hi, 0.8)
|
||||||
|
j = j + 1
|
||||||
|
}
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
|
// #144's contract, kept as a control: one vector for the whole signal.
|
||||||
|
fn fingerprint_realizer(signal: String) -> Geometry {
|
||||||
|
let g: Geometry = geometry_new(4)
|
||||||
|
let n: Int = str_len(signal)
|
||||||
|
let a: Int = geometry_set(g, 0, int_to_float(n))
|
||||||
|
g
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Void {
|
fn main() -> Void {
|
||||||
println("geometry is a value that carries its own width")
|
|
||||||
let g8: Geometry = geometry_new(8)
|
|
||||||
let _c: Int = check(geometry_is(g8), "geometry_new returns a live Geometry")
|
|
||||||
let d8: Int = geometry_dim(g8)
|
|
||||||
let _c: Int = check(eq_int(d8, 8), "a Geometry carries its own width (8)")
|
|
||||||
let _c: Int = check(geometry_free(g8), "geometry_free reports what it did")
|
|
||||||
|
|
||||||
println("nonsense is refused — with no arbitrary max-dim bound")
|
|
||||||
// #141 needed `dim <= 8192` only to bound an allocation sized from a
|
|
||||||
// caller's CLAIM about a string's length. A value that carries its own
|
|
||||||
// width has nothing left to validate.
|
|
||||||
let z: Geometry = geometry_new(0)
|
|
||||||
let zi: Int = geometry_is(z)
|
|
||||||
let _c: Int = check(1 - zi, "dim 0 is not a geometry")
|
|
||||||
let ng: Geometry = geometry_new(-4)
|
|
||||||
let ngi: Int = geometry_is(ng)
|
|
||||||
let _c: Int = check(1 - ngi, "negative dim is not a geometry")
|
|
||||||
let nd: Int = geometry_dim(0)
|
|
||||||
let _c: Int = check(1 - nd, "geometry_dim of a non-geometry is 0, not a crash")
|
|
||||||
let nf: Int = geometry_free(0)
|
|
||||||
let _c: Int = check(1 - nf, "geometry_free of a non-geometry is a no-op")
|
|
||||||
|
|
||||||
println("components round-trip, and out-of-range is refused")
|
|
||||||
let g3: Geometry = geometry_new(3)
|
|
||||||
let s0: Int = geometry_set(g3, 0, 1.5)
|
|
||||||
let s1: Int = geometry_set(g3, 1, -2.5)
|
|
||||||
let _c: Int = check(s0, "set in range succeeds")
|
|
||||||
let oob: Int = geometry_set(g3, 3, 9.0)
|
|
||||||
let _c: Int = check(1 - oob, "set out of range is refused, not silently dropped")
|
|
||||||
let _c: Int = check(near(geometry_get(g3, 0), 1.5), "component 0 round-trips")
|
|
||||||
let _c: Int = check(near(geometry_get(g3, 1), -2.5), "component 1 round-trips (negative)")
|
|
||||||
let ff3: Int = geometry_free(g3)
|
|
||||||
|
|
||||||
println("hex is an EDGE adapter, and derives its own width")
|
|
||||||
// little-endian float32: 1.0 = 0000803f, 2.0 = 00000040
|
|
||||||
let gh: Geometry = geometry_from_f32le_hex("0000803f00000040")
|
|
||||||
let _c: Int = check(geometry_is(gh), "valid hex decodes to a Geometry")
|
|
||||||
let dh: Int = geometry_dim(gh)
|
|
||||||
let _c: Int = check(eq_int(dh, 2), "width DERIVED from input, never supplied")
|
|
||||||
let _c: Int = check(near(geometry_get(gh, 0), 1.0), "first component decoded")
|
|
||||||
let _c: Int = check(near(geometry_get(gh, 1), 2.0), "second component decoded")
|
|
||||||
let back: String = geometry_to_f32le_hex(gh)
|
|
||||||
let _c: Int = check(str_eq(back, "0000803f00000040"), "hex round-trips exactly")
|
|
||||||
let ffh: Int = geometry_free(gh)
|
|
||||||
|
|
||||||
println("malformed hex is refused")
|
|
||||||
let he: Geometry = geometry_from_f32le_hex("")
|
|
||||||
let hei: Int = geometry_is(he)
|
|
||||||
let _c: Int = check(1 - hei, "empty hex is not a geometry")
|
|
||||||
let hr: Geometry = geometry_from_f32le_hex("0000803f0000")
|
|
||||||
let hri: Int = geometry_is(hr)
|
|
||||||
let _c: Int = check(1 - hri, "length not a multiple of 8 is refused")
|
|
||||||
let hn: Geometry = geometry_from_f32le_hex("zzzzzzzz")
|
|
||||||
let hni: Int = geometry_is(hn)
|
|
||||||
let _c: Int = check(1 - hni, "non-hex characters are refused")
|
|
||||||
|
|
||||||
println("a realizer declared in El is a first-class realizer")
|
println("a realizer declared in El is a first-class realizer")
|
||||||
let reg: Int = realizer_register("tone", "tone_realizer")
|
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||||
let _c: Int = check(reg, "an El fn registers as a realizer BY NAME")
|
let _c: Int = check(reg, "an El fn registers as a realizer by name")
|
||||||
let _c: Int = check(realizer_has("tone"), "the modality now has an organ")
|
let _c: Int = check(realizer_has("tone"), "the modality now has an organ")
|
||||||
let gt: Geometry = transduce("aaa", "tone")
|
|
||||||
let _c: Int = check(geometry_is(gt), "transduce returns real geometry")
|
|
||||||
let dt: Int = geometry_dim(gt)
|
|
||||||
let _c: Int = check(eq_int(dt, 4), "the El realizer determined the width, not the runtime")
|
|
||||||
// str_len("aaa") == 3, so component 0 must be 3.0 — proof the signal
|
|
||||||
// actually reached the El function rather than a stub answering for it.
|
|
||||||
let _c: Int = check(near(geometry_get(gt, 0), 3.0), "the signal REACHED the El realizer")
|
|
||||||
let fft: Int = geometry_free(gt)
|
|
||||||
|
|
||||||
println("distinct signals transduce to distinct geometry")
|
println("transduction decomposes a signal into parts")
|
||||||
let g1: Geometry = transduce("aa", "tone")
|
let m: Manifold = transduce("CEG", "tone")
|
||||||
let g2: Geometry = transduce("aaaaa", "tone")
|
let _c: Int = check(manifold_is(m), "transduce returns a real Manifold")
|
||||||
let a1: Float = geometry_get(g1, 0)
|
let sz: Int = manifold_size(m)
|
||||||
let a2: Float = geometry_get(g2, 0)
|
let _c: Int = check(eq_int(sz, 5), "three notes and two intervals are five parts")
|
||||||
// 5 - 2 = 3. If transduction were a stub these would be equal.
|
let rc: Int = manifold_rel_count(m)
|
||||||
let _c: Int = check(near(a2 - a1, 3.0), "different signals produce different geometry")
|
let _c: Int = check(eq_int(rc, 6), "and they stand in six stated relations")
|
||||||
let ff1: Int = geometry_free(g1)
|
|
||||||
let ff2: Int = geometry_free(g2)
|
|
||||||
|
|
||||||
println("the registry keys on modality")
|
println("every part is addressable BY KEY, which is what survives persistence")
|
||||||
let r2: Int = realizer_register("pulse", "pulse_realizer")
|
let i_c: Int = manifold_index_of(m, "note:0")
|
||||||
let _c: Int = check(r2, "a second modality registers independently")
|
let _c: Int = check(1 - eq_int(i_c, -1), "the first note is addressable on its own")
|
||||||
let mt: Geometry = transduce("aaa", "tone")
|
let i_iv: Int = manifold_index_of(m, "interval:0-1")
|
||||||
let mp: Geometry = transduce("aaa", "pulse")
|
let _c: Int = check(1 - eq_int(i_iv, -1), "so is the interval between the first two")
|
||||||
let mdt: Int = geometry_dim(mt)
|
let miss: Int = manifold_index_of(m, "never_added")
|
||||||
let mdp: Int = geometry_dim(mp)
|
let _c: Int = check(eq_int(miss, -1), "an unknown key is -1, not component 0")
|
||||||
let _c: Int = check(eq_int(mdt, 4), "tone still routes to its own realizer")
|
|
||||||
let _c: Int = check(eq_int(mdp, 2), "pulse routes to a different realizer")
|
|
||||||
let ffm1: Int = geometry_free(mt)
|
|
||||||
let ffm2: Int = geometry_free(mp)
|
|
||||||
|
|
||||||
println("no organ is reported as no organ")
|
println("parts carry their own geometry, and may differ in width")
|
||||||
// A modality with no realizer must transduce to NOTHING. It must never
|
let gn: Geometry = manifold_geometry(m, i_c)
|
||||||
// fall back to embedding a description of the signal and calling that
|
let _c: Int = check(eq_int(geometry_dim(gn), 2), "a note component is 2 wide")
|
||||||
// perception — that silent substitution is the defect this all exists to end.
|
let _c: Int = check(near(geometry_get(gn, 0), 67.0), "and it is C — the signal reached the realizer")
|
||||||
let eh: Int = realizer_has("echolocation")
|
let gi: Geometry = manifold_geometry(m, i_iv)
|
||||||
let _c: Int = check(1 - eh, "unregistered modality has no organ")
|
let _c: Int = check(eq_int(geometry_dim(gi), 1), "an interval component is 1 wide")
|
||||||
let ge: Geometry = transduce("anything", "echolocation")
|
// A single vector per signal cannot represent parts of unequal width at all.
|
||||||
let gei: Int = geometry_is(ge)
|
let _c: Int = check(near(geometry_get(gi, 0), 2.0), "C to E is two semitones")
|
||||||
let _c: Int = check(1 - gei, "no realizer means NO geometry, not fake geometry")
|
let f1: Int = geometry_free(gn)
|
||||||
|
let f2: Int = geometry_free(gi)
|
||||||
|
|
||||||
println("an unresolvable realizer name fails at WIRING time")
|
println("the relations are content no single part carries")
|
||||||
let bad: Int = realizer_register("ghost", "no_such_function_anywhere")
|
// That "2" above is not a property of C and not a property of E. It exists
|
||||||
let _c: Int = check(1 - bad, "unresolvable realizer name is a registration failure")
|
// only BETWEEN them, so a representation with no relations cannot hold it.
|
||||||
let gh2: Int = realizer_has("ghost")
|
let spans: Int = 0
|
||||||
let _c: Int = check(1 - gh2, "and nothing gets registered")
|
let k: Int = 0
|
||||||
|
while k < rc {
|
||||||
|
if str_eq(manifold_rel_name(m, k), "spans") {
|
||||||
|
if str_eq(manifold_rel_from(m, k), "interval:0-1") { spans = spans + 1 }
|
||||||
|
}
|
||||||
|
k = k + 1
|
||||||
|
}
|
||||||
|
let _c: Int = check(eq_int(spans, 2), "the interval is wired to both notes it spans")
|
||||||
|
|
||||||
println("a realizer returning non-geometry transduces nothing")
|
println("relation weight IS the grounding (correspondence-and-censorship §1)")
|
||||||
let rb: Int = realizer_register("bogus", "bogus_realizer")
|
let wk: Int = 0
|
||||||
let _c: Int = check(rb, "the symbol resolves, so registration succeeds")
|
let found: Int = 0
|
||||||
let gb: Geometry = transduce("x", "bogus")
|
while wk < rc {
|
||||||
let gbi: Int = geometry_is(gb)
|
if str_eq(manifold_rel_name(m, wk), "sounds_before") {
|
||||||
let _c: Int = check(1 - gbi, "contract enforced at the boundary: nothing handed back")
|
if near(manifold_rel_weight(m, wk), 0.8) > 0 { found = 1 }
|
||||||
|
}
|
||||||
|
wk = wk + 1
|
||||||
|
}
|
||||||
|
let _c: Int = check(found, "the ordering relation carries the weight its realizer stated")
|
||||||
|
|
||||||
println("norm lets a caller check a realizer emitted signal, not zeros")
|
println("the decomposition persists as real, separately addressable nodes")
|
||||||
let gn: Geometry = geometry_new(2)
|
let ids: [String] = el_list_empty()
|
||||||
let _c: Int = check(near(geometry_norm(gn), 0.0), "a fresh geometry is zero — norm says so")
|
let n0: Int = engram_node_count()
|
||||||
let n0: Int = geometry_set(gn, 0, 3.0)
|
let e0: Int = engram_edge_count()
|
||||||
let n1: Int = geometry_set(gn, 1, 4.0)
|
let pi: Int = 0
|
||||||
let _c: Int = check(near(geometry_norm(gn), 5.0), "3-4-5: norm is 5")
|
while pi < sz {
|
||||||
let ffn: Int = geometry_free(gn)
|
let key: String = manifold_key(m, pi)
|
||||||
|
let g: Geometry = manifold_geometry(m, pi)
|
||||||
|
let id: String = engram_node("component " + key, "Concept", 0.6)
|
||||||
|
let att: Int = node_attach_geometry(id, g)
|
||||||
|
ids = el_list_append(ids, id)
|
||||||
|
let ff: Int = geometry_free(g)
|
||||||
|
pi = pi + 1
|
||||||
|
}
|
||||||
|
let ri: Int = 0
|
||||||
|
while ri < rc {
|
||||||
|
let fi: Int = manifold_index_of(m, manifold_rel_from(m, ri))
|
||||||
|
let ti: Int = manifold_index_of(m, manifold_rel_to(m, ri))
|
||||||
|
engram_connect(el_list_get(ids, fi), el_list_get(ids, ti),
|
||||||
|
manifold_rel_weight(m, ri), manifold_rel_name(m, ri))
|
||||||
|
ri = ri + 1
|
||||||
|
}
|
||||||
|
let _c: Int = check(eq_int(engram_node_count() - n0, 5), "one signal became five nodes")
|
||||||
|
let _c: Int = check(eq_int(engram_edge_count() - e0, 6), "and six edges between them")
|
||||||
|
|
||||||
|
println("each part's geometry is independently readable back off its node")
|
||||||
|
let id_c: String = el_list_get(ids, manifold_index_of(m, "note:0"))
|
||||||
|
let id_iv: String = el_list_get(ids, manifold_index_of(m, "interval:0-1"))
|
||||||
|
let _c: Int = check(eq_int(node_geometry_dim(id_c), 2), "note:0 node carries a 2-wide geometry")
|
||||||
|
let _c: Int = check(eq_int(node_geometry_dim(id_iv), 1), "interval:0-1 node carries a 1-wide one")
|
||||||
|
|
||||||
|
println("one part can be grounded without touching its siblings")
|
||||||
|
let ear: String = engram_node("evidence: heard a C in the recording", "Memory", 0.7)
|
||||||
|
engram_connect(ear, id_c, 0.95, "corroborates")
|
||||||
|
let _c: Int = check(engram_edge_between(ear, id_c), "evidence attaches to note:0 specifically")
|
||||||
|
let id_g: String = el_list_get(ids, manifold_index_of(m, "note:2"))
|
||||||
|
let _c: Int = check(1 - engram_edge_between(ear, id_g), "and NOT to note:2 — the sibling is untouched")
|
||||||
|
// This is the whole gain, and it is impossible with a fingerprint: with one
|
||||||
|
// node per signal, "the C is corroborated" and "the G is not" have the same
|
||||||
|
// grounding target and cannot both be recorded.
|
||||||
|
let _c: Int = check(eq_int(node_geometry_dim(id_g), 2), "note:2 geometry is intact regardless")
|
||||||
|
|
||||||
|
println("a fingerprint realizer transduces NOTHING")
|
||||||
|
// #144's contract exactly: signal in, one Geometry out. It resolves, so the
|
||||||
|
// organ is present — but it does not decompose, so it does not transduce.
|
||||||
|
// "No organ" and "an organ that only fingerprints" must not look alike.
|
||||||
|
let rf: Int = realizer_register("fingerprint", "fingerprint_realizer")
|
||||||
|
let _c: Int = check(rf, "the symbol resolves, so registration succeeds")
|
||||||
|
let mf: Manifold = transduce("x", "fingerprint")
|
||||||
|
let _c: Int = check(1 - manifold_is(mf), "a single vector is not a transduction")
|
||||||
|
|
||||||
|
println("the one-part case is a size-one manifold, not a bare vector")
|
||||||
|
let g1: Geometry = geometry_new(3)
|
||||||
|
let s1: Int = geometry_set(g1, 0, 5.0)
|
||||||
|
let ms: Manifold = manifold_single("level", "scalar", g1)
|
||||||
|
let _c: Int = check(manifold_is(ms), "manifold_single yields a real Manifold")
|
||||||
|
let _c: Int = check(eq_int(manifold_size(ms), 1), "of size one — visibly degenerate, not hidden")
|
||||||
|
let fg: Int = geometry_free(g1)
|
||||||
|
let fs: Int = manifold_free(ms)
|
||||||
|
|
||||||
|
println("no organ is still reported as no organ")
|
||||||
|
let me: Manifold = transduce("anything", "echolocation")
|
||||||
|
let _c: Int = check(1 - manifold_is(me), "no realizer means no manifold, not a fake one")
|
||||||
|
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
|
||||||
// Reaching here means nothing called exit(1) along the way.
|
// Reaching here means nothing called exit(1) along the way.
|
||||||
println("")
|
println("")
|
||||||
|
|||||||
+883
-108
File diff suppressed because it is too large
Load Diff
+66
-11
@@ -625,20 +625,70 @@ el_val_t geometry_free(el_val_t g); /* 1 if freed, 0 if not a
|
|||||||
el_val_t geometry_from_f32le_hex(el_val_t hex); /* 0 on empty/odd-length/non-hex */
|
el_val_t geometry_from_f32le_hex(el_val_t hex); /* 0 on empty/odd-length/non-hex */
|
||||||
el_val_t geometry_to_f32le_hex(el_val_t g); /* "" if not a Geometry */
|
el_val_t geometry_to_f32le_hex(el_val_t g); /* "" if not a Geometry */
|
||||||
|
|
||||||
/* ── Realizers + transduce ───────────────────────────────────────────────────
|
/* ── Manifold: the result of a transduction ──────────────────────────────────
|
||||||
* A REALIZER maps one modality into geometry. Registration is by NAME, so a
|
* A transduced signal is a SUBGRAPH — named components, each with its own
|
||||||
* new modality never requires a runtime patch: every El `fn name(...)`
|
* geometry, plus typed weighted relations among them — not a single vector.
|
||||||
* compiles to a global C symbol with that exact name, and the registry
|
* One vector is a fingerprint: matchable, rankable, and nothing else. A song
|
||||||
* resolves it with dlsym against the running binary — the same mechanism
|
* decomposes into pitch, interval, rhythm, harmonic function; the song IS the
|
||||||
* http_set_handler already relies on.
|
* structure of those relations, and collapsing it to a point discards exactly
|
||||||
|
* what made it reasonable-about. See el_runtime.c ("Manifold") for the full
|
||||||
|
* rationale, the key-addressing rule, and the ownership contract.
|
||||||
*
|
*
|
||||||
* fn tone_realizer(signal: String) -> Geometry { ... }
|
* Components are addressed BY KEY, never by index, because the key is what
|
||||||
|
* survives persistence: a component becomes a node, and it is separately
|
||||||
|
* groundable precisely because it is separately named. Relation weight IS the
|
||||||
|
* grounding (correspondence-and-censorship.md §1) — one quantity, no separate
|
||||||
|
* score, nothing computed on read.
|
||||||
|
*
|
||||||
|
* OWNERSHIP: a Manifold is owned by the El caller and released with
|
||||||
|
* manifold_free, which also releases every component's geometry. manifold_add
|
||||||
|
* COPIES the geometry it is given and manifold_geometry RETURNS a copy, so no
|
||||||
|
* component's vector is ever aliased in either direction. */
|
||||||
|
el_val_t manifold_new(void); /* empty; 0 on failure */
|
||||||
|
el_val_t manifold_is(el_val_t m); /* 1 if a live Manifold */
|
||||||
|
el_val_t manifold_add(el_val_t m, el_val_t key, el_val_t role, el_val_t g);
|
||||||
|
/* component index, or -1 on empty/duplicate
|
||||||
|
* key or a value that is not a Geometry */
|
||||||
|
el_val_t manifold_relate(el_val_t m, el_val_t from, el_val_t rel,
|
||||||
|
el_val_t to, el_val_t weight);
|
||||||
|
/* 1 ok / 0 if either endpoint is unknown —
|
||||||
|
* an unresolvable edge is REFUSED, never
|
||||||
|
* silently dropped */
|
||||||
|
el_val_t manifold_size(el_val_t m); /* component count */
|
||||||
|
el_val_t manifold_rel_count(el_val_t m); /* relation count */
|
||||||
|
el_val_t manifold_index_of(el_val_t m, el_val_t key); /* index by key, or -1 */
|
||||||
|
el_val_t manifold_key(el_val_t m, el_val_t i); /* "" if out of range */
|
||||||
|
el_val_t manifold_role(el_val_t m, el_val_t i); /* "" if out of range */
|
||||||
|
el_val_t manifold_geometry(el_val_t m, el_val_t i); /* a COPY the caller frees */
|
||||||
|
el_val_t manifold_rel_from(el_val_t m, el_val_t j); /* source component key */
|
||||||
|
el_val_t manifold_rel_name(el_val_t m, el_val_t j); /* relation name */
|
||||||
|
el_val_t manifold_rel_to(el_val_t m, el_val_t j); /* target component key */
|
||||||
|
el_val_t manifold_rel_weight(el_val_t m, el_val_t j); /* Float — the grounding */
|
||||||
|
el_val_t manifold_single(el_val_t key, el_val_t role, el_val_t g);
|
||||||
|
/* the degenerate one-part case, expressible
|
||||||
|
* but visibly a size-1 manifold rather than
|
||||||
|
* a parallel path back to a bare vector */
|
||||||
|
el_val_t manifold_free(el_val_t m); /* 1 if freed, 0 otherwise */
|
||||||
|
|
||||||
|
/* ── Realizers + transduce ───────────────────────────────────────────────────
|
||||||
|
* A REALIZER DECOMPOSES one modality into components and relations. It does
|
||||||
|
* not encode a signal to a point; that operation is one layer below and is
|
||||||
|
* called geometry. Registration is by NAME, so a new modality never requires a
|
||||||
|
* runtime patch: every El `fn name(...)` compiles to a global C symbol with
|
||||||
|
* that exact name, and the registry resolves it with dlsym against the running
|
||||||
|
* binary — the same mechanism http_set_handler already relies on.
|
||||||
|
*
|
||||||
|
* fn tone_realizer(signal: String) -> Manifold { ... }
|
||||||
* realizer_register("tone", "tone_realizer")
|
* realizer_register("tone", "tone_realizer")
|
||||||
* let g: Geometry = transduce(sample, "tone")
|
* let m: Manifold = transduce(sample, "tone")
|
||||||
*/
|
*
|
||||||
|
* SUPERSEDES #144's `transduce -> Geometry`. A realizer that still returns a
|
||||||
|
* bare Geometry now transduces NOTHING (transduce returns 0), deliberately: an
|
||||||
|
* organ that only fingerprints must not be indistinguishable from a working
|
||||||
|
* one. A modality with genuinely one part says so with manifold_single. */
|
||||||
el_val_t realizer_register(el_val_t modality, el_val_t fn_name); /* 1 ok / 0 unresolved */
|
el_val_t realizer_register(el_val_t modality, el_val_t fn_name); /* 1 ok / 0 unresolved */
|
||||||
el_val_t realizer_has(el_val_t modality); /* 1 if a realizer is registered */
|
el_val_t realizer_has(el_val_t modality); /* 1 if a realizer is registered */
|
||||||
el_val_t transduce(el_val_t signal, el_val_t modality); /* Geometry, or 0 if no organ */
|
el_val_t transduce(el_val_t signal, el_val_t modality); /* Manifold, or 0 if no organ */
|
||||||
|
|
||||||
/* ── Engram local graph primitives ───────────────────────────────────────────
|
/* ── Engram local graph primitives ───────────────────────────────────────────
|
||||||
* Operate on the CGI's local Engram knowledge graph.
|
* Operate on the CGI's local Engram knowledge graph.
|
||||||
@@ -732,8 +782,13 @@ el_val_t engram_geo_analogy_json(el_val_t a_seeds, el_val_t b_seeds);
|
|||||||
el_val_t engram_reason_analogy_json(el_val_t a_seeds, el_val_t b_seeds, el_val_t c_seeds);
|
el_val_t engram_reason_analogy_json(el_val_t a_seeds, el_val_t b_seeds, el_val_t c_seeds);
|
||||||
/* COGNITION (2026-08-14): THE ONE OPERATION + grounding, surfaced live. */
|
/* COGNITION (2026-08-14): THE ONE OPERATION + grounding, surfaced live. */
|
||||||
el_val_t engram_think_json(el_val_t seeds, el_val_t faculty);
|
el_val_t engram_think_json(el_val_t seeds, el_val_t faculty);
|
||||||
|
/* GROUNDING (2026-08-16): grounding is an attribute of the RELATION and it IS the
|
||||||
|
* hebbian weight. ground reads; ground_record writes; trajectory reads the chain. */
|
||||||
el_val_t engram_ground_json(el_val_t claim, el_val_t evidence, el_val_t for_whom);
|
el_val_t engram_ground_json(el_val_t claim, el_val_t evidence, el_val_t for_whom);
|
||||||
el_val_t engram_assert_json(el_val_t claim_id, el_val_t for_whom, el_val_t floor);
|
el_val_t engram_ground_record_json(el_val_t claim, el_val_t evidence,
|
||||||
|
el_val_t provenance, el_val_t floor);
|
||||||
|
el_val_t engram_ground_trajectory_json(el_val_t claim, el_val_t evidence);
|
||||||
|
el_val_t engram_assert_json(el_val_t claim_id, el_val_t for_whom, el_val_t floor, el_val_t rel_floor);
|
||||||
el_val_t engram_attend_json(el_val_t node_id, el_val_t observer, el_val_t salience);
|
el_val_t engram_attend_json(el_val_t node_id, el_val_t observer, el_val_t salience);
|
||||||
el_val_t engram_correspondence_beat_json(el_val_t seeds, el_val_t faculty, el_val_t keystone);
|
el_val_t engram_correspondence_beat_json(el_val_t seeds, el_val_t faculty, el_val_t keystone);
|
||||||
el_val_t engram_consolidate_permanence(el_val_t node_id);
|
el_val_t engram_consolidate_permanence(el_val_t node_id);
|
||||||
|
|||||||
+372
-29
@@ -246,14 +246,6 @@ static int put_edge(EngramPagedStore* s, const char* id, const char* from, const
|
|||||||
e.metadata = (char*)meta;
|
e.metadata = (char*)meta;
|
||||||
return store_put_edge(s, &e);
|
return store_put_edge(s, &e);
|
||||||
}
|
}
|
||||||
int cog_ground_edge(EngramPagedStore* s, const char* claim_id,
|
|
||||||
const char* evidence_id, double grounding, const char* for_whom) {
|
|
||||||
if (!s || !claim_id || !evidence_id) return -1;
|
|
||||||
char id[512], meta[256];
|
|
||||||
snprintf(id, sizeof id, "gb-%s-%s-%s", claim_id, evidence_id, for_whom ? for_whom : "global");
|
|
||||||
snprintf(meta, sizeof meta, "for_whom=%s", for_whom ? for_whom : "-");
|
|
||||||
return put_edge(s, id, claim_id, evidence_id, COG_GROUNDED_BY_RELATION, grounding, meta);
|
|
||||||
}
|
|
||||||
int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
||||||
const char* observer_id, double salience) {
|
const char* observer_id, double salience) {
|
||||||
if (!s || !node_id || !observer_id) return -1;
|
if (!s || !node_id || !observer_id) return -1;
|
||||||
@@ -261,35 +253,386 @@ int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
|||||||
snprintf(id, sizeof id, "st-%s-%s", node_id, observer_id);
|
snprintf(id, sizeof id, "st-%s-%s", node_id, observer_id);
|
||||||
return put_edge(s, id, node_id, observer_id, COG_SALIENT_TO_RELATION, salience, NULL);
|
return put_edge(s, id, node_id, observer_id, COG_SALIENT_TO_RELATION, salience, NULL);
|
||||||
}
|
}
|
||||||
int cog_assert_gate(EngramPagedStore* s, const char* claim_id,
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
const char* for_whom, double floor) {
|
* §7 GROUNDING IS THE EDGE'S WEIGHT, AND THE WEIGHT IS A VECTOR.
|
||||||
if (!s || !claim_id) return -1;
|
* See engram_cognition.h §7 for the model and for the measurements the two
|
||||||
if (!(floor > 0)) floor = 0.5;
|
* design decisions (thirteen regions, min aggregate) rest on.
|
||||||
StoreEdge* edges = NULL; size_t n = 0;
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||||
if (store_get_edges_from(s, claim_id, &edges, &n) < 0) return -1;
|
|
||||||
double best = 0.0; int found = 0;
|
/* ── The one decay model. Moved here verbatim from el_runtime.c's
|
||||||
for (size_t i = 0; i < n; i++) {
|
* engram_temporal_decay so nodes and edges share a single implementation and a
|
||||||
if (!edges[i].relation || strcmp(edges[i].relation, COG_GROUNDED_BY_RELATION) != 0) continue;
|
* single set of constants; engram_temporal_decay now delegates. Bit-identical
|
||||||
/* grounded-for-whom: match observer if requested; global (for_whom=-) always counts */
|
* for nodes: reinforcements := activation_count, lambda_override :=
|
||||||
int match = 1;
|
* temporal_decay_rate.
|
||||||
if (for_whom && edges[i].metadata) {
|
*
|
||||||
const char* fw = strstr(edges[i].metadata, "for_whom=");
|
* This is what makes decay ANALYTIC rather than sampled: between two recorded
|
||||||
if (fw) { fw += 9; if (strcmp(fw, for_whom) != 0 && strcmp(fw, "-") != 0) match = 0; }
|
* versions the trajectory is not unknown, it is known in closed form from the
|
||||||
}
|
* last point and elapsed time. Store the point, read the curve. */
|
||||||
if (match) { found = 1; if (edges[i].weight > best) best = edges[i].weight; }
|
double cog_decay_factor(int64_t age_ms, double reinforcements, double lambda_override) {
|
||||||
}
|
if (age_ms <= 0) return 1.0;
|
||||||
store_edges_free(edges, n);
|
double lambda = (lambda_override > 0.0) ? lambda_override : COG_DECAY_LAMBDA;
|
||||||
if (!found) return 0; /* ungrounded => refuse assertion (still held) */
|
double age_hours = (double)age_ms / 3600000.0;
|
||||||
return (best >= floor) ? 1 : 0;
|
if (reinforcements < 0) reinforcements = 0;
|
||||||
|
double t_half = COG_T_HALF_HOURS * (1.0 + log(1.0 + reinforcements));
|
||||||
|
double factor = exp(-lambda * age_hours / t_half);
|
||||||
|
if (factor < COG_DECAY_FLOOR) factor = COG_DECAY_FLOOR;
|
||||||
|
return factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* cog_prov_name(CogProvClass p) {
|
||||||
|
switch (p) {
|
||||||
|
case COG_PROV_OBSERVED: return "observed";
|
||||||
|
case COG_PROV_INFERRED: return "inferred";
|
||||||
|
case COG_PROV_TOLD: return "told";
|
||||||
|
case COG_PROV_IMPRINTED: return "imprinted";
|
||||||
|
default: return "unset";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CogProvClass cog_prov_parse(const char* s) {
|
||||||
|
if (!s) return COG_PROV_UNSET;
|
||||||
|
if (!strcmp(s, "observed")) return COG_PROV_OBSERVED;
|
||||||
|
if (!strcmp(s, "inferred")) return COG_PROV_INFERRED;
|
||||||
|
if (!strcmp(s, "told")) return COG_PROV_TOLD;
|
||||||
|
if (!strcmp(s, "imprinted")) return COG_PROV_IMPRINTED;
|
||||||
|
return COG_PROV_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Locate the GRD1 block in an edge's metadata. It is always the tail; anything
|
||||||
|
* ahead of it is the edge's pre-existing metadata, preserved verbatim. */
|
||||||
|
static const char* cog_grd_find(const char* meta) {
|
||||||
|
if (!meta) return NULL;
|
||||||
|
size_t ml = strlen(COG_GROUNDING_META_MAGIC);
|
||||||
|
if (strncmp(meta, COG_GROUNDING_META_MAGIC, ml) == 0) return meta;
|
||||||
|
const char* p = meta;
|
||||||
|
while ((p = strstr(p, COG_GROUNDING_META_MAGIC)) != NULL) {
|
||||||
|
if (p > meta && p[-1] == '\n') return p;
|
||||||
|
p += ml;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cog_grounding_parse(const StoreEdge* e, int64_t now_ms, CogGrounding* out) {
|
||||||
|
if (!e || !out) return -1;
|
||||||
|
memset(out, 0, sizeof *out);
|
||||||
|
|
||||||
|
/* Two dimensions exist on every edge whether or not grounding has ever been
|
||||||
|
* established, because they ARE existing substrate rather than new fields:
|
||||||
|
* associative — the accrued hebb, with its existing dynamics;
|
||||||
|
* polarity — the signed authored weight. `inhibitory` is precisely this
|
||||||
|
* distinction crushed to one bit, so it is the seed sign. */
|
||||||
|
out->associative = e->hebb;
|
||||||
|
out->polarity = e->inhibitory ? -e->weight : e->weight;
|
||||||
|
out->prov = COG_PROV_UNSET;
|
||||||
|
out->ts = e->last_fired > 0 ? e->last_fired : e->updated_at;
|
||||||
|
|
||||||
|
const char* blk = cog_grd_find(e->metadata);
|
||||||
|
if (blk) {
|
||||||
|
out->present = 1;
|
||||||
|
char* copy = dupstr(blk);
|
||||||
|
if (!copy) return -1;
|
||||||
|
for (char* line = strtok(copy, "\n"); line; line = strtok(NULL, "\n")) {
|
||||||
|
if (line[0] == '\0') continue;
|
||||||
|
char tag = line[0];
|
||||||
|
const char* rest = line + 1; while (*rest == ' ') rest++;
|
||||||
|
if (tag == 'w') { /* the four numeric dimensions */
|
||||||
|
double v[4] = {0,0,0,0}; parse_floats(rest, v, 4);
|
||||||
|
out->factual = v[0]; out->relational = v[1];
|
||||||
|
out->associative = v[2]; out->polarity = v[3];
|
||||||
|
} else if (tag == 'k') { /* provenance class */
|
||||||
|
out->prov = cog_prov_parse(rest);
|
||||||
|
} else if (tag == 't') { /* timestamp + seq + reinforcements */
|
||||||
|
double v[3] = {0,0,0}; parse_floats(rest, v, 3);
|
||||||
|
out->ts = (int64_t)v[0]; out->seq = (int64_t)v[1]; out->reinforcements = v[2];
|
||||||
|
} else if (tag == 'd') {
|
||||||
|
double v[3] = {0,0,0}; parse_floats(rest, v, 3);
|
||||||
|
out->fac_proj = v[0]; out->rel_proj = v[1]; out->cos_angle = v[2];
|
||||||
|
} else if (tag == 'v') {
|
||||||
|
snprintf(out->binding_value, sizeof out->binding_value, "%s", rest);
|
||||||
|
} else if (tag == 'c') {
|
||||||
|
double v[2] = {0,0}; parse_floats(rest, v, 2);
|
||||||
|
out->floor_at_record = v[0]; out->rel_floor_at_record = v[1];
|
||||||
|
} else if (tag == 'p') {
|
||||||
|
snprintf(out->prev_edge, sizeof out->prev_edge, "%s", rest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(copy);
|
||||||
|
}
|
||||||
|
out->agreement = (out->cos_angle > 0) ? 1 : (out->cos_angle < 0 ? -1 : 0);
|
||||||
|
|
||||||
|
/* ── DERIVED. Nothing below this line is ever serialized. Recency, decay and
|
||||||
|
* staleness are read off the curve; storing them is how a number ends up
|
||||||
|
* asserting something nothing computed (§8.1 / spec §2). */
|
||||||
|
out->age_ms = (out->ts > 0 && now_ms > out->ts) ? (now_ms - out->ts) : 0;
|
||||||
|
out->decay = cog_decay_factor(out->age_ms, out->reinforcements, 0.0);
|
||||||
|
out->factual_now = out->factual * out->decay;
|
||||||
|
out->relational_now = out->relational * out->decay;
|
||||||
|
out->associative_now = out->associative * out->decay;
|
||||||
|
out->stale = (out->present && out->floor_at_record > 0 &&
|
||||||
|
out->factual_now < out->floor_at_record) ? 1 : 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* cog_grounding_metadata(const char* base_meta, const CogGrounding* g) {
|
||||||
|
if (!g) return NULL;
|
||||||
|
size_t keep = 0;
|
||||||
|
if (base_meta) {
|
||||||
|
const char* blk = cog_grd_find(base_meta);
|
||||||
|
keep = blk ? (size_t)(blk - base_meta) : strlen(base_meta);
|
||||||
|
while (keep > 0 && base_meta[keep - 1] == '\n') keep--;
|
||||||
|
}
|
||||||
|
size_t cap = keep + 1024;
|
||||||
|
char* buf = malloc(cap); if (!buf) return NULL;
|
||||||
|
size_t o = 0;
|
||||||
|
if (keep) { memcpy(buf, base_meta, keep); o = keep; buf[o++] = '\n'; }
|
||||||
|
o += (size_t)snprintf(buf + o, cap - o, "%s\n", COG_GROUNDING_META_MAGIC);
|
||||||
|
/* STORED ONLY. factual / relational / associative / polarity / provenance /
|
||||||
|
* timestamp — plus the joint state a decision saw. No confidence, no
|
||||||
|
* recency, no staleness, no volatility: those are read off the curve. */
|
||||||
|
o += (size_t)snprintf(buf + o, cap - o, "w %.9g %.9g %.9g %.9g\n",
|
||||||
|
g->factual, g->relational, g->associative, g->polarity);
|
||||||
|
o += (size_t)snprintf(buf + o, cap - o, "k %s\n", cog_prov_name(g->prov));
|
||||||
|
o += (size_t)snprintf(buf + o, cap - o, "t %lld %lld %.9g\n",
|
||||||
|
(long long)g->ts, (long long)g->seq, g->reinforcements);
|
||||||
|
o += (size_t)snprintf(buf + o, cap - o, "d %.9g %.9g %.9g\n",
|
||||||
|
g->fac_proj, g->rel_proj, g->cos_angle);
|
||||||
|
o += (size_t)snprintf(buf + o, cap - o, "v %s\n", g->binding_value[0] ? g->binding_value : "-");
|
||||||
|
o += (size_t)snprintf(buf + o, cap - o, "c %.9g %.9g\n", g->floor_at_record, g->rel_floor_at_record);
|
||||||
|
if (g->prev_edge[0]) o += (size_t)snprintf(buf + o, cap - o, "p %s\n", g->prev_edge);
|
||||||
|
(void)o;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Consequence, not epsilon. Every test is a floor crossing or a sign change,
|
||||||
|
* both exact. Ordered so the two INHERENT (discrete) moves are reported in
|
||||||
|
* preference to the graded ones, because they bypass the salience gate. */
|
||||||
|
CogSignificance cog_grounding_significant(const CogGrounding* prev,
|
||||||
|
const CogGrounding* now,
|
||||||
|
double floor, double rel_floor) {
|
||||||
|
if (!now) return COG_SIG_NONE;
|
||||||
|
if (!prev || !prev->present) return COG_SIG_FIRST_RECORD;
|
||||||
|
|
||||||
|
/* INHERENT 1 — polarity sign flip. Ignorance and disagreement are different
|
||||||
|
* states, and support → contradiction is a change of state rather than a
|
||||||
|
* drift, so no threshold applies. Comparing signs, with zero its own class. */
|
||||||
|
{
|
||||||
|
int sp = prev->polarity > 0 ? 1 : (prev->polarity < 0 ? -1 : 0);
|
||||||
|
int sn = now->polarity > 0 ? 1 : (now->polarity < 0 ? -1 : 0);
|
||||||
|
if (sp != sn) return COG_SIG_POLARITY_FLIP;
|
||||||
|
}
|
||||||
|
/* INHERENT 2 — provenance class change. told → observed is a categorical
|
||||||
|
* upgrade in what the relation is entitled to, not a movement along an axis. */
|
||||||
|
if (prev->prov != now->prov) return COG_SIG_PROVENANCE_CHANGE;
|
||||||
|
|
||||||
|
/* Crossing an assert floor — the move changes whether this relation can be
|
||||||
|
* spoken. Compared on the DECAYED values, because that is what the gate reads. */
|
||||||
|
if ((prev->factual_now >= floor) != (now->factual_now >= floor)) return COG_SIG_FACTUAL_FLOOR;
|
||||||
|
if ((prev->relational_now >= rel_floor) != (now->relational_now >= rel_floor)) return COG_SIG_RELATIONAL_FLOOR;
|
||||||
|
|
||||||
|
/* Flipping factual/relational agreement — the relation stops being "true and
|
||||||
|
* meaningful" and becomes "true and misapplied", or the reverse. This is the
|
||||||
|
* 911/CPS contradiction as a measured event rather than a reviewable one. */
|
||||||
|
if (prev->agreement != now->agreement) return COG_SIG_AGREEMENT_FLIP;
|
||||||
|
|
||||||
|
/* A gradient reversing — the evidence stopped pulling the claim toward it and
|
||||||
|
* began pushing it away, or the same on the values axis. */
|
||||||
|
if ((prev->fac_proj > 0) != (now->fac_proj > 0)) return COG_SIG_DIRECTION_REVERSAL;
|
||||||
|
if ((prev->rel_proj > 0) != (now->rel_proj > 0)) return COG_SIG_DIRECTION_REVERSAL;
|
||||||
|
|
||||||
|
return COG_SIG_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cog_significance_inherent(CogSignificance s) {
|
||||||
|
return (s == COG_SIG_FIRST_RECORD || s == COG_SIG_POLARITY_FLIP ||
|
||||||
|
s == COG_SIG_PROVENANCE_CHANGE) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* cog_significance_name(CogSignificance s) {
|
||||||
|
switch (s) {
|
||||||
|
case COG_SIG_FIRST_RECORD: return "first-record";
|
||||||
|
case COG_SIG_POLARITY_FLIP: return "polarity-sign-flip";
|
||||||
|
case COG_SIG_PROVENANCE_CHANGE: return "provenance-class-change";
|
||||||
|
case COG_SIG_FACTUAL_FLOOR: return "factual-floor-crossed";
|
||||||
|
case COG_SIG_RELATIONAL_FLOOR: return "relational-floor-crossed";
|
||||||
|
case COG_SIG_AGREEMENT_FLIP: return "agreement-sign-flip";
|
||||||
|
case COG_SIG_DIRECTION_REVERSAL: return "gradient-direction-reversal";
|
||||||
|
default: return "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Recording: a NEW edge record. The predecessor is never touched. ────────── */
|
||||||
|
int cog_grounding_record(EngramPagedStore* s, const StoreEdge* base,
|
||||||
|
const CogGrounding* g, char* out_id, size_t out_id_cap) {
|
||||||
|
if (!s || !base || !base->id || !g) return -1;
|
||||||
|
char root[192];
|
||||||
|
snprintf(root, sizeof root, "%s", base->id);
|
||||||
|
char* hash = strchr(root, '#'); if (hash) *hash = '\0';
|
||||||
|
|
||||||
|
int seq = (int)g->seq + 1;
|
||||||
|
char vid[224];
|
||||||
|
snprintf(vid, sizeof vid, "%s#%d", root, seq);
|
||||||
|
|
||||||
|
CogGrounding rec = *g;
|
||||||
|
rec.seq = seq;
|
||||||
|
snprintf(rec.prev_edge, sizeof rec.prev_edge, "%s", base->id);
|
||||||
|
|
||||||
|
char* meta = cog_grounding_metadata(base->metadata, &rec);
|
||||||
|
if (!meta) return -1;
|
||||||
|
|
||||||
|
StoreEdge e; memset(&e, 0, sizeof e);
|
||||||
|
e.id = vid; e.from_id = base->from_id; e.to_id = base->to_id;
|
||||||
|
e.relation = base->relation; e.metadata = meta;
|
||||||
|
/* The vector IS the weight, so the scalar fields carry their dimensions:
|
||||||
|
* `weight` the magnitude of polarity, `inhibitory` its sign, `hebb` the
|
||||||
|
* associative strength. Nothing here is a second copy of a derived value. */
|
||||||
|
e.weight = rec.polarity < 0 ? -rec.polarity : rec.polarity;
|
||||||
|
e.inhibitory = rec.polarity < 0 ? 1 : 0;
|
||||||
|
e.hebb = rec.associative;
|
||||||
|
e.confidence = base->confidence;
|
||||||
|
e.created_at = base->created_at;
|
||||||
|
e.updated_at = rec.ts;
|
||||||
|
e.last_fired = rec.ts;
|
||||||
|
e.layer_id = base->layer_id;
|
||||||
|
int rc = store_put_edge(s, &e);
|
||||||
|
free(meta);
|
||||||
|
if (rc != 0) return -1;
|
||||||
|
if (out_id && out_id_cap) snprintf(out_id, out_id_cap, "%s", vid);
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cog_grounding_head(EngramPagedStore* s, const char* base_id,
|
||||||
|
StoreEdge* out, int max_versions) {
|
||||||
|
if (!s || !base_id || !out) return -1;
|
||||||
|
if (max_versions <= 0) max_versions = 64;
|
||||||
|
char root[192]; snprintf(root, sizeof root, "%s", base_id);
|
||||||
|
char* hash = strchr(root, '#'); if (hash) *hash = '\0';
|
||||||
|
|
||||||
|
StoreEdge cur; memset(&cur, 0, sizeof cur);
|
||||||
|
if (store_get_edge(s, root, &cur) != 1) return -1;
|
||||||
|
int found = 0;
|
||||||
|
for (int v = 1; v <= max_versions; v++) {
|
||||||
|
char vid[224]; snprintf(vid, sizeof vid, "%s#%d", root, v);
|
||||||
|
StoreEdge nx;
|
||||||
|
if (store_get_edge(s, vid, &nx) != 1) break;
|
||||||
|
store_edge_free(&cur); cur = nx; found = v;
|
||||||
|
}
|
||||||
|
*out = cur;
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── VOLATILITY AND DRIFT: derived from the chain, stored nowhere. The series
|
||||||
|
* exists only because nothing was destroyed, which is the whole return on
|
||||||
|
* immutability — a derivative for free. */
|
||||||
|
int cog_grounding_trajectory(EngramPagedStore* s, const char* base_id,
|
||||||
|
int64_t now_ms, CogTrajectory* out) {
|
||||||
|
if (!s || !base_id || !out) return -1;
|
||||||
|
memset(out, 0, sizeof *out);
|
||||||
|
char root[192]; snprintf(root, sizeof root, "%s", base_id);
|
||||||
|
char* hash = strchr(root, '#'); if (hash) *hash = '\0';
|
||||||
|
|
||||||
|
double pf = 0, pr = 0, f0 = 0, r0 = 0, fN = 0, rN = 0;
|
||||||
|
double sum_df = 0, sum_dr = 0;
|
||||||
|
int n = 0;
|
||||||
|
for (int v = 0; v <= 64; v++) {
|
||||||
|
char vid[224];
|
||||||
|
if (v == 0) snprintf(vid, sizeof vid, "%s", root);
|
||||||
|
else snprintf(vid, sizeof vid, "%s#%d", root, v);
|
||||||
|
StoreEdge e;
|
||||||
|
if (store_get_edge(s, vid, &e) != 1) { if (v) break; else continue; }
|
||||||
|
CogGrounding g;
|
||||||
|
if (cog_grounding_parse(&e, now_ms, &g) == 0) {
|
||||||
|
if (n == 0) { f0 = g.factual; r0 = g.relational; }
|
||||||
|
else { sum_df += fabs(g.factual - pf); sum_dr += fabs(g.relational - pr); }
|
||||||
|
pf = g.factual; pr = g.relational; fN = pf; rN = pr;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
store_edge_free(&e);
|
||||||
|
}
|
||||||
|
out->n_versions = n;
|
||||||
|
if (n > 1) {
|
||||||
|
out->factual_volatility = sum_df / (double)(n - 1);
|
||||||
|
out->relational_volatility = sum_dr / (double)(n - 1);
|
||||||
|
}
|
||||||
|
out->factual_drift = fN - f0;
|
||||||
|
out->relational_drift = rN - r0;
|
||||||
|
/* "STAYED TRUE, BECAME WRONG" — the event the joint record makes visible and
|
||||||
|
* that per-dimension versioning would have destroyed: the fact held while
|
||||||
|
* the meaning degraded. Expressed as signs, so there is no tolerance here
|
||||||
|
* either: factual did not fall, relational did. */
|
||||||
|
out->stayed_true_became_wrong =
|
||||||
|
(n > 1 && out->factual_drift >= 0 && out->relational_drift < 0) ? 1 : 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Assertion gates on BOTH floors. Traversal is untouched: activation still
|
||||||
|
* conducts on the factual/associative side, so a relation can remain thinkable
|
||||||
|
* while ceasing to be assertable. That gap is where the wide angles live. ──── */
|
||||||
|
int cog_assert_two_axis(EngramPagedStore* s, const char* claim_id,
|
||||||
|
double floor, double rel_floor, int64_t now_ms,
|
||||||
|
CogAssertion* out) {
|
||||||
|
if (!s || !claim_id || !out) return -1;
|
||||||
|
memset(out, 0, sizeof *out);
|
||||||
|
if (!(floor > 0)) floor = 0.5;
|
||||||
|
if (!(rel_floor > 0)) rel_floor = floor;
|
||||||
|
|
||||||
|
/* still_held is DERIVED, not a literal (§8.1). Holding is unconditional —
|
||||||
|
* the store gates nothing — so the question the field actually answers is
|
||||||
|
* whether the content is present and live. */
|
||||||
|
StoreNode n;
|
||||||
|
if (store_get_node(s, claim_id, &n) == 1) { out->still_held = !n.tombstoned; store_node_free(&n); }
|
||||||
|
else out->still_held = 0;
|
||||||
|
|
||||||
|
double best = -1.0;
|
||||||
|
for (int dir = 0; dir < 2; dir++) {
|
||||||
|
StoreEdge* edges = NULL; size_t ne = 0;
|
||||||
|
int rc = dir == 0 ? store_get_edges_from(s, claim_id, &edges, &ne)
|
||||||
|
: store_get_edges_to (s, claim_id, &edges, &ne);
|
||||||
|
if (rc < 0) continue;
|
||||||
|
for (size_t i = 0; i < ne; i++) {
|
||||||
|
if (edges[i].tombstoned) continue;
|
||||||
|
CogGrounding g;
|
||||||
|
if (cog_grounding_parse(&edges[i], now_ms, &g) != 0) continue;
|
||||||
|
out->n_edges++;
|
||||||
|
out->found = 1;
|
||||||
|
if (g.factual_now > best) {
|
||||||
|
best = g.factual_now;
|
||||||
|
out->factual = g.factual_now;
|
||||||
|
out->relational = g.relational_now; /* the SAME edge, not a max */
|
||||||
|
out->polarity = g.polarity;
|
||||||
|
out->cos_angle = g.cos_angle;
|
||||||
|
out->agreement = g.agreement;
|
||||||
|
out->prov = g.prov;
|
||||||
|
out->relational_established = g.present;
|
||||||
|
snprintf(out->best_edge, sizeof out->best_edge, "%s", edges[i].id ? edges[i].id : "");
|
||||||
|
snprintf(out->binding_value, sizeof out->binding_value, "%s", g.binding_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store_edges_free(edges, ne);
|
||||||
|
}
|
||||||
|
/* BOTH floors, and an unestablished relational axis does NOT pass by default
|
||||||
|
* — defaulting it to passing is the exemption §0 forbids. A negative polarity
|
||||||
|
* is a relation that actively contradicts and can never license assertion. */
|
||||||
|
out->may_assert = (out->found && out->relational_established &&
|
||||||
|
out->polarity > 0 &&
|
||||||
|
out->factual >= floor && out->relational >= rel_floor) ? 1 : 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════ THE CORRESPONDENCE-LOOP ═════ */
|
/* ═══════════════════════════════════════════════ THE CORRESPONDENCE-LOOP ═════ */
|
||||||
int engram_correspondence_beat(const GeoDescriptor* region, const float* anchor,
|
int engram_correspondence_beat(const GeoDescriptor* region, const float* anchor,
|
||||||
double outcome_y, CogStance* stance,
|
double outcome_y, CogStance* stance,
|
||||||
int learn, double max_step, CogBeatResult* out) {
|
int learn, double max_step, CogBeatResult* out) {
|
||||||
if (!region || !stance || !out) return -1;
|
if (!region || !stance || !out) return -1;
|
||||||
memset(out, 0, sizeof *out);
|
memset(out, 0, sizeof *out);
|
||||||
if (stance->keystone) { learn = 0; out->wrote_keystone = 1; } /* §6: never write a keystone */
|
/* 2026-08-16: the keystone block is GONE. It refused to learn about the
|
||||||
|
* reference frame, which does not make it a good reference — it makes it
|
||||||
|
* unexaminable, trading circular calibration for an ungroundable one (spec
|
||||||
|
* §2). Measured cost of the block: on the keystone region the beat reported
|
||||||
|
* 0.00% brier reduction over n_trials 0 — it never ran, so nothing about the
|
||||||
|
* self was ever calibrated OR falsifiable. What replaces it is a provenance
|
||||||
|
* constraint, not a permission: cog_grounding_downstream refuses evidence
|
||||||
|
* that is downstream of the region being calibrated, for every region alike.
|
||||||
|
* `wrote_keystone` is retained as a reporting field only and is always 0. */
|
||||||
|
|
||||||
GeoGradient g;
|
GeoGradient g;
|
||||||
if (engram_think(region, anchor, stance, &g) != 0) return -1; /* PREDICTION */
|
if (engram_think(region, anchor, stance, &g) != 0) return -1; /* PREDICTION */
|
||||||
|
|||||||
+269
-17
@@ -23,7 +23,7 @@
|
|||||||
* and a region, and grounded-for-whom.
|
* and a region, and grounded-for-whom.
|
||||||
*
|
*
|
||||||
* PURE + (mostly) READ-ONLY, stdlib + libm only. think() and the warp are pure
|
* PURE + (mostly) READ-ONLY, stdlib + libm only. think() and the warp are pure
|
||||||
* over their inputs. Persistence (Stance <-> StoreNode, grounded-by edges) is the
|
* over their inputs. Persistence (Stance <-> StoreNode, edge grounding vectors) is the
|
||||||
* only part that touches the store, and it is additive / supersede / tombstone —
|
* only part that touches the store, and it is additive / supersede / tombstone —
|
||||||
* never mutate-in-place, never delete. It NEVER touches the live daemon: all
|
* never mutate-in-place, never delete. It NEVER touches the live daemon: all
|
||||||
* offline against a scratch store, per the design's rails.
|
* offline against a scratch store, per the design's rails.
|
||||||
@@ -152,30 +152,25 @@ int engram_express(const GeoGradient* g, const float* anchor, float* out_point);
|
|||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
* §5 HOLD vs GROUND vs ASSERT. Holding is unconditional (the store gates nothing).
|
* §5 HOLD vs GROUND vs ASSERT. Holding is unconditional (the store gates nothing).
|
||||||
* Grounding is a RELATION — a "grounded-by" edge, probabilistic, grounded-for-whom.
|
* Grounding is an ATTRIBUTE OF a relation — carried on the edge itself, as a
|
||||||
* The honesty floor is checked only at ASSERTION.
|
* vector (§7). The honesty floor is checked only at ASSERTION, on both axes.
|
||||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||||
#define COG_GROUNDED_BY_RELATION "grounded-by"
|
/* DELETED 2026-08-16: COG_GROUNDED_BY_RELATION and cog_ground_edge.
|
||||||
|
*
|
||||||
|
* A "grounded-by" edge models grounding as a relation BETWEEN two nodes. It is a
|
||||||
|
* property OF a relation — and it is that relation's weight. Minting a new edge
|
||||||
|
* to carry a score was the error; #147 corrected which endpoints the edge landed
|
||||||
|
* on and left the wrong idea standing. There is nothing to ground a claim
|
||||||
|
* "against" that is not already an edge, and if no edge exists the honest answer
|
||||||
|
* is that the two are not related — not a freshly minted one scoring 0.98.
|
||||||
|
* See §7 for what replaced it. */
|
||||||
#define COG_SALIENT_TO_RELATION "salient-to"
|
#define COG_SALIENT_TO_RELATION "salient-to"
|
||||||
|
|
||||||
/* Write a grounded-by edge (additive). weight = grounding ∈(0,1] from the verifier;
|
|
||||||
* for_whom recorded in edge metadata (grounding is relational). Never a node flag. */
|
|
||||||
int cog_ground_edge(EngramPagedStore* s, const char* claim_id,
|
|
||||||
const char* evidence_id, double grounding, const char* for_whom);
|
|
||||||
|
|
||||||
/* Write/refresh a salient-to edge: salience is RELATIONAL (grounded-for-whom),
|
/* Write/refresh a salient-to edge: salience is RELATIONAL (grounded-for-whom),
|
||||||
* carried on the edge to the observer — not baked into the node scalar (§2.1). */
|
* carried on the edge to the observer — not baked into the node scalar (§2.1). */
|
||||||
int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
||||||
const char* observer_id, double salience);
|
const char* observer_id, double salience);
|
||||||
|
|
||||||
/* The honesty floor — a QUERY at assertion time, NOT a schema constraint. Reads the
|
|
||||||
* claim's stored grounded-by edges (for the given observer) and returns:
|
|
||||||
* 1 = may assert (best grounding >= floor),
|
|
||||||
* 0 = REFUSE assertion (holds unconditionally; only asserting is gated),
|
|
||||||
* <0 = error. The content remains held either way. */
|
|
||||||
int cog_assert_gate(EngramPagedStore* s, const char* claim_id,
|
|
||||||
const char* for_whom, double floor);
|
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
* §4 THE REFLEXIVE CORRESPONDENCE-LOOP — the learning engine. think scores its
|
* §4 THE REFLEXIVE CORRESPONDENCE-LOOP — the learning engine. think scores its
|
||||||
* OWN gradient against outcome, refines the stance on the error, and (optionally)
|
* OWN gradient against outcome, refines the stance on the error, and (optionally)
|
||||||
@@ -209,8 +204,265 @@ int engram_correspondence_beat(const GeoDescriptor* region, const float* anchor,
|
|||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
* §6 METASTABILITY. Keystones (self/values) are read-mostly: the loop reads but
|
* §6 METASTABILITY. Keystones (self/values) are read-mostly: the loop reads but
|
||||||
* never writes them. Mark by stance flag or by a keystone-id set the loop consults.
|
* never writes them. Mark by stance flag or by a keystone-id set the loop consults.
|
||||||
|
*
|
||||||
|
* SUPERSEDED BY §7's PROVENANCE CONSTRAINT (2026-08-16). The keystone flag is a
|
||||||
|
* PERMISSION: it asks who the target is, not where the evidence came from. That
|
||||||
|
* is censorship, and it costs the ability to ever ground the self (spec
|
||||||
|
* correspondence-and-censorship.md §0/§2). The constraint that actually protects
|
||||||
|
* a reference frame is cog_grounding_downstream: a region may not be calibrated
|
||||||
|
* by evidence downstream of itself. These declarations remain only so existing
|
||||||
|
* call sites keep compiling; nothing in the grounding path consults them.
|
||||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||||
typedef struct { const char** ids; int n; } CogKeystoneSet;
|
typedef struct { const char** ids; int n; } CogKeystoneSet;
|
||||||
int cog_is_keystone(const CogKeystoneSet* ks, const CogStance* s);
|
int cog_is_keystone(const CogKeystoneSet* ks, const CogStance* s);
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
* §7 GROUNDING IS THE EDGE'S WEIGHT, AND THE WEIGHT IS A VECTOR
|
||||||
|
* (2026-08-16; spec correspondence-and-censorship.md §2–§6 @ 2b7e4ba.)
|
||||||
|
*
|
||||||
|
* THE MODEL. Grounding is not a subsystem, a score, or a relation BETWEEN nodes.
|
||||||
|
* It is an attribute OF a relation. The graph already IS the grounding structure:
|
||||||
|
* every edge is a grounded relation, and what that relation is worth is carried
|
||||||
|
* on the edge itself. Three things follow, and each DELETES rather than adds:
|
||||||
|
*
|
||||||
|
* 1. `grounded-by` as a relation type does not exist, and cog_ground_edge is
|
||||||
|
* gone. Minting an edge to hold a score models grounding as a relation
|
||||||
|
* between nodes when it is a property of a relation. #147 corrected which
|
||||||
|
* endpoints that edge landed on and left the wrong idea standing.
|
||||||
|
* 2. There is no observer, and no sampling rate. Change is not a consequence of
|
||||||
|
* use — it IS use, the way potentiation is the firing rather than something
|
||||||
|
* that reads the firing and writes a weight. So no supervisor compares a
|
||||||
|
* value to a threshold and decides to persist.
|
||||||
|
* 3. Between two recorded versions the trajectory is not unknown. Decay is a
|
||||||
|
* pure function of the last recorded point and elapsed time, so it is
|
||||||
|
* ANALYTIC: store the point, read the curve.
|
||||||
|
*
|
||||||
|
* WHAT IS *NOT* HERE, DELIBERATELY. An earlier draft of the spec posed "a graph
|
||||||
|
* predicate for evidence downstream of itself" as the hard problem, and this file
|
||||||
|
* briefly contained one. It is withdrawn. Non-circularity is TEMPORAL, not
|
||||||
|
* topological: you cannot recalibrate the ruler while measuring with it, so you
|
||||||
|
* do it when you are not using the frame to act. Reachability could never have
|
||||||
|
* worked — measured on the live store, reachability from the self region over
|
||||||
|
* all relations reaches 89.2% of the graph (10,580 of 11,861 nodes) and 16.0%
|
||||||
|
* over hebbian/semantic relations alone, so the predicate marks essentially all
|
||||||
|
* evidence tainted and the constraint degenerates into the total block that
|
||||||
|
* censorship started as. Nothing replaces it here; the independence is a fact
|
||||||
|
* about engagement, owned by the dreamer, not a fact about the graph.
|
||||||
|
*
|
||||||
|
* ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
* §7.1 THE VECTOR
|
||||||
|
*
|
||||||
|
* The test for a real dimension is whether it can move independently of the
|
||||||
|
* others. Five can, and each maps onto substrate that already exists:
|
||||||
|
*
|
||||||
|
* factual correspondence with evidence. [GRD1]
|
||||||
|
* relational correspondence with values — min over THIRTEEN
|
||||||
|
* value regions, carrying the binding value's NAME. [GRD1]
|
||||||
|
* associative co-activation frequency. This is the edge's `hebb`
|
||||||
|
* field with its existing dynamics — NOT a new one.
|
||||||
|
* Independent by construction: every superstition is
|
||||||
|
* a strong association with no factual grounding.
|
||||||
|
* polarity SIGNED. Near zero means "no support"; NEGATIVE means
|
||||||
|
* "this actively contradicts". The edge's `inhibitory`
|
||||||
|
* bit is exactly this distinction crushed to one bit,
|
||||||
|
* and is carried forward as the seed value. [GRD1]
|
||||||
|
* provenance observed / inferred / told / imprinted. Categorical,
|
||||||
|
* and load-bearing: it governs what the relation is
|
||||||
|
* entitled to. [GRD1]
|
||||||
|
*
|
||||||
|
* Plus a TIMESTAMP, which is what turns the supersession chain into a time
|
||||||
|
* series of vectors rather than a series of numbers.
|
||||||
|
*
|
||||||
|
* DERIVED, THEREFORE NEVER STORED. Confidence (high grounding AND low
|
||||||
|
* volatility), recency (decay read off the curve), staleness (grounding fallen
|
||||||
|
* below its floor), volatility (the derivative of a series nothing destroyed).
|
||||||
|
* Storing confidence separately is how `confidence: 0.5` ends up sitting beside
|
||||||
|
* a zero direction vector, asserting something nothing computed. Every field in
|
||||||
|
* CogGrounding below is marked STORED or DERIVED, and the serializer writes
|
||||||
|
* only the STORED ones.
|
||||||
|
*
|
||||||
|
* THE VALUES REFERENCE IS THIRTEEN REGIONS AND THE AGGREGATE IS MIN.
|
||||||
|
* Measured on the live store: the values root kn-5b606390 `contains` exactly 13
|
||||||
|
* value nodes; pairwise centroid cosine among their regions is min 0.1525,
|
||||||
|
* mean 0.5199, median 0.5282, max 0.9278 — they demonstrably do not form one
|
||||||
|
* region. Against a single union region the individual values sit at cosine
|
||||||
|
* 0.38..0.89, with constraints-as-freedom at 0.3812 and change-is-the-signal at
|
||||||
|
* 0.4677, so a union centroid under-represents precisely the values a claim is
|
||||||
|
* most likely to be measured against. MIN rather than MEAN because a mean lets
|
||||||
|
* strong agreement with twelve values mask a violation of the thirteenth, which
|
||||||
|
* is the mechanism of rationalization; min yields a binding constraint with a
|
||||||
|
* NAME attached rather than a score.
|
||||||
|
*
|
||||||
|
* TRAVERSAL CONDUCTS ON FACTUAL; ASSERTION REQUIRES BOTH. If activation
|
||||||
|
* conducted on relational weight, Neuron could not follow a chain of reasoning
|
||||||
|
* to a conclusion he then rejects — censorship arriving through the spreading
|
||||||
|
* rule. The gap between reachable and assertable is where the wide
|
||||||
|
* factual/relational angles live, and that gap is the interesting part.
|
||||||
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
/* ── The one decay model (moved here from el_runtime.c so that node decay and
|
||||||
|
* edge-grounding decay are a single implementation with a single set of
|
||||||
|
* constants, rather than a model and a parallel copy of it). Half-life scales
|
||||||
|
* with how established the thing is: T_eff = T_HALF · (1 + ln(1 + reinforcements)).
|
||||||
|
* The floor is a preference, not a cliff — max penalty for age alone is 4x.
|
||||||
|
* `lambda_override` > 0 replaces the default rate; 0 means use the default. */
|
||||||
|
#define COG_T_HALF_HOURS 168.0
|
||||||
|
#define COG_DECAY_LAMBDA 0.693147
|
||||||
|
#define COG_DECAY_FLOOR 0.25
|
||||||
|
double cog_decay_factor(int64_t age_ms, double reinforcements, double lambda_override);
|
||||||
|
|
||||||
|
/* The compact vector block carried in the edge's own metadata. Line schema, same
|
||||||
|
* precedent as STNC1 / GEO1. Metadata the edge already carried is preserved
|
||||||
|
* verbatim ahead of the magic line. */
|
||||||
|
#define COG_GROUNDING_META_MAGIC "GRD1"
|
||||||
|
|
||||||
|
/* Provenance class — categorical, and it governs what the relation is entitled
|
||||||
|
* to. A change of class is inherently significant and needs no threshold,
|
||||||
|
* because told → observed is a categorical upgrade, not a drift. */
|
||||||
|
typedef enum {
|
||||||
|
COG_PROV_UNSET = 0,
|
||||||
|
COG_PROV_OBSERVED = 1,
|
||||||
|
COG_PROV_INFERRED = 2,
|
||||||
|
COG_PROV_TOLD = 3,
|
||||||
|
COG_PROV_IMPRINTED = 4
|
||||||
|
} CogProvClass;
|
||||||
|
const char* cog_prov_name(CogProvClass p);
|
||||||
|
CogProvClass cog_prov_parse(const char* s);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int present; /* 1 iff the edge carries a GRD1 block */
|
||||||
|
|
||||||
|
/* ── STORED: the vector, as it stood at `ts` ─────────────────────────────── */
|
||||||
|
double factual; /* correspondence with evidence */
|
||||||
|
double relational; /* min over the thirteen value regions */
|
||||||
|
double associative; /* co-activation frequency — mirrors edge->hebb */
|
||||||
|
double polarity; /* SIGNED support; <0 = actively contradicts */
|
||||||
|
CogProvClass prov; /* observed / inferred / told / imprinted */
|
||||||
|
int64_t ts; /* when this version was recorded (ms) */
|
||||||
|
int64_t seq; /* supersession sequence number */
|
||||||
|
double reinforcements; /* uses folded into this version */
|
||||||
|
char binding_value[128]; /* the argmin value — the conflict's NAME */
|
||||||
|
/* the two gradients as frame-independent signed projections, plus the angle
|
||||||
|
* between them in full R^dim. These are part of the JOINT STATE a decision
|
||||||
|
* saw, not a convenience: near +1 evidence and values push the same way; at
|
||||||
|
* or below 0 the relation is factually supported and relationally wrong. */
|
||||||
|
double fac_proj, rel_proj, cos_angle;
|
||||||
|
int agreement; /* sign(cos_angle): +1 / 0 / −1 */
|
||||||
|
double floor_at_record, rel_floor_at_record;
|
||||||
|
char prev_edge[192]; /* the version this superseded ("" if first) */
|
||||||
|
|
||||||
|
/* ── DERIVED at read time. NEVER serialized. ─────────────────────────────── */
|
||||||
|
int64_t age_ms; /* recency: now − ts */
|
||||||
|
double decay; /* cog_decay_factor over that age */
|
||||||
|
double factual_now; /* factual · decay */
|
||||||
|
double relational_now;
|
||||||
|
double associative_now;
|
||||||
|
int stale; /* grounding fallen below its floor */
|
||||||
|
} CogGrounding;
|
||||||
|
|
||||||
|
/* Read an edge's vector as of `now_ms`. Pure — never writes. An edge with no
|
||||||
|
* GRD1 block still has an associative strength (its accrued hebb) and a polarity
|
||||||
|
* (its signed authored weight); `present` says whether the grounding dimensions
|
||||||
|
* have ever been established, and an unestablished dimension is reported as such
|
||||||
|
* rather than defaulted to a passing value. */
|
||||||
|
int cog_grounding_parse(const StoreEdge* e, int64_t now_ms, CogGrounding* out);
|
||||||
|
|
||||||
|
/* Serialize the STORED half of the vector, preserving pre-existing non-GRD1
|
||||||
|
* metadata. Returns an owned string. Derived fields are not written. */
|
||||||
|
char* cog_grounding_metadata(const char* base_meta, const CogGrounding* g);
|
||||||
|
|
||||||
|
/* ── §7.2 CONSOLIDATION-GATED SUPERSESSION ──────────────────────────────────
|
||||||
|
*
|
||||||
|
* Supersession is not recording — it is CONSOLIDATION, gated by salience, which
|
||||||
|
* is why you remember the argument and not the commute. Significance is
|
||||||
|
* evaluated PER-DIMENSION but the record is the WHOLE VECTOR: any dimension
|
||||||
|
* moving enough to matter triggers a supersession, and the new version captures
|
||||||
|
* every dimension as it stood at that instant. Versioning axes independently
|
||||||
|
* would make the joint state unreconstructable, and the joint state is the point
|
||||||
|
* — it is what makes "stayed true, became wrong" visible as an event (factual
|
||||||
|
* holding steady across versions while relational degrades).
|
||||||
|
*
|
||||||
|
* There is deliberately no epsilon in this enum or in the function that computes
|
||||||
|
* it. Every test is a floor crossing or a sign change, both exact. Two of them
|
||||||
|
* are INHERENTLY significant because they are discrete state changes rather than
|
||||||
|
* drift, and those bypass the salience gate entirely. */
|
||||||
|
typedef enum {
|
||||||
|
COG_SIG_NONE = 0, /* nothing decision-relevant moved — DO NOT RECORD */
|
||||||
|
COG_SIG_FIRST_RECORD = 1, /* no prior version exists */
|
||||||
|
COG_SIG_POLARITY_FLIP = 2, /* INHERENT: support ↔ contradiction, or ignorance
|
||||||
|
* ↔ either. A discrete change of state. */
|
||||||
|
COG_SIG_PROVENANCE_CHANGE = 3, /* INHERENT: told → observed is a categorical
|
||||||
|
* upgrade in what the relation is entitled to. */
|
||||||
|
COG_SIG_FACTUAL_FLOOR = 4, /* crossed the assert floor, factual axis */
|
||||||
|
COG_SIG_RELATIONAL_FLOOR = 5, /* crossed the assert floor, relational axis */
|
||||||
|
COG_SIG_AGREEMENT_FLIP = 6, /* factual/relational agreement changed sign */
|
||||||
|
COG_SIG_DIRECTION_REVERSAL = 7 /* a gradient reversed direction */
|
||||||
|
} CogSignificance;
|
||||||
|
|
||||||
|
CogSignificance cog_grounding_significant(const CogGrounding* prev,
|
||||||
|
const CogGrounding* now,
|
||||||
|
double floor, double rel_floor);
|
||||||
|
const char* cog_significance_name(CogSignificance s);
|
||||||
|
/* 1 iff this reason is a discrete state change that consolidates regardless of
|
||||||
|
* salience (polarity flip, provenance change, first record). */
|
||||||
|
int cog_significance_inherent(CogSignificance s);
|
||||||
|
|
||||||
|
/* ── §7.3 RECORDING: supersession of the EDGE, never an overwrite ────────────
|
||||||
|
* Writes version seq+1 as a NEW edge record with the same endpoints and relation
|
||||||
|
* and id "<root>#<seq+1>", carrying a GRD1 `p` pointer to its predecessor. The
|
||||||
|
* predecessor is never touched. The chain IS the trajectory: not only what the
|
||||||
|
* grounding is but which way it has been moving and how fast — a derivative
|
||||||
|
* obtained for free from immutability, because the points were never destroyed.
|
||||||
|
* Returns the version written (>=1), or <0 on error. */
|
||||||
|
int cog_grounding_record(EngramPagedStore* s, const StoreEdge* base,
|
||||||
|
const CogGrounding* g, char* out_id, size_t out_id_cap);
|
||||||
|
|
||||||
|
/* Walk forward from a base edge id to its newest recorded version. Point reads
|
||||||
|
* only; consolidation is gated, so the chain is short. Returns the highest
|
||||||
|
* version found (0 = the base record is the only one). */
|
||||||
|
int cog_grounding_head(EngramPagedStore* s, const char* base_id,
|
||||||
|
StoreEdge* out, int max_versions);
|
||||||
|
|
||||||
|
/* VOLATILITY — derived, never stored: the mean absolute per-version change of a
|
||||||
|
* dimension across the recorded chain. Feeds the equally-derived `confidence`
|
||||||
|
* (high grounding AND low volatility), which is likewise never stored. */
|
||||||
|
typedef struct {
|
||||||
|
int n_versions;
|
||||||
|
double factual_volatility;
|
||||||
|
double relational_volatility;
|
||||||
|
double factual_drift; /* signed: newest − oldest */
|
||||||
|
double relational_drift;
|
||||||
|
int stayed_true_became_wrong; /* factual steady while relational degraded */
|
||||||
|
} CogTrajectory;
|
||||||
|
int cog_grounding_trajectory(EngramPagedStore* s, const char* base_id,
|
||||||
|
int64_t now_ms, CogTrajectory* out);
|
||||||
|
|
||||||
|
/* ── §7.4 ASSERTION GATES ON BOTH FLOORS ────────────────────────────────────
|
||||||
|
* A well-evidenced claim must not earn the right to be asserted regardless of
|
||||||
|
* whether it means the right thing. `may_assert` requires the decayed factual
|
||||||
|
* grounding to clear `floor` AND the decayed relational grounding to clear
|
||||||
|
* `rel_floor`. A relation whose relational axis has never been established does
|
||||||
|
* not pass by default — it is reported unestablished and refused, because
|
||||||
|
* defaulting it to passing is exactly the exemption §0 forbids. Traversal is
|
||||||
|
* untouched: activation still conducts on the factual/associative side, so a
|
||||||
|
* relation can remain thinkable while ceasing to be assertable. */
|
||||||
|
typedef struct {
|
||||||
|
int may_assert;
|
||||||
|
int found; /* any relation at all on this claim */
|
||||||
|
int relational_established;
|
||||||
|
int still_held; /* DERIVED: node present and not tombstoned */
|
||||||
|
double factual; /* best decayed factual grounding */
|
||||||
|
double relational; /* the SAME edge's relational axis, not a max */
|
||||||
|
double polarity;
|
||||||
|
double cos_angle;
|
||||||
|
int agreement;
|
||||||
|
CogProvClass prov;
|
||||||
|
char best_edge[192];
|
||||||
|
char binding_value[128];
|
||||||
|
int n_edges;
|
||||||
|
} CogAssertion;
|
||||||
|
int cog_assert_two_axis(EngramPagedStore* s, const char* claim_id,
|
||||||
|
double floor, double rel_floor, int64_t now_ms,
|
||||||
|
CogAssertion* out);
|
||||||
|
|
||||||
#endif /* ENGRAM_COGNITION_H */
|
#endif /* ENGRAM_COGNITION_H */
|
||||||
|
|||||||
+440
-140
@@ -1,61 +1,128 @@
|
|||||||
import "../../runtime/eltest.el"
|
import "../../runtime/eltest.el"
|
||||||
// test_transduce.el — geometry as a first-class El value, and realizers
|
// test_transduce.el — transduction produces a SUBGRAPH, not a point.
|
||||||
// declared in El rather than patched into the runtime.
|
|
||||||
//
|
//
|
||||||
// WHAT IS ACTUALLY UNDER TEST. Until 2026-08-16 no El ingest path could carry
|
// WHAT IS ACTUALLY UNDER TEST. #144 moved transduction into the language and
|
||||||
// a vector: nodes took text, and geometry was DERIVED from that text. Text was
|
// got the dispatch right: realizers declared in El, resolved by name, no
|
||||||
// therefore the mandatory entry medium, so any non-text modality had to be
|
// runtime patch per modality. It got the RESULT TYPE wrong —
|
||||||
// DESCRIBED in prose first and the geometry we reasoned over was the geometry
|
// `transduce(signal, modality) -> Geometry`, one vector per signal.
|
||||||
// OF THE DESCRIPTION, not of the signal. The fix has two halves, and this file
|
|
||||||
// exercises both:
|
|
||||||
//
|
//
|
||||||
// 1. Geometry is a VALUE — it carries its own width, so nothing has to
|
// One vector is a FINGERPRINT. It can be matched and it can be ranked, and
|
||||||
// assert a width against a string's length.
|
// that is the whole of what it can ever do. It cannot be decomposed, cannot
|
||||||
// 2. A REALIZER is an ordinary El function. `tone_realizer` below is not in
|
// have one part grounded while another is not, and cannot be contradicted in
|
||||||
// the runtime, is not known to the compiler, and is not special in any
|
// one part while holding in another — because it has no parts. Treating
|
||||||
// way; it is registered BY NAME and dispatched to through transduce().
|
// transduction as a CONVERSION (signal in, position out) is the premise this
|
||||||
// That is the load-bearing claim: adding a modality must not require a
|
// file exists to falsify.
|
||||||
// runtime patch, or nothing has actually moved into the language.
|
//
|
||||||
|
// A song is not a point. It decomposes into pitch, interval, rhythm, harmonic
|
||||||
|
// function — components, each with its own geometry, plus the relations among
|
||||||
|
// them. THE SONG IS THE STRUCTURE OF THE RELATIONS. So transduction yields a
|
||||||
|
// Manifold: named components carrying geometry, and typed weighted relations
|
||||||
|
// between them.
|
||||||
|
//
|
||||||
|
// The geometry tests below are UNCHANGED from #144 and still pass, which is
|
||||||
|
// the point: Geometry was never wrong, it was misplaced. A vector is the right
|
||||||
|
// representation for a COMPONENT. It was only ever wrong as the representation
|
||||||
|
// of a whole transduced signal.
|
||||||
//
|
//
|
||||||
// COMPARISON DISCIPLINE IN THIS FILE (measured 2026-08-16, not stylistic):
|
// COMPARISON DISCIPLINE IN THIS FILE (measured 2026-08-16, not stylistic):
|
||||||
// elc lowers `a == b` to a NUMERIC comparison only when both operand names are
|
// elc lowers `a == b` to a NUMERIC comparison only when both operand names are
|
||||||
// in the per-function int-name set, which `let x: Int` populates. A bare call
|
// in the per-function int-name set, which `let x: Int` populates. A bare call
|
||||||
// like `geometry_is(g) == 0` is not a registered name, so it lowers to
|
// like `manifold_size(m) == 5` is not a registered name, so it lowers to
|
||||||
// `str_eq(...)` — strcmp on two integers reinterpreted as pointers. `<` and `>`
|
// `str_eq(...)` — strcmp on two integers reinterpreted as pointers. `<` and `>`
|
||||||
// lower directly via binop_to_c with no type inference at all, so truthiness is
|
// lower directly via binop_to_c with no type inference at all, so truthiness is
|
||||||
// written `> 0` / `< 1` here, and any exact `==` is done on a value first bound
|
// written `> 0` / `< 1` here, and any exact `==` is done on a value first bound
|
||||||
// through `let x: Int`.
|
// through `let x: Int`.
|
||||||
|
//
|
||||||
|
// ONE FURTHER RULE, measured while writing this file: that int-name set LEAKS
|
||||||
|
// ACROSS `test` BLOCKS. Binding `dn` as a Float in one test and as an Int in
|
||||||
|
// another silently demoted the Int comparison to str_eq and failed an
|
||||||
|
// assertion that was arithmetically true. Every Int-bound name compared with
|
||||||
|
// `==` here is therefore spelled UNIQUELY across the whole file (note_dim,
|
||||||
|
// iv_dim, ...), rather than reusing a short name per test.
|
||||||
|
|
||||||
// ── A realizer, written entirely in El ──────────────────────────────────────
|
// ── A DECOMPOSING realizer, written entirely in El ──────────────────────────
|
||||||
// Maps a "tone" signal into a 4-component geometry. Deliberately trivial —
|
// "tone" signals are note letters, e.g. "CEG". This realizer does NOT return
|
||||||
// what is being proven is that an El function can BE a realizer, not that
|
// one vector for the chord. It returns the PARTS — one component per note, one
|
||||||
// this is good acoustics. The one real property it has: distinct signals
|
// per interval between adjacent notes — and the relations that make those
|
||||||
// produce distinct geometry, so the test can tell transduction from a stub.
|
// parts a chord rather than an unordered bag of pitches.
|
||||||
fn tone_realizer(signal: String) -> Geometry {
|
//
|
||||||
|
// The interval is deliberately a COMPONENT, not an attribute of a note. An
|
||||||
|
// interval is a thing with its own geometry that belongs to neither endpoint;
|
||||||
|
// modelling it as a field on a note is exactly the collapse this change
|
||||||
|
// rejects, one level down.
|
||||||
|
fn tone_realizer(signal: String) -> Manifold {
|
||||||
|
let m: Manifold = manifold_new()
|
||||||
|
let n: Int = str_len(signal)
|
||||||
|
|
||||||
|
let i: Int = 0
|
||||||
|
while i < n {
|
||||||
|
let code: Int = str_char_code(signal, i)
|
||||||
|
let g: Geometry = geometry_new(2)
|
||||||
|
let s0: Int = geometry_set(g, 0, int_to_float(code))
|
||||||
|
let s1: Int = geometry_set(g, 1, int_to_float(i))
|
||||||
|
let idx: Int = manifold_add(m, "note:" + int_to_str(i), "pitch", g)
|
||||||
|
let f: Int = geometry_free(g)
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let j: Int = 1
|
||||||
|
while j < n {
|
||||||
|
let a: Int = str_char_code(signal, j - 1)
|
||||||
|
let b: Int = str_char_code(signal, j)
|
||||||
|
let lo: String = "note:" + int_to_str(j - 1)
|
||||||
|
let hi: String = "note:" + int_to_str(j)
|
||||||
|
let key: String = "interval:" + int_to_str(j - 1) + "-" + int_to_str(j)
|
||||||
|
let g: Geometry = geometry_new(1)
|
||||||
|
let s: Int = geometry_set(g, 0, int_to_float(b - a))
|
||||||
|
let idx: Int = manifold_add(m, key, "interval", g)
|
||||||
|
let f: Int = geometry_free(g)
|
||||||
|
let e1: Int = manifold_relate(m, key, "spans", lo, 0.9)
|
||||||
|
let e2: Int = manifold_relate(m, key, "spans", hi, 0.9)
|
||||||
|
let e3: Int = manifold_relate(m, lo, "sounds_before", hi, 0.8)
|
||||||
|
j = j + 1
|
||||||
|
}
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
|
// A second realizer for a different modality, to prove the registry keys on
|
||||||
|
// modality and does not just hand back "the last thing registered". Its
|
||||||
|
// decomposition has a DIFFERENT shape — two components, one relation — so a
|
||||||
|
// test can tell the two organs apart by structure alone.
|
||||||
|
fn pulse_realizer(signal: String) -> Manifold {
|
||||||
|
let m: Manifold = manifold_new()
|
||||||
|
let ga: Geometry = geometry_new(1)
|
||||||
|
let sa: Int = geometry_set(ga, 0, 1.0)
|
||||||
|
let ia: Int = manifold_add(m, "onset", "event", ga)
|
||||||
|
let fa: Int = geometry_free(ga)
|
||||||
|
let gb: Geometry = geometry_new(1)
|
||||||
|
let sb: Int = geometry_set(gb, 0, 0.0)
|
||||||
|
let ib: Int = manifold_add(m, "decay", "envelope", gb)
|
||||||
|
let fb: Int = geometry_free(gb)
|
||||||
|
let e: Int = manifold_relate(m, "onset", "decays_into", "decay", 0.7)
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
|
// #144's ACTUAL CONTRACT, preserved verbatim as a control: a realizer that
|
||||||
|
// returns one vector for the whole signal. This is not a strawman — it is what
|
||||||
|
// the merged primitive asked realizers to be. It must now transduce NOTHING.
|
||||||
|
fn fingerprint_realizer(signal: String) -> Geometry {
|
||||||
let g: Geometry = geometry_new(4)
|
let g: Geometry = geometry_new(4)
|
||||||
let n: Int = str_len(signal)
|
let n: Int = str_len(signal)
|
||||||
let a: Int = geometry_set(g, 0, int_to_float(n))
|
let a: Int = geometry_set(g, 0, int_to_float(n))
|
||||||
let b: Int = geometry_set(g, 1, int_to_float(n * 2))
|
let b: Int = geometry_set(g, 1, int_to_float(n * 2))
|
||||||
let c: Int = geometry_set(g, 2, int_to_float(n * 3))
|
|
||||||
let d: Int = geometry_set(g, 3, int_to_float(n * 4))
|
|
||||||
g
|
g
|
||||||
}
|
}
|
||||||
|
|
||||||
// A second realizer for a different modality, to prove the registry keys on
|
// A realizer returning something that is not a value at all.
|
||||||
// modality and does not just hand back "the last thing registered".
|
fn bogus_realizer(signal: String) -> Manifold {
|
||||||
fn pulse_realizer(signal: String) -> Geometry {
|
|
||||||
let g: Geometry = geometry_new(2)
|
|
||||||
let a: Int = geometry_set(g, 0, 1.0)
|
|
||||||
let b: Int = geometry_set(g, 1, 0.0)
|
|
||||||
g
|
|
||||||
}
|
|
||||||
|
|
||||||
// A deliberately BROKEN realizer: it returns something that is not a Geometry.
|
|
||||||
// transduce() must not hand this back to a caller as if it were one.
|
|
||||||
fn bogus_realizer(signal: String) -> Geometry {
|
|
||||||
return 12345
|
return 12345
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// Geometry — unchanged from #144. A vector is the right representation for a
|
||||||
|
// COMPONENT; it was only ever wrong as the representation of a whole signal.
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
test "geometry-is-a-value-with-its-own-width" {
|
test "geometry-is-a-value-with-its-own-width" {
|
||||||
let g: Geometry = geometry_new(8)
|
let g: Geometry = geometry_new(8)
|
||||||
let live: Int = geometry_is(g)
|
let live: Int = geometry_is(g)
|
||||||
@@ -67,17 +134,12 @@ test "geometry-is-a-value-with-its-own-width" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "geometry-rejects-nonsense-without-an-arbitrary-bound" {
|
test "geometry-rejects-nonsense-without-an-arbitrary-bound" {
|
||||||
// dim <= 0 is not a width. Note there is deliberately no MAX dim here:
|
|
||||||
// #141 needed `dim <= 8192` only to bound an allocation sized from a
|
|
||||||
// caller's claim about a string. A value that carries its own width has
|
|
||||||
// nothing left to validate, so the only failure left is allocation.
|
|
||||||
let zero: Geometry = geometry_new(0)
|
let zero: Geometry = geometry_new(0)
|
||||||
let z: Int = geometry_is(zero)
|
let z: Int = geometry_is(zero)
|
||||||
assert z < 1, "dim 0 is not a geometry"
|
assert z < 1, "dim 0 is not a geometry"
|
||||||
let neg: Geometry = geometry_new(-4)
|
let neg: Geometry = geometry_new(-4)
|
||||||
let n: Int = geometry_is(neg)
|
let n: Int = geometry_is(neg)
|
||||||
assert n < 1, "negative dim is not a geometry"
|
assert n < 1, "negative dim is not a geometry"
|
||||||
// Accessors must be total: a non-geometry is 0-width, never a crash.
|
|
||||||
let nd: Int = geometry_dim(0)
|
let nd: Int = geometry_dim(0)
|
||||||
assert nd < 1, "geometry_dim of a non-geometry is 0"
|
assert nd < 1, "geometry_dim of a non-geometry is 0"
|
||||||
let ni: Int = geometry_is(0)
|
let ni: Int = geometry_is(0)
|
||||||
@@ -105,21 +167,11 @@ test "geometry-components-round-trip" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "hex-is-an-edge-adapter-and-derives-its-own-width" {
|
test "hex-is-an-edge-adapter-and-derives-its-own-width" {
|
||||||
// 2 components, little-endian float32: 1.0 = 0000803f, 2.0 = 00000040.
|
|
||||||
let g: Geometry = geometry_from_f32le_hex("0000803f00000040")
|
let g: Geometry = geometry_from_f32le_hex("0000803f00000040")
|
||||||
let live: Int = geometry_is(g)
|
let live: Int = geometry_is(g)
|
||||||
assert live > 0, "valid hex decodes to a Geometry"
|
assert live > 0, "valid hex decodes to a Geometry"
|
||||||
let d: Int = geometry_dim(g)
|
let hex_dim: Int = geometry_dim(g)
|
||||||
assert d == 2, "width is DERIVED from the input, never supplied"
|
assert hex_dim == 2, "width is DERIVED from the input, never supplied"
|
||||||
let a: Float = geometry_get(g, 0)
|
|
||||||
let da: Float = a - 1.0
|
|
||||||
assert da < 0.001, "first component decoded"
|
|
||||||
assert da > -0.001, "first component decoded"
|
|
||||||
let b: Float = geometry_get(g, 1)
|
|
||||||
let db: Float = b - 2.0
|
|
||||||
assert db < 0.001, "second component decoded"
|
|
||||||
assert db > -0.001, "second component decoded"
|
|
||||||
// Egress adapter is the exact inverse.
|
|
||||||
let back: String = geometry_to_f32le_hex(g)
|
let back: String = geometry_to_f32le_hex(g)
|
||||||
assert str_eq(back, "0000803f00000040"), "hex round-trips exactly"
|
assert str_eq(back, "0000803f00000040"), "hex round-trips exactly"
|
||||||
let freed: Int = geometry_free(g)
|
let freed: Int = geometry_free(g)
|
||||||
@@ -137,98 +189,346 @@ test "hex-rejects-malformed-input" {
|
|||||||
assert nh < 1, "non-hex characters are refused"
|
assert nh < 1, "non-hex characters are refused"
|
||||||
}
|
}
|
||||||
|
|
||||||
test "a-realizer-declared-in-el-is-a-first-class-realizer" {
|
|
||||||
// THE CLAIM: tone_realizer is an ordinary El function. It is not in the
|
|
||||||
// runtime and the compiler knows nothing about it. Registering it by name
|
|
||||||
// is enough to make it the organ for a modality.
|
|
||||||
let reg: Int = realizer_register("tone", "tone_realizer")
|
|
||||||
assert reg > 0, "an El fn registers as a realizer by name"
|
|
||||||
let has: Int = realizer_has("tone")
|
|
||||||
assert has > 0, "the modality now has an organ"
|
|
||||||
|
|
||||||
let g: Geometry = transduce("aaa", "tone")
|
|
||||||
let live: Int = geometry_is(g)
|
|
||||||
assert live > 0, "transduce returns real geometry"
|
|
||||||
let d: Int = geometry_dim(g)
|
|
||||||
assert d == 4, "the El realizer determined the width, not the runtime"
|
|
||||||
// str_len("aaa") == 3, so component 0 must be 3.0 — proof the signal
|
|
||||||
// actually reached the El function rather than a stub answering for it.
|
|
||||||
let c0: Float = geometry_get(g, 0)
|
|
||||||
let dc: Float = c0 - 3.0
|
|
||||||
assert dc < 0.001, "the signal reached the El realizer"
|
|
||||||
assert dc > -0.001, "the signal reached the El realizer"
|
|
||||||
let freed: Int = geometry_free(g)
|
|
||||||
}
|
|
||||||
|
|
||||||
test "distinct-signals-transduce-to-distinct-geometry" {
|
|
||||||
let reg: Int = realizer_register("tone", "tone_realizer")
|
|
||||||
let g1: Geometry = transduce("aa", "tone")
|
|
||||||
let g2: Geometry = transduce("aaaaa", "tone")
|
|
||||||
let a: Float = geometry_get(g1, 0)
|
|
||||||
let b: Float = geometry_get(g2, 0)
|
|
||||||
let diff: Float = b - a
|
|
||||||
// 5 - 2 = 3. If transduction were a stub these would be equal.
|
|
||||||
assert diff > 2.9, "different signals produce different geometry"
|
|
||||||
assert diff < 3.1, "different signals produce different geometry"
|
|
||||||
let f1: Int = geometry_free(g1)
|
|
||||||
let f2: Int = geometry_free(g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
test "the-registry-keys-on-modality" {
|
|
||||||
let r1: Int = realizer_register("tone", "tone_realizer")
|
|
||||||
let r2: Int = realizer_register("pulse", "pulse_realizer")
|
|
||||||
assert r2 > 0, "a second modality registers independently"
|
|
||||||
let gt: Geometry = transduce("aaa", "tone")
|
|
||||||
let gp: Geometry = transduce("aaa", "pulse")
|
|
||||||
let dt: Int = geometry_dim(gt)
|
|
||||||
let dp: Int = geometry_dim(gp)
|
|
||||||
assert dt == 4, "tone still routes to its own realizer"
|
|
||||||
assert dp == 2, "pulse routes to a different realizer"
|
|
||||||
let f1: Int = geometry_free(gt)
|
|
||||||
let f2: Int = geometry_free(gp)
|
|
||||||
}
|
|
||||||
|
|
||||||
test "no-organ-is-reported-as-no-organ" {
|
|
||||||
// A modality with no realizer must transduce to NOTHING. It must never
|
|
||||||
// fall back to embedding a description of the signal and calling that
|
|
||||||
// perception — that silent substitution is the entire defect this change
|
|
||||||
// exists to end.
|
|
||||||
let has: Int = realizer_has("echolocation")
|
|
||||||
assert has < 1, "unregistered modality has no organ"
|
|
||||||
let g: Geometry = transduce("anything", "echolocation")
|
|
||||||
let live: Int = geometry_is(g)
|
|
||||||
assert live < 1, "no realizer means no geometry, not fake geometry"
|
|
||||||
}
|
|
||||||
|
|
||||||
test "registration-of-an-unresolvable-name-fails-loudly" {
|
|
||||||
// Reported at the moment of WIRING, not later as "this modality mysteriously
|
|
||||||
// produces nothing". Distinguishing "no organ" from "broken organ" is the
|
|
||||||
// lesson that made this whole change necessary.
|
|
||||||
let bad: Int = realizer_register("ghost", "no_such_function_anywhere")
|
|
||||||
assert bad < 1, "an unresolvable realizer name is a registration failure"
|
|
||||||
let has: Int = realizer_has("ghost")
|
|
||||||
assert has < 1, "and nothing gets registered"
|
|
||||||
}
|
|
||||||
|
|
||||||
test "a-realizer-returning-non-geometry-transduces-nothing" {
|
|
||||||
let reg: Int = realizer_register("bogus", "bogus_realizer")
|
|
||||||
assert reg > 0, "the symbol resolves, so registration succeeds"
|
|
||||||
// ...but the contract is enforced at the boundary, so the caller never
|
|
||||||
// receives a value that would misbehave far away from here.
|
|
||||||
let g: Geometry = transduce("x", "bogus")
|
|
||||||
let live: Int = geometry_is(g)
|
|
||||||
assert live < 1, "a non-Geometry return transduced nothing"
|
|
||||||
}
|
|
||||||
|
|
||||||
test "norm-lets-a-caller-check-a-realizer-emitted-signal" {
|
test "norm-lets-a-caller-check-a-realizer-emitted-signal" {
|
||||||
let g: Geometry = geometry_new(2)
|
let g: Geometry = geometry_new(2)
|
||||||
let z: Float = geometry_norm(g)
|
let z: Float = geometry_norm(g)
|
||||||
assert z < 0.001, "a fresh geometry is zero — norm says so"
|
assert z < 0.001, "a fresh geometry is zero — norm says so"
|
||||||
let s0: Int = geometry_set(g, 0, 3.0)
|
let s0: Int = geometry_set(g, 0, 3.0)
|
||||||
let s1: Int = geometry_set(g, 1, 4.0)
|
let s1: Int = geometry_set(g, 1, 4.0)
|
||||||
let n: Float = geometry_norm(g)
|
let nrm: Float = geometry_norm(g)
|
||||||
let dn: Float = n - 5.0
|
let dnorm: Float = nrm - 5.0
|
||||||
assert dn < 0.001, "3-4-5: norm is 5"
|
assert dnorm < 0.001, "3-4-5: norm is 5"
|
||||||
assert dn > -0.001, "3-4-5: norm is 5"
|
assert dnorm > -0.001, "3-4-5: norm is 5"
|
||||||
let freed: Int = geometry_free(g)
|
let freed: Int = geometry_free(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// Manifold — the corrected result of a transduction
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
test "a-manifold-is-a-value-that-holds-parts-and-relations" {
|
||||||
|
let m: Manifold = manifold_new()
|
||||||
|
let live: Int = manifold_is(m)
|
||||||
|
assert live > 0, "manifold_new returns a live Manifold"
|
||||||
|
let fresh_sz: Int = manifold_size(m)
|
||||||
|
assert fresh_sz == 0, "a fresh manifold has no components"
|
||||||
|
let fresh_rc: Int = manifold_rel_count(m)
|
||||||
|
assert fresh_rc == 0, "a fresh manifold has no relations"
|
||||||
|
let freed: Int = manifold_free(m)
|
||||||
|
assert freed > 0, "manifold_free reports what it did"
|
||||||
|
}
|
||||||
|
|
||||||
|
test "manifold-accessors-are-total" {
|
||||||
|
let ni2: Int = manifold_is(0)
|
||||||
|
assert ni2 < 1, "manifold_is of a non-manifold is 0"
|
||||||
|
let ns: Int = manifold_size(0)
|
||||||
|
assert ns < 1, "manifold_size of a non-manifold is 0"
|
||||||
|
let nf2: Int = manifold_free(0)
|
||||||
|
assert nf2 < 1, "manifold_free of a non-manifold is a no-op"
|
||||||
|
let k: String = manifold_key(0, 0)
|
||||||
|
assert str_eq(k, ""), "manifold_key of a non-manifold is empty, never a crash"
|
||||||
|
}
|
||||||
|
|
||||||
|
test "components-are-addressed-by-key-not-by-index" {
|
||||||
|
// The key is what survives persistence: a component becomes a node, and it
|
||||||
|
// is separately groundable precisely because it is separately NAMED.
|
||||||
|
let m: Manifold = manifold_new()
|
||||||
|
let g: Geometry = geometry_new(1)
|
||||||
|
let s: Int = geometry_set(g, 0, 7.0)
|
||||||
|
let first_idx: Int = manifold_add(m, "rhythm", "temporal", g)
|
||||||
|
assert first_idx == 0, "the first component is index 0"
|
||||||
|
let found_idx: Int = manifold_index_of(m, "rhythm")
|
||||||
|
assert found_idx == 0, "a component is found by its key"
|
||||||
|
let missing: Int = manifold_index_of(m, "never_added")
|
||||||
|
assert missing < 0, "an unknown key resolves to -1, not to component 0"
|
||||||
|
let role: String = manifold_role(m, 0)
|
||||||
|
assert str_eq(role, "temporal"), "a component carries what KIND of part it is"
|
||||||
|
let f: Int = geometry_free(g)
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a-duplicate-key-is-refused-because-addressing-must-be-unambiguous" {
|
||||||
|
let m: Manifold = manifold_new()
|
||||||
|
let g: Geometry = geometry_new(1)
|
||||||
|
let ok_idx: Int = manifold_add(m, "pitch", "spectral", g)
|
||||||
|
assert ok_idx == 0, "first add succeeds"
|
||||||
|
let dup: Int = manifold_add(m, "pitch", "spectral", g)
|
||||||
|
assert dup < 0, "two components answering to one name is not an addressing scheme"
|
||||||
|
let dup_sz: Int = manifold_size(m)
|
||||||
|
assert dup_sz == 1, "and the duplicate did not land"
|
||||||
|
let f: Int = geometry_free(g)
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a-part-with-no-geometry-is-not-a-part" {
|
||||||
|
let m: Manifold = manifold_new()
|
||||||
|
let bad: Int = manifold_add(m, "ghost", "none", 0)
|
||||||
|
assert bad < 0, "a non-Geometry is refused as a component"
|
||||||
|
let empty_key: Int = manifold_add(m, "", "none", geometry_new(1))
|
||||||
|
assert empty_key < 0, "an unaddressable component is refused"
|
||||||
|
let none_sz: Int = manifold_size(m)
|
||||||
|
assert none_sz < 1, "nothing landed"
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "an-edge-to-a-nonexistent-endpoint-is-refused-not-dropped" {
|
||||||
|
// A decomposition that silently loses edges is indistinguishable from one
|
||||||
|
// that never had them.
|
||||||
|
let m: Manifold = manifold_new()
|
||||||
|
let g: Geometry = geometry_new(1)
|
||||||
|
let a: Int = manifold_add(m, "here", "part", g)
|
||||||
|
let dangling: Int = manifold_relate(m, "here", "points_at", "nowhere", 0.5)
|
||||||
|
assert dangling < 1, "an edge to an unknown target is refused"
|
||||||
|
let backwards: Int = manifold_relate(m, "nowhere", "points_at", "here", 0.5)
|
||||||
|
assert backwards < 1, "an edge from an unknown source is refused"
|
||||||
|
let dang_rc: Int = manifold_rel_count(m)
|
||||||
|
assert dang_rc < 1, "and no relation was recorded"
|
||||||
|
let f: Int = geometry_free(g)
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a-component-owns-its-geometry-independently-of-the-caller" {
|
||||||
|
// manifold_add COPIES. Freeing the caller's vector must not disturb the
|
||||||
|
// component, or a decomposition would be unusable the moment it was built.
|
||||||
|
let m: Manifold = manifold_new()
|
||||||
|
let g: Geometry = geometry_new(2)
|
||||||
|
let s0: Int = geometry_set(g, 0, 42.0)
|
||||||
|
let idx: Int = manifold_add(m, "part", "kind", g)
|
||||||
|
let freed: Int = geometry_free(g)
|
||||||
|
assert freed > 0, "the caller freed its own vector"
|
||||||
|
let back: Geometry = manifold_geometry(m, 0)
|
||||||
|
let live: Int = geometry_is(back)
|
||||||
|
assert live > 0, "the component still has geometry"
|
||||||
|
let v: Float = geometry_get(back, 0)
|
||||||
|
let dv: Float = v - 42.0
|
||||||
|
assert dv < 0.001, "and it is the right geometry"
|
||||||
|
assert dv > -0.001, "and it is the right geometry"
|
||||||
|
let fb: Int = geometry_free(back)
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// transduce — signal in, SUBGRAPH out
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
test "a-realizer-declared-in-el-is-a-first-class-realizer" {
|
||||||
|
// THE CLAIM, unchanged from #144: tone_realizer is an ordinary El function.
|
||||||
|
// It is not in the runtime and the compiler knows nothing about it.
|
||||||
|
// Registering it by name is enough to make it the organ for a modality.
|
||||||
|
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||||
|
assert reg > 0, "an El fn registers as a realizer by name"
|
||||||
|
let has: Int = realizer_has("tone")
|
||||||
|
assert has > 0, "the modality now has an organ"
|
||||||
|
|
||||||
|
let m: Manifold = transduce("CEG", "tone")
|
||||||
|
let live: Int = manifold_is(m)
|
||||||
|
assert live > 0, "transduce returns a real Manifold"
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "transduction-decomposes-a-signal-into-parts" {
|
||||||
|
// THE CENTRAL CLAIM. "CEG" is three notes. What comes back is not one
|
||||||
|
// vector standing for a chord — it is five addressable parts (three notes,
|
||||||
|
// two intervals) and six relations. A fingerprint has one part by
|
||||||
|
// construction and could not express this at any width.
|
||||||
|
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||||
|
let m: Manifold = transduce("CEG", "tone")
|
||||||
|
|
||||||
|
let ceg_sz: Int = manifold_size(m)
|
||||||
|
assert ceg_sz == 5, "three notes and two intervals are five distinct parts"
|
||||||
|
let ceg_rc: Int = manifold_rel_count(m)
|
||||||
|
assert ceg_rc == 6, "and the parts stand in six stated relations"
|
||||||
|
|
||||||
|
// Every part is independently addressable BY NAME.
|
||||||
|
let n0: Int = manifold_index_of(m, "note:0")
|
||||||
|
assert n0 > -1, "the first note is addressable on its own"
|
||||||
|
let n2: Int = manifold_index_of(m, "note:2")
|
||||||
|
assert n2 > -1, "so is the third"
|
||||||
|
let iv: Int = manifold_index_of(m, "interval:0-1")
|
||||||
|
assert iv > -1, "so is the interval between the first two"
|
||||||
|
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "each-part-carries-its-own-geometry" {
|
||||||
|
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||||
|
let m: Manifold = transduce("CEG", "tone")
|
||||||
|
|
||||||
|
// 'C' is 67. The note component's geometry is the note's, not the chord's.
|
||||||
|
let note_i: Int = manifold_index_of(m, "note:0")
|
||||||
|
let gn: Geometry = manifold_geometry(m, note_i)
|
||||||
|
let note_dim: Int = geometry_dim(gn)
|
||||||
|
assert note_dim == 2, "a note component has the width its realizer gave it"
|
||||||
|
let pitch: Float = geometry_get(gn, 0)
|
||||||
|
let dpitch: Float = pitch - 67.0
|
||||||
|
assert dpitch < 0.001, "and it is C, so the signal reached the El realizer"
|
||||||
|
assert dpitch > -0.001, "and it is C, so the signal reached the El realizer"
|
||||||
|
|
||||||
|
// Parts may have DIFFERENT widths. A single vector per signal cannot
|
||||||
|
// represent parts of unequal dimensionality at all.
|
||||||
|
let iv_i: Int = manifold_index_of(m, "interval:0-1")
|
||||||
|
let gi: Geometry = manifold_geometry(m, iv_i)
|
||||||
|
let iv_dim: Int = geometry_dim(gi)
|
||||||
|
assert iv_dim == 1, "an interval component has its own, different width"
|
||||||
|
|
||||||
|
let f1: Int = geometry_free(gn)
|
||||||
|
let f2: Int = geometry_free(gi)
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "the-relations-are-content-no-single-part-carries" {
|
||||||
|
// THE POINT OF THE WHOLE CHANGE. C->E is two semitones. That "2" is not a
|
||||||
|
// property of C and not a property of E; it exists only BETWEEN them. A
|
||||||
|
// representation with no relations cannot hold it, which is why collapsing
|
||||||
|
// a signal to one vector does not merely lose resolution — it loses a
|
||||||
|
// category of content.
|
||||||
|
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||||
|
let m: Manifold = transduce("CEG", "tone")
|
||||||
|
|
||||||
|
let step_i: Int = manifold_index_of(m, "interval:0-1")
|
||||||
|
let gi: Geometry = manifold_geometry(m, step_i)
|
||||||
|
let step: Float = geometry_get(gi, 0)
|
||||||
|
let dstep: Float = step - 2.0
|
||||||
|
assert dstep < 0.001, "C to E is two semitones"
|
||||||
|
assert dstep > -0.001, "C to E is two semitones"
|
||||||
|
|
||||||
|
// And the interval is WIRED to both endpoints, so the structure says which
|
||||||
|
// two things it is the interval between.
|
||||||
|
let spans: Int = 0
|
||||||
|
let span_rc: Int = manifold_rel_count(m)
|
||||||
|
let k: Int = 0
|
||||||
|
while k < span_rc {
|
||||||
|
let rn: String = manifold_rel_name(m, k)
|
||||||
|
let rf: String = manifold_rel_from(m, k)
|
||||||
|
if str_eq(rn, "spans") {
|
||||||
|
if str_eq(rf, "interval:0-1") { spans = spans + 1 }
|
||||||
|
}
|
||||||
|
k = k + 1
|
||||||
|
}
|
||||||
|
assert spans == 2, "the interval is related to both notes it spans"
|
||||||
|
|
||||||
|
let fg: Int = geometry_free(gi)
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "relation-weight-is-the-grounding-carried-on-the-edge" {
|
||||||
|
// correspondence-and-censorship.md §1: grounding is an attribute of the
|
||||||
|
// edge and it IS the weight — one quantity, not a score computed beside
|
||||||
|
// it. A realizer states a relation and its weight is the claim.
|
||||||
|
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||||
|
let m: Manifold = transduce("CE", "tone")
|
||||||
|
|
||||||
|
let ce_rc: Int = manifold_rel_count(m)
|
||||||
|
assert ce_rc == 3, "one interval yields two spans and one ordering"
|
||||||
|
|
||||||
|
let found_w: Int = 0
|
||||||
|
let k: Int = 0
|
||||||
|
while k < ce_rc {
|
||||||
|
let rn: String = manifold_rel_name(m, k)
|
||||||
|
if str_eq(rn, "sounds_before") {
|
||||||
|
let w: Float = manifold_rel_weight(m, k)
|
||||||
|
let dw: Float = w - 0.8
|
||||||
|
if dw < 0.001 { if dw > -0.001 { found_w = found_w + 1 } }
|
||||||
|
}
|
||||||
|
k = k + 1
|
||||||
|
}
|
||||||
|
assert found_w == 1, "the ordering relation carries the weight its realizer stated"
|
||||||
|
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "distinct-signals-decompose-differently" {
|
||||||
|
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||||
|
let m2: Manifold = transduce("CE", "tone")
|
||||||
|
let m3: Manifold = transduce("CEG", "tone")
|
||||||
|
let two_sz: Int = manifold_size(m2)
|
||||||
|
let three_sz: Int = manifold_size(m3)
|
||||||
|
assert two_sz == 3, "two notes decompose into two notes and one interval"
|
||||||
|
assert three_sz == 5, "three notes decompose into three notes and two intervals"
|
||||||
|
// Structure differs, not just position: fingerprints of a two-note and a
|
||||||
|
// three-note signal have identical shape and differ only numerically.
|
||||||
|
let two_rc: Int = manifold_rel_count(m2)
|
||||||
|
let three_rc: Int = manifold_rel_count(m3)
|
||||||
|
assert two_rc < three_rc, "and the relational structure itself differs"
|
||||||
|
let f2: Int = manifold_free(m2)
|
||||||
|
let f3: Int = manifold_free(m3)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "the-registry-keys-on-modality" {
|
||||||
|
let r1: Int = realizer_register("tone", "tone_realizer")
|
||||||
|
let rp: Int = realizer_register("pulse", "pulse_realizer")
|
||||||
|
assert rp > 0, "a second modality registers independently"
|
||||||
|
let mt: Manifold = transduce("CEG", "tone")
|
||||||
|
let mp: Manifold = transduce("CEG", "pulse")
|
||||||
|
let tone_sz: Int = manifold_size(mt)
|
||||||
|
let pulse_sz: Int = manifold_size(mp)
|
||||||
|
assert tone_sz == 5, "tone still routes to its own realizer"
|
||||||
|
assert pulse_sz == 2, "pulse routes to a different realizer, with its own decomposition"
|
||||||
|
let onset: Int = manifold_index_of(mp, "onset")
|
||||||
|
assert onset > -1, "and to that realizer's own component vocabulary"
|
||||||
|
let f1: Int = manifold_free(mt)
|
||||||
|
let f2: Int = manifold_free(mp)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "no-organ-is-reported-as-no-organ" {
|
||||||
|
// A modality with no realizer must transduce to NOTHING. It must never
|
||||||
|
// fall back to embedding a description of the signal and calling that
|
||||||
|
// perception — that silent substitution is the original defect.
|
||||||
|
let has: Int = realizer_has("echolocation")
|
||||||
|
assert has < 1, "unregistered modality has no organ"
|
||||||
|
let m: Manifold = transduce("anything", "echolocation")
|
||||||
|
let live: Int = manifold_is(m)
|
||||||
|
assert live < 1, "no realizer means no manifold, not a fake one"
|
||||||
|
}
|
||||||
|
|
||||||
|
test "registration-of-an-unresolvable-name-fails-loudly" {
|
||||||
|
let bad: Int = realizer_register("ghost", "no_such_function_anywhere")
|
||||||
|
assert bad < 1, "an unresolvable realizer name is a registration failure"
|
||||||
|
let has: Int = realizer_has("ghost")
|
||||||
|
assert has < 1, "and nothing gets registered"
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a-fingerprint-realizer-transduces-nothing" {
|
||||||
|
// THE SUPERSESSION OF #144, asserted directly. fingerprint_realizer is
|
||||||
|
// exactly what the merged primitive asked a realizer to be: signal in, one
|
||||||
|
// Geometry out. It resolves, so registration succeeds — the organ is
|
||||||
|
// present. But it does not decompose, so it does not transduce.
|
||||||
|
//
|
||||||
|
// This is a deliberate hard failure. "No organ" and "an organ that only
|
||||||
|
// fingerprints" must not be indistinguishable, which is the same
|
||||||
|
// distinction realizer_register already draws between an absent and a
|
||||||
|
// broken organ. A modality with genuinely one part says so with
|
||||||
|
// manifold_single, and is then visibly a size-1 manifold.
|
||||||
|
let reg: Int = realizer_register("fingerprint", "fingerprint_realizer")
|
||||||
|
assert reg > 0, "the symbol resolves, so registration succeeds"
|
||||||
|
let m: Manifold = transduce("x", "fingerprint")
|
||||||
|
let live: Int = manifold_is(m)
|
||||||
|
assert live < 1, "a single vector is not a transduction"
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a-realizer-returning-nonsense-transduces-nothing" {
|
||||||
|
let reg: Int = realizer_register("bogus", "bogus_realizer")
|
||||||
|
assert reg > 0, "the symbol resolves, so registration succeeds"
|
||||||
|
let m: Manifold = transduce("x", "bogus")
|
||||||
|
let live: Int = manifold_is(m)
|
||||||
|
assert live < 1, "a non-Manifold return transduced nothing"
|
||||||
|
}
|
||||||
|
|
||||||
|
test "the-one-part-case-is-a-size-one-manifold-not-a-bare-vector" {
|
||||||
|
// Some modalities really do have one part. That is a manifold of size 1 —
|
||||||
|
// a special case of decomposition, not a parallel path back to a
|
||||||
|
// fingerprint. Anything reading it still asks manifold_size and still gets
|
||||||
|
// a real answer, and a second part can be added later without changing the
|
||||||
|
// type of the thing.
|
||||||
|
let g: Geometry = geometry_new(3)
|
||||||
|
let s: Int = geometry_set(g, 0, 5.0)
|
||||||
|
let m: Manifold = manifold_single("level", "scalar", g)
|
||||||
|
let live: Int = manifold_is(m)
|
||||||
|
assert live > 0, "manifold_single yields a real Manifold"
|
||||||
|
let one_sz: Int = manifold_size(m)
|
||||||
|
assert one_sz == 1, "of size one — visibly degenerate, not hidden"
|
||||||
|
let idx: Int = manifold_index_of(m, "level")
|
||||||
|
assert idx == 0, "and its one part is still addressable by name"
|
||||||
|
let f: Int = geometry_free(g)
|
||||||
|
let fm: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user