M-INTEROCEPTION P3 (partial): descriptor-displacement drift-sensor primitive; self-anchor prerequisite flagged
Adds engram_geo_displacement(A, B, core_frac) — a read-only interoceptive primitive that measures how far a neighborhood descriptor B has drifted from a baseline A and decomposes it into GROWTH (periphery extends, core fixed) vs CORRUPTION (the invariant core displaces). The core is the top core_frac of A's members by centrality; per shared member (matched by id) the displacement is the change in radial position (dist_centroid). Centroid separation (L2 + cosine) and radius delta give the aggregate move. Pure function, no store mutation, no flag. HONESTLY PARTIAL: a live self-drift reading needs a persisted SelfAnchor baseline to compare "now" against, and no persisted self node / anchored self-neighborhood exists in this store yet. The primitive takes an EXPLICIT baseline so it is real and testable today; capturing a durable SelfAnchor and wiring the ENGRAM_DRIFT_SENSOR live reading is a flagged follow-up. We do not fabricate a self silently. MEASURED on synthetic descriptors: - GROWTH (periphery 0.50->0.90, core fixed): core_disp=0.000, periph_disp=0.400, centroid_sep=0.000, radius_delta=0.400. - CORRUPTION (core 0.10->0.60, periphery fixed): core_disp=0.500, periph_disp=0.000, centroid_sep=0.566. - Identity A vs A: zero drift. The sensor discriminates cleanly (corruption core_disp >> growth core_disp). ASan+UBSan clean.
This commit is contained in:
@@ -521,6 +521,70 @@ void engram_geo_free(GeoDescriptor* g){
|
||||
free(g);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* M-INTEROCEPTION P3 — DRIFT SENSOR primitive (descriptor displacement).
|
||||
* Read-only. Measures how far descriptor B has drifted from a baseline A and
|
||||
* decomposes it into GROWTH (periphery extends, core fixed) vs CORRUPTION (the
|
||||
* invariant core displaces). The core is the top `core_frac` of A's members by
|
||||
* centrality; the periphery is the rest. Per shared member (matched by id), the
|
||||
* displacement is the change in its radial position (dist_centroid) between A
|
||||
* and B; centroid separation + radius delta give the aggregate move.
|
||||
*
|
||||
* PREREQUISITE FLAGGED (honesty rail, design §2/§6): a LIVE self-drift reading
|
||||
* needs a persisted SelfAnchor baseline descriptor to compare "now" against.
|
||||
* That anchor does NOT exist yet — there is no persisted self node / anchored
|
||||
* self-neighborhood in this store. This primitive therefore takes an EXPLICIT
|
||||
* baseline so it is real and testable today; capturing a durable SelfAnchor
|
||||
* snapshot and wiring the ENGRAM_DRIFT_SENSOR live reading is a follow-up. We do
|
||||
* NOT fabricate a self silently.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
static double eg_geo_l2(const float* x, const float* y, int dim){
|
||||
double s=0; for(int i=0;i<dim;i++){ double d=(double)x[i]-(double)y[i]; s+=d*d; } return sqrt(s);
|
||||
}
|
||||
static double eg_geo_cosv(const float* x, const float* y, int dim){
|
||||
double dot=0,nx=0,ny=0;
|
||||
for(int i=0;i<dim;i++){ dot+=(double)x[i]*y[i]; nx+=(double)x[i]*x[i]; ny+=(double)y[i]*y[i]; }
|
||||
if(nx<=0.0||ny<=0.0) return 0.0;
|
||||
return dot/(sqrt(nx)*sqrt(ny));
|
||||
}
|
||||
void engram_geo_displacement(const GeoDescriptor* a, const GeoDescriptor* b,
|
||||
double core_frac, GeoDisplacement* out){
|
||||
if(!out) return;
|
||||
memset(out,0,sizeof(*out));
|
||||
if(!a||!b) return;
|
||||
if(a->centroid && b->centroid && a->dim==b->dim && a->dim>0){
|
||||
out->centroid_sep = eg_geo_l2(a->centroid,b->centroid,a->dim);
|
||||
out->centroid_cos = 1.0 - eg_geo_cosv(a->centroid,b->centroid,a->dim);
|
||||
}
|
||||
out->radius_delta = fabs(a->radius - b->radius);
|
||||
if(!(core_frac>0.0 && core_frac<=1.0)) core_frac=0.3;
|
||||
int na=a->n_members;
|
||||
if(na<=0) return;
|
||||
int* order=malloc((size_t)na*sizeof(int));
|
||||
if(!order) return;
|
||||
for(int i=0;i<na;i++) order[i]=i;
|
||||
/* insertion sort by centrality desc (neighborhoods are small) */
|
||||
for(int i=1;i<na;i++){ int k=order[i]; int j=i-1;
|
||||
while(j>=0 && a->members[order[j]].centrality < a->members[k].centrality){ order[j+1]=order[j]; j--; }
|
||||
order[j+1]=k; }
|
||||
int ncore=(int)(core_frac*na+0.5); if(ncore<1) ncore=1; if(ncore>na) ncore=na;
|
||||
double core_sum=0, periph_sum=0; int core_n=0, periph_n=0;
|
||||
for(int r=0;r<na;r++){
|
||||
const GeoMember* ma=&a->members[order[r]];
|
||||
const GeoMember* mb=NULL;
|
||||
for(int j=0;j<b->n_members;j++){
|
||||
if(b->members[j].id && ma->id && strcmp(b->members[j].id,ma->id)==0){ mb=&b->members[j]; break; }
|
||||
}
|
||||
if(!mb) continue;
|
||||
double disp=fabs(ma->dist_centroid - mb->dist_centroid);
|
||||
if(r<ncore){ core_sum+=disp; core_n++; } else { periph_sum+=disp; periph_n++; }
|
||||
}
|
||||
free(order);
|
||||
out->core_matched=core_n; out->periph_matched=periph_n;
|
||||
out->core_disp = core_n ? core_sum/core_n : 0.0;
|
||||
out->periph_disp = periph_n ? periph_sum/periph_n : 0.0;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* M10 — REIFICATION: persist / load / lookup first-class neighborhood records.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
@@ -158,6 +158,22 @@ GeoDescriptor* engram_geometry_descriptor(
|
||||
|
||||
void engram_geo_free(GeoDescriptor* g);
|
||||
|
||||
/* ── M-INTEROCEPTION P3: drift-sensor primitive (descriptor displacement) ────
|
||||
* Read-only. GROWTH vs CORRUPTION split of how far B drifted from baseline A.
|
||||
* See engram_geometry.c for the honesty note on the missing SelfAnchor. */
|
||||
typedef struct {
|
||||
double centroid_sep; /* L2 distance between centroids (same frame) */
|
||||
double centroid_cos; /* 1 - cosine(centroidA, centroidB) */
|
||||
double radius_delta; /* |radiusA - radiusB| — neighborhood scale change */
|
||||
double core_disp; /* mean radial displacement of the invariant core */
|
||||
double periph_disp; /* mean radial displacement of the periphery */
|
||||
int core_matched; /* # core members matched by id across A,B */
|
||||
int periph_matched; /* # periphery members matched by id across A,B */
|
||||
} GeoDisplacement;
|
||||
|
||||
void engram_geo_displacement(const GeoDescriptor* a, const GeoDescriptor* b,
|
||||
double core_frac, GeoDisplacement* out);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* M10 — REIFICATION: densely co-wired relational neighborhoods crystallized into
|
||||
* FIRST-CLASS, PERSISTED store records (design doc §2; memory 885f5945). This is
|
||||
|
||||
Reference in New Issue
Block a user