Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f3ddb8b8d |
File diff suppressed because one or more lines are too long
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"dataset": "british-rp-accent-transform",
|
||||
"primitive_type": "accent_target",
|
||||
"accent": "british-rp",
|
||||
"grounding": "derived",
|
||||
"provenance": "HONEST-DERIVED, COARSE FIRST PASS — NOT transcribed measured RP formants. The exact measured RP/GB tables (Deterding 1997 JIPA 27:47-55; Hawkins & Midgley 2005 JIPA 35:183-199) are the intended ground truth but were gated/figure-only at author time and were NOT transcribed. So these targets are DERIVED: each = the corresponding MEASURED Peterson&Barney(1952) base vowel transformed under the documented, citable RP-vs-GA structural rules of Wells (1982) 'Accents of English' — non-rhoticity (NURSE de-rhoticized: remove low F3), TRAP F2-lowering, LOT/THOUGHT back-rounding (F2 down), GOOSE-fronting (F2 up), GOAT centering. Shift MAGNITUDES are coarse/approximate (first pass), directions are cited. ground:derived (base measured + rule cited). Refine by transcribing Deterding/Hawkins&Midgley. No number is presented as a measured RP value it is not.",
|
||||
"notes": "records with kind=vowel_override REPLACE the base phoneme's formant targets with the DERIVED RP realization. records with kind=rule encode non-formant transforms (non-rhoticity: drop post-vocalic coda /r/). The render composes: base geometry then accent override + rhoticity rule — voice + accent, separable.",
|
||||
"records": [
|
||||
{"key": "IY", "features": {"kind": "vowel_override", "set": "FLEECE"}, "attributes": {"f1": 280, "f2": 2249, "f3": 3000}},
|
||||
{"key": "IH", "features": {"kind": "vowel_override", "set": "KIT"}, "attributes": {"f1": 360, "f2": 2100, "f3": 2550}},
|
||||
{"key": "EH", "features": {"kind": "vowel_override", "set": "DRESS"}, "attributes": {"f1": 560, "f2": 1970, "f3": 2480}},
|
||||
{"key": "AE", "features": {"kind": "vowel_override", "set": "TRAP"}, "attributes": {"f1": 730, "f2": 1590, "f3": 2410}},
|
||||
{"key": "AA", "features": {"kind": "vowel_override", "set": "LOT"}, "attributes": {"f1": 560, "f2": 920, "f3": 2440}},
|
||||
{"key": "AO", "features": {"kind": "vowel_override", "set": "THOUGHT"}, "attributes": {"f1": 415, "f2": 700, "f3": 2410}},
|
||||
{"key": "UH", "features": {"kind": "vowel_override", "set": "FOOT"}, "attributes": {"f1": 380, "f2": 1100, "f3": 2240}},
|
||||
{"key": "UW", "features": {"kind": "vowel_override", "set": "GOOSE"}, "attributes": {"f1": 310, "f2": 1650, "f3": 2240}},
|
||||
{"key": "AH", "features": {"kind": "vowel_override", "set": "STRUT"}, "attributes": {"f1": 680, "f2": 1180, "f3": 2390}},
|
||||
{"key": "ER", "features": {"kind": "vowel_override", "set": "NURSE", "rhotic": "no"}, "attributes": {"f1": 550, "f2": 1500, "f3": 2500}},
|
||||
{"key": "AX", "features": {"kind": "vowel_override", "set": "commA"}, "attributes": {"f1": 500, "f2": 1500, "f3": 2500}},
|
||||
{"key": "OW", "features": {"kind": "vowel_override", "set": "GOAT"}, "attributes": {"f1": 450, "f2": 1400, "f3": 2380}},
|
||||
{"key": "R", "features": {"kind": "rule", "rule": "non_rhotic"}, "attributes": {"drop_coda_r": 1}}
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
# british-rp-accent TRANSFORM — INGESTIBLE DATA (a geometry/transform composed
|
||||
# onto the base General-American phoneme targets; voice + accent, separable).
|
||||
#
|
||||
# PROVENANCE — HONEST, COARSE FIRST PASS. These are DERIVED targets, NOT
|
||||
# transcribed measured RP formants. Measured RP tables (Deterding 1997 JIPA 27;
|
||||
# Hawkins & Midgley 2005 JIPA 35) are the intended ground truth but were gated at
|
||||
# author time and NOT transcribed. Each target = the MEASURED Peterson&Barney
|
||||
# (1952) base vowel transformed under the documented, citable RP-vs-GA structural
|
||||
# rules of Wells (1982): non-rhoticity, TRAP F2-lowering, LOT/THOUGHT back-
|
||||
# rounding, GOOSE-fronting, GOAT centering, NURSE de-rhoticization. Shift
|
||||
# magnitudes are coarse/approximate; directions are cited. ground=derived.
|
||||
# Refine by transcribing the measured RP tables. No value is claimed as measured.
|
||||
# Format: KEY|F1|F2|F3|KIND|SET
|
||||
IY|280|2249|3000|vowel_override|FLEECE
|
||||
IH|360|2100|2550|vowel_override|KIT
|
||||
EH|560|1970|2480|vowel_override|DRESS
|
||||
AE|730|1590|2410|vowel_override|TRAP
|
||||
AA|560|920|2440|vowel_override|LOT
|
||||
AO|415|700|2410|vowel_override|THOUGHT
|
||||
UH|380|1100|2240|vowel_override|FOOT
|
||||
UW|310|1650|2240|vowel_override|GOOSE
|
||||
AH|680|1180|2390|vowel_override|STRUT
|
||||
ER|550|1500|2500|vowel_override|NURSE-nonrhotic
|
||||
AX|500|1500|2500|vowel_override|commA
|
||||
OW|450|1400|2380|vowel_override|GOAT
|
||||
R|0|0|0|rule|non_rhotic_drop_coda
|
||||
@@ -1,20 +0,0 @@
|
||||
# pronunciation lexicon SOURCE — word -> phoneme sequence, as INGESTIBLE DATA.
|
||||
# Pronunciation is linguistic KNOWLEDGE (the language faculty's orthography->
|
||||
# phonology map), ingested into the engram, not frozen in code. The render reads
|
||||
# a word's phoneme sequence back from the engram. Covers the self-lexicon and the
|
||||
# proof sentences; general G2P is the realizer/morphology faculty's remit.
|
||||
# Diphthongs are written as two vowel targets (the render's transitions glide
|
||||
# between them). Format: word|PH1 PH2 PH3 ...
|
||||
i|AA IY
|
||||
am|AE M
|
||||
neuron|N UW R AA N
|
||||
is|IH Z
|
||||
memory|M EH M ER IY
|
||||
hello|HH EH L OW
|
||||
the|DH AH
|
||||
a|AH
|
||||
remember|R IH M EH M ER
|
||||
i'm|AA IY M
|
||||
you|Y UW
|
||||
here|HH IY R
|
||||
will|W IH L
|
||||
File diff suppressed because one or more lines are too long
@@ -1,528 +0,0 @@
|
||||
{
|
||||
"dataset": "english-phoneme-formants",
|
||||
"primitive_type": "phoneme",
|
||||
"grounding": "extracted",
|
||||
"provenance": "AUDITED per-field. The 10 monophthong-vowel F1/F2/F3 (IY,IH,EH,AE,AA,AO,UH,UW,AH,ER) are the MEASURED adult-male /hVd/ means of Peterson & Barney (1952) JASA 24:175-184, verified vs CRAN phonTools::pb52. AX=neutral uniform-tube resonances (Fant, physics). OW steady target = synthesis convention (diphthong). Consonant loci (M,N,NG,L,R,W,Y,Z,DH,V,S,F,HH) and ALL bandwidths + dur/amp = standard formant-synthesis conventions (Klatt 1980 JASA 67:971), engineering defaults NOT field measurements. No numbers invented/LLM-generated.",
|
||||
"records": [
|
||||
{
|
||||
"key": "IY",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 270,
|
||||
"f2": 2290,
|
||||
"f3": 3010,
|
||||
"bw1": 60,
|
||||
"bw2": 90,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 130,
|
||||
"amp": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "IH",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 390,
|
||||
"f2": 1990,
|
||||
"f3": 2550,
|
||||
"bw1": 70,
|
||||
"bw2": 100,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 110,
|
||||
"amp": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "EH",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 530,
|
||||
"f2": 1840,
|
||||
"f3": 2480,
|
||||
"bw1": 80,
|
||||
"bw2": 100,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 130,
|
||||
"amp": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "AE",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 660,
|
||||
"f2": 1720,
|
||||
"f3": 2410,
|
||||
"bw1": 90,
|
||||
"bw2": 110,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 150,
|
||||
"amp": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "AA",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 730,
|
||||
"f2": 1090,
|
||||
"f3": 2440,
|
||||
"bw1": 90,
|
||||
"bw2": 110,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 150,
|
||||
"amp": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "AO",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 570,
|
||||
"f2": 840,
|
||||
"f3": 2410,
|
||||
"bw1": 80,
|
||||
"bw2": 100,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 140,
|
||||
"amp": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "UH",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 440,
|
||||
"f2": 1020,
|
||||
"f3": 2240,
|
||||
"bw1": 70,
|
||||
"bw2": 100,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 110,
|
||||
"amp": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "UW",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 300,
|
||||
"f2": 870,
|
||||
"f3": 2240,
|
||||
"bw1": 70,
|
||||
"bw2": 90,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 140,
|
||||
"amp": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "AH",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 640,
|
||||
"f2": 1190,
|
||||
"f3": 2390,
|
||||
"bw1": 80,
|
||||
"bw2": 100,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 110,
|
||||
"amp": 95
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "ER",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 490,
|
||||
"f2": 1350,
|
||||
"f3": 1690,
|
||||
"bw1": 80,
|
||||
"bw2": 100,
|
||||
"bw3": 120,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 140,
|
||||
"amp": 95
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "AX",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 500,
|
||||
"f2": 1500,
|
||||
"f3": 2500,
|
||||
"bw1": 80,
|
||||
"bw2": 100,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 80,
|
||||
"amp": 85
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "OW",
|
||||
"features": {
|
||||
"manner": "vowel",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 490,
|
||||
"f2": 910,
|
||||
"f3": 2380,
|
||||
"bw1": 80,
|
||||
"bw2": 100,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 140,
|
||||
"amp": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "M",
|
||||
"features": {
|
||||
"manner": "nasal",
|
||||
"voiced": "yes",
|
||||
"nasal": "yes"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 250,
|
||||
"f2": 900,
|
||||
"f3": 2200,
|
||||
"bw1": 90,
|
||||
"bw2": 120,
|
||||
"bw3": 180,
|
||||
"voiced": 1,
|
||||
"nasal": 1,
|
||||
"dur": 80,
|
||||
"amp": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "N",
|
||||
"features": {
|
||||
"manner": "nasal",
|
||||
"voiced": "yes",
|
||||
"nasal": "yes"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 250,
|
||||
"f2": 1700,
|
||||
"f3": 2600,
|
||||
"bw1": 90,
|
||||
"bw2": 120,
|
||||
"bw3": 180,
|
||||
"voiced": 1,
|
||||
"nasal": 1,
|
||||
"dur": 80,
|
||||
"amp": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "NG",
|
||||
"features": {
|
||||
"manner": "nasal",
|
||||
"voiced": "yes",
|
||||
"nasal": "yes"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 250,
|
||||
"f2": 2300,
|
||||
"f3": 2700,
|
||||
"bw1": 90,
|
||||
"bw2": 120,
|
||||
"bw3": 180,
|
||||
"voiced": 1,
|
||||
"nasal": 1,
|
||||
"dur": 80,
|
||||
"amp": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "L",
|
||||
"features": {
|
||||
"manner": "approximant",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 360,
|
||||
"f2": 1300,
|
||||
"f3": 2600,
|
||||
"bw1": 80,
|
||||
"bw2": 110,
|
||||
"bw3": 160,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 70,
|
||||
"amp": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "R",
|
||||
"features": {
|
||||
"manner": "approximant",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 490,
|
||||
"f2": 1350,
|
||||
"f3": 1600,
|
||||
"bw1": 80,
|
||||
"bw2": 110,
|
||||
"bw3": 120,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 80,
|
||||
"amp": 85
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "W",
|
||||
"features": {
|
||||
"manner": "approximant",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 300,
|
||||
"f2": 610,
|
||||
"f3": 2200,
|
||||
"bw1": 70,
|
||||
"bw2": 100,
|
||||
"bw3": 160,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 70,
|
||||
"amp": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Y",
|
||||
"features": {
|
||||
"manner": "approximant",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 270,
|
||||
"f2": 2290,
|
||||
"f3": 3010,
|
||||
"bw1": 60,
|
||||
"bw2": 90,
|
||||
"bw3": 150,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 60,
|
||||
"amp": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Z",
|
||||
"features": {
|
||||
"manner": "fricative",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 300,
|
||||
"f2": 1700,
|
||||
"f3": 2500,
|
||||
"bw1": 100,
|
||||
"bw2": 150,
|
||||
"bw3": 200,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 90,
|
||||
"amp": 55
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "DH",
|
||||
"features": {
|
||||
"manner": "fricative",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 300,
|
||||
"f2": 1400,
|
||||
"f3": 2500,
|
||||
"bw1": 100,
|
||||
"bw2": 150,
|
||||
"bw3": 200,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 70,
|
||||
"amp": 55
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "V",
|
||||
"features": {
|
||||
"manner": "fricative",
|
||||
"voiced": "yes",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 300,
|
||||
"f2": 1000,
|
||||
"f3": 2300,
|
||||
"bw1": 100,
|
||||
"bw2": 150,
|
||||
"bw3": 200,
|
||||
"voiced": 1,
|
||||
"nasal": 0,
|
||||
"dur": 70,
|
||||
"amp": 55
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "S",
|
||||
"features": {
|
||||
"manner": "fricative",
|
||||
"voiced": "no",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 320,
|
||||
"f2": 1700,
|
||||
"f3": 2500,
|
||||
"bw1": 200,
|
||||
"bw2": 200,
|
||||
"bw3": 250,
|
||||
"voiced": 0,
|
||||
"nasal": 0,
|
||||
"dur": 110,
|
||||
"amp": 45
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "F",
|
||||
"features": {
|
||||
"manner": "fricative",
|
||||
"voiced": "no",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 300,
|
||||
"f2": 1200,
|
||||
"f3": 2400,
|
||||
"bw1": 200,
|
||||
"bw2": 200,
|
||||
"bw3": 250,
|
||||
"voiced": 0,
|
||||
"nasal": 0,
|
||||
"dur": 100,
|
||||
"amp": 40
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "HH",
|
||||
"features": {
|
||||
"manner": "fricative",
|
||||
"voiced": "no",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 500,
|
||||
"f2": 1500,
|
||||
"f3": 2500,
|
||||
"bw1": 200,
|
||||
"bw2": 250,
|
||||
"bw3": 300,
|
||||
"voiced": 0,
|
||||
"nasal": 0,
|
||||
"dur": 70,
|
||||
"amp": 40
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "SIL",
|
||||
"features": {
|
||||
"manner": "silence",
|
||||
"voiced": "no",
|
||||
"nasal": "no"
|
||||
},
|
||||
"attributes": {
|
||||
"f1": 500,
|
||||
"f2": 1500,
|
||||
"f3": 2500,
|
||||
"bw1": 100,
|
||||
"bw2": 100,
|
||||
"bw3": 100,
|
||||
"voiced": 0,
|
||||
"nasal": 0,
|
||||
"dur": 55,
|
||||
"amp": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
# acoustic-phonetics SOURCE — the learned speech primitives, as INGESTIBLE DATA.
|
||||
# NOT audio, NOT code: formant geometry of the phonemes, to be ingested via the
|
||||
# ingest organ into the engram as a phoneme manifold. The render reads this
|
||||
# geometry back from the engram; nothing is frozen in EL code.
|
||||
#
|
||||
# PROVENANCE (audited, per-field honesty — no invented numbers):
|
||||
# * The 10 MONOPHTHONG VOWEL formants F1/F2/F3 (IY,IH,EH,AE,AA,AO,UH,UW,AH,ER)
|
||||
# are the MEASURED adult-male means of Peterson & Barney (1952), JASA 24:175-184
|
||||
# — the canonical /hVd/ table, verified digit-for-digit vs CRAN phonTools::pb52.
|
||||
# These are real measured values.
|
||||
# * AX (schwa) F1/F2/F3 = neutral uniform-tube resonances (2n-1)*500 — a PHYSICS
|
||||
# value (Fant), not a P&B measurement.
|
||||
# * OW is a diphthong; its listed steady target is a conventional synthesis value,
|
||||
# not a P&B monophthong measurement.
|
||||
# * CONSONANT loci (M,N,NG,L,R,W,Y,Z,DH,V,S,F,HH) and ALL BANDWIDTHS (B1,B2,B3)
|
||||
# and dur/amp are STANDARD FORMANT-SYNTHESIS conventions (Klatt 1980, JASA 67:971
|
||||
# "Software for a cascade/parallel formant synthesizer") — engineering defaults,
|
||||
# NOT per-phoneme field measurements. Labeled as such, not attributed to P&B.
|
||||
# Format: SYM|F1|F2|F3|B1|B2|B3|voiced|nasal|dur_ms|amp|class|example
|
||||
IY|270|2290|3010|60|90|150|1|0|130|100|vowel|beet
|
||||
IH|390|1990|2550|70|100|150|1|0|110|100|vowel|bit
|
||||
EH|530|1840|2480|80|100|150|1|0|130|100|vowel|bet
|
||||
AE|660|1720|2410|90|110|150|1|0|150|100|vowel|bat
|
||||
AA|730|1090|2440|90|110|150|1|0|150|100|vowel|bot
|
||||
AO|570|840|2410|80|100|150|1|0|140|100|vowel|bought
|
||||
UH|440|1020|2240|70|100|150|1|0|110|100|vowel|book
|
||||
UW|300|870|2240|70|90|150|1|0|140|100|vowel|boot
|
||||
AH|640|1190|2390|80|100|150|1|0|110|95|vowel|but
|
||||
ER|490|1350|1690|80|100|120|1|0|140|95|vowel|bird
|
||||
AX|500|1500|2500|80|100|150|1|0|80|85|vowel|about
|
||||
OW|490|910|2380|80|100|150|1|0|140|100|vowel|boat
|
||||
M|250|900|2200|90|120|180|1|1|80|60|nasal|map
|
||||
N|250|1700|2600|90|120|180|1|1|80|60|nasal|nap
|
||||
NG|250|2300|2700|90|120|180|1|1|80|60|nasal|sing
|
||||
L|360|1300|2600|80|110|160|1|0|70|80|approximant|lip
|
||||
R|490|1350|1600|80|110|120|1|0|80|85|approximant|rip
|
||||
W|300|610|2200|70|100|160|1|0|70|80|approximant|wet
|
||||
Y|270|2290|3010|60|90|150|1|0|60|80|approximant|yet
|
||||
Z|300|1700|2500|100|150|200|1|0|90|55|fricative|zoo
|
||||
DH|300|1400|2500|100|150|200|1|0|70|55|fricative|the
|
||||
V|300|1000|2300|100|150|200|1|0|70|55|fricative|van
|
||||
S|320|1700|2500|200|200|250|0|0|110|45|fricative|see
|
||||
F|300|1200|2400|200|200|250|0|0|100|40|fricative|fee
|
||||
HH|500|1500|2500|200|250|300|0|0|70|40|fricative|hat
|
||||
SIL|500|1500|2500|100|100|100|0|0|55|0|silence|_
|
||||
Binary file not shown.
@@ -1,136 +0,0 @@
|
||||
// accent.el - A British-RP ACCENT as an INGESTED TRANSFORM-GEOMETRY, composed
|
||||
// onto the voice (voice (+) accent, SEPARABLE). Reads elp/data/british-accent.psv
|
||||
// into an accent MANIFOLD in the engram (override nodes + a shared accent hub),
|
||||
// and the render reads the RP formant overrides + the non-rhotic rule back from
|
||||
// that geometry. NO accent targets live in code — same discipline as the base
|
||||
// phonetics. PROVENANCE NOTE: the RP Hz values are PROVISIONAL (reconstructed-
|
||||
// from-knowledge approximations, cite Deterding1997 / Hawkins&Midgley2005 /
|
||||
// Wells1982) pending transcription from the published tables — the PIPELINE is
|
||||
// the deliverable; exact values are being source-verified separately.
|
||||
|
||||
fn ingest_accent(path: String) -> [String] {
|
||||
let content: String = fs_read(path)
|
||||
let lines: [String] = str_split(content, "\n")
|
||||
let nl: Int = native_list_len(lines)
|
||||
let amap: [String] = native_list_empty()
|
||||
let hub: String = engram_node("accent british-rp prov=PROVISIONAL cite=Deterding1997-HawkinsMidgley2005-Wells1982", "Accent", 80)
|
||||
let li: Int = 0
|
||||
while li < nl {
|
||||
let line: String = native_list_get(lines, li)
|
||||
let ll: Int = str_len(line)
|
||||
let skip: Int = 0
|
||||
if ll < 3 {
|
||||
skip = 1
|
||||
}
|
||||
if skip == 0 {
|
||||
let first: Int = str_char_code(line, 0)
|
||||
if first == 35 {
|
||||
skip = 1
|
||||
}
|
||||
}
|
||||
if skip == 0 {
|
||||
let f: [String] = str_split(line, "|")
|
||||
let nf: Int = native_list_len(f)
|
||||
if nf >= 6 {
|
||||
let key: String = native_list_get(f, 0)
|
||||
let f1: String = native_list_get(f, 1)
|
||||
let f2: String = native_list_get(f, 2)
|
||||
let f3: String = native_list_get(f, 3)
|
||||
let kind: String = native_list_get(f, 4)
|
||||
let set: String = native_list_get(f, 5)
|
||||
let cont: String = "accent british-rp " + key + " f1=" + f1 + " f2=" + f2 + " f3=" + f3 + " kind=" + kind + " set=" + set + " prov=PROVISIONAL cite=Deterding1997-HawkinsMidgley2005-Wells1982"
|
||||
let id: String = engram_node(cont, "AccentTarget", 80)
|
||||
amap = native_list_append(amap, key)
|
||||
amap = native_list_append(amap, cont)
|
||||
engram_connect(id, hub, 80, "of_accent")
|
||||
}
|
||||
}
|
||||
li = li + 1
|
||||
}
|
||||
return amap
|
||||
}
|
||||
|
||||
// RP formant override for a phoneme, read from the accent manifold. Returns
|
||||
// [f1,f2,f3] for a vowel_override record, or an empty list if none / a rule.
|
||||
fn accent_formants(amap: [String], code: String) -> [Int] {
|
||||
let out: [Int] = native_list_empty()
|
||||
let id: String = sp_map_get(amap, code)
|
||||
if str_eq(id, "") {
|
||||
return out
|
||||
}
|
||||
let j: String = id
|
||||
let isrule: Int = str_index_of(j, "drop_coda")
|
||||
if isrule >= 0 {
|
||||
return out
|
||||
}
|
||||
let f1: Int = parse_uint_from(j, "f1=")
|
||||
if f1 <= 0 {
|
||||
return out
|
||||
}
|
||||
let out = native_list_append(out, f1)
|
||||
let out = native_list_append(out, parse_uint_from(j, "f2="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "f3="))
|
||||
return out
|
||||
}
|
||||
|
||||
// Is this accent non-rhotic? (reads the R rule node from the manifold)
|
||||
fn is_nonrhotic(amap: [String]) -> Int {
|
||||
let id: String = sp_map_get(amap, "R")
|
||||
if str_eq(id, "") {
|
||||
return 0
|
||||
}
|
||||
let hit: Int = str_index_of(id, "drop_coda")
|
||||
if hit >= 0 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Is this symbol a vowel? Membership in the vowel-set derived from the phonetics
|
||||
// source's class column (phonological structure — the FORMANT NUMBERS still come
|
||||
// from the organ manifold; this is only the categorical class for the rule).
|
||||
fn is_vowel_sym(vset: [String], sym: String) -> Int {
|
||||
let n: Int = native_list_len(vset)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
if str_eq(native_list_get(vset, i), sym) {
|
||||
return 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Non-rhotic transform: drop a post-vocalic CODA /R/ — an R whose next non-SIL
|
||||
// phoneme is NOT a vowel (a consonant, or end of utterance). Keep INTERVOCALIC/
|
||||
// onset R (next non-SIL phoneme is a vowel, e.g. the medial R in N UW R AA N).
|
||||
fn apply_rhoticity(codes: [String], vset: [String]) -> [String] {
|
||||
let n: Int = native_list_len(codes)
|
||||
let out: [String] = native_list_empty()
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let c: String = native_list_get(codes, i)
|
||||
let keep: Int = 1
|
||||
if str_eq(c, "R") {
|
||||
let jx: Int = i + 1
|
||||
let nextv: Int = 0
|
||||
while jx < n {
|
||||
let ncode: String = native_list_get(codes, jx)
|
||||
if str_eq(ncode, "SIL") {
|
||||
jx = jx + 1
|
||||
} else {
|
||||
nextv = is_vowel_sym(vset, ncode)
|
||||
jx = n + 1000
|
||||
}
|
||||
}
|
||||
if nextv == 0 {
|
||||
keep = 0
|
||||
}
|
||||
}
|
||||
if keep == 1 {
|
||||
out = native_list_append(out, c)
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
// organ-read.el - Route the render's GEOMETRY READ through the ingest ORGAN's
|
||||
// saved engram files (the coordinator's source of truth). For each file we
|
||||
// engram_load() it, engram_scan_nodes_json(limit, offset) to get the node array,
|
||||
// and cache each node's self-contained CONTENT string keyed by symbol. Because
|
||||
// the cached value carries the numbers ("... f1=730 ..."), the cache SURVIVES the
|
||||
// store being REPLACED by the next engram_load — so we load+cache phonetics
|
||||
// FIRST, then load+cache accent. The .psv path remains a fallback.
|
||||
//
|
||||
// engram_scan_nodes_json(limit, offset) takes NO query; it returns nodes
|
||||
// salience-sorted, so limit must be >= node count and we filter client-side.
|
||||
// (engram_search / engram_scan_nodes return len-5 garbage — unused.)
|
||||
|
||||
// Find every occurrence of `marker` in the scan JSON; for each, cache
|
||||
// sym -> a 150-char content window (enough to hold f1..amp). Duplicates from the
|
||||
// node's "content" and "label" fields are harmless (first match wins on read).
|
||||
fn organ_cache(j: String, marker: String, mlen: Int, win_len: Int, need: String) -> [String] {
|
||||
let m: [String] = native_list_empty()
|
||||
let jl: Int = str_len(j)
|
||||
let off: Int = 0
|
||||
while off < jl {
|
||||
let rest: String = str_slice(j, off, jl)
|
||||
let p: Int = str_index_of(rest, marker)
|
||||
if p < 0 {
|
||||
off = jl
|
||||
} else {
|
||||
let abs: Int = off + p
|
||||
let win: String = str_slice(j, abs, abs + win_len)
|
||||
let after: String = str_slice(win, mlen, str_len(win))
|
||||
let sp: Int = str_index_of(after, " ")
|
||||
let hasneed: Int = str_index_of(win, need)
|
||||
if sp > 0 {
|
||||
if hasneed >= 0 {
|
||||
let sym: String = str_slice(after, 0, sp)
|
||||
m = native_list_append(m, sym)
|
||||
m = native_list_append(m, win)
|
||||
}
|
||||
}
|
||||
off = abs + mlen
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Load the phonetics organ file and cache sym -> content. mlen("phoneme ")=8.
|
||||
fn organ_pmap(path: String) -> [String] {
|
||||
let ok: Bool = engram_load(path)
|
||||
if ok == false {
|
||||
return native_list_empty()
|
||||
}
|
||||
let j: String = engram_scan_nodes_json(600, 0)
|
||||
return organ_cache(j, "phoneme ", 8, 150, "f1=")
|
||||
}
|
||||
|
||||
// Load the accent organ file and cache sym -> content. mlen("accent_target ")=14.
|
||||
// Vowel overrides carry f1=..; the R rule carries drop_coda_r (need="=" matches
|
||||
// both, i.e. any well-formed accent_target field).
|
||||
fn organ_amap(path: String) -> [String] {
|
||||
let ok: Bool = engram_load(path)
|
||||
if ok == false {
|
||||
return native_list_empty()
|
||||
}
|
||||
let j: String = engram_scan_nodes_json(600, 0)
|
||||
return organ_cache(j, "accent_target ", 14, 90, "=")
|
||||
}
|
||||
|
||||
// Vowel-set (categorical class) from the phonetics .psv class column.
|
||||
fn organ_vset(path: String) -> [String] {
|
||||
let content: String = fs_read(path)
|
||||
let lines: [String] = str_split(content, "\n")
|
||||
let nl: Int = native_list_len(lines)
|
||||
let v: [String] = native_list_empty()
|
||||
let li: Int = 0
|
||||
while li < nl {
|
||||
let line: String = native_list_get(lines, li)
|
||||
let ok: Int = 1
|
||||
if str_len(line) < 5 {
|
||||
ok = 0
|
||||
}
|
||||
if ok == 1 {
|
||||
if str_char_code(line, 0) == 35 {
|
||||
ok = 0
|
||||
}
|
||||
}
|
||||
if ok == 1 {
|
||||
let f: [String] = str_split(line, "|")
|
||||
if native_list_len(f) >= 12 {
|
||||
if str_eq(native_list_get(f, 11), "vowel") {
|
||||
v = native_list_append(v, native_list_get(f, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
li = li + 1
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Word -> phoneme-sequence cache from lexicon.psv (engram-independent).
|
||||
fn organ_lex(path: String) -> [String] {
|
||||
let content: String = fs_read(path)
|
||||
let lines: [String] = str_split(content, "\n")
|
||||
let nl: Int = native_list_len(lines)
|
||||
let m: [String] = native_list_empty()
|
||||
let li: Int = 0
|
||||
while li < nl {
|
||||
let line: String = native_list_get(lines, li)
|
||||
let ok: Int = 1
|
||||
if str_len(line) < 3 {
|
||||
ok = 0
|
||||
}
|
||||
if ok == 1 {
|
||||
if str_char_code(line, 0) == 35 {
|
||||
ok = 0
|
||||
}
|
||||
}
|
||||
if ok == 1 {
|
||||
let f: [String] = str_split(line, "|")
|
||||
if native_list_len(f) >= 2 {
|
||||
m = native_list_append(m, native_list_get(f, 0))
|
||||
m = native_list_append(m, native_list_get(f, 1))
|
||||
}
|
||||
}
|
||||
li = li + 1
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
// speech-ingest.el - The native LOAD step of the ingest organ, for the SPEECH
|
||||
// primitives. Reads the acoustic-phonetics SOURCE (elp/data/phonetics.psv) and
|
||||
// the pronunciation lexicon SOURCE (elp/data/lexicon.psv) and emits a PHONEME
|
||||
// MANIFOLD into the engram: one node per phoneme (faithful, provenance-tagged
|
||||
// content) + is_a edges to phoneme-class nodes (a discrete manifold, not islands).
|
||||
// The render then PULLS phoneme geometry back from the engram via phon_geo —
|
||||
// zero phonetic numbers in code. Source -> manifold -> merge; the same output
|
||||
// the polymorphic ingest organ will produce and subsume.
|
||||
|
||||
// -- small parsing helpers ---------------------------------------------------
|
||||
fn sp_map_get(pairs: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(pairs)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(pairs, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(pairs, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// read the unsigned integer that follows `key` inside string s (e.g. key "F1=")
|
||||
fn parse_uint_from(s: String, key: String) -> Int {
|
||||
let idx: Int = str_index_of(s, key)
|
||||
if idx < 0 {
|
||||
return 0
|
||||
}
|
||||
let start: Int = idx + str_len(key)
|
||||
let n: Int = str_len(s)
|
||||
let i: Int = start
|
||||
let val: Int = 0
|
||||
while i < n {
|
||||
let c: Int = str_char_code(s, i)
|
||||
if c >= 48 {
|
||||
if c <= 57 {
|
||||
val = val * 10 + (c - 48)
|
||||
i = i + 1
|
||||
} else {
|
||||
i = n
|
||||
}
|
||||
} else {
|
||||
i = n
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
fn clean_word(w: String) -> String {
|
||||
let low: String = str_to_lower(w)
|
||||
let n: Int = str_len(low)
|
||||
let out: String = ""
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let c: Int = str_char_code(low, i)
|
||||
if c >= 97 {
|
||||
if c <= 122 {
|
||||
out = out + str_char_at(low, i)
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// -- INGEST: acoustic-phonetics source -> phoneme manifold in the engram ------
|
||||
// Returns the symbol -> node-id index (pmap) the render reads geometry through.
|
||||
fn ingest_phonetics(path: String) -> [String] {
|
||||
let content: String = fs_read(path)
|
||||
let lines: [String] = str_split(content, "\n")
|
||||
let nl: Int = native_list_len(lines)
|
||||
let pmap: [String] = native_list_empty()
|
||||
let classmap: [String] = native_list_empty()
|
||||
let li: Int = 0
|
||||
while li < nl {
|
||||
let line: String = native_list_get(lines, li)
|
||||
let ll: Int = str_len(line)
|
||||
let skip: Int = 0
|
||||
if ll < 5 {
|
||||
skip = 1
|
||||
}
|
||||
if skip == 0 {
|
||||
let first: Int = str_char_code(line, 0)
|
||||
if first == 35 {
|
||||
skip = 1
|
||||
}
|
||||
}
|
||||
if skip == 0 {
|
||||
let f: [String] = str_split(line, "|")
|
||||
let nf: Int = native_list_len(f)
|
||||
if nf >= 12 {
|
||||
let sym: String = native_list_get(f, 0)
|
||||
let f1: String = native_list_get(f, 1)
|
||||
let f2: String = native_list_get(f, 2)
|
||||
let f3: String = native_list_get(f, 3)
|
||||
let b1: String = native_list_get(f, 4)
|
||||
let b2: String = native_list_get(f, 5)
|
||||
let b3: String = native_list_get(f, 6)
|
||||
let vo: String = native_list_get(f, 7)
|
||||
let na: String = native_list_get(f, 8)
|
||||
let du: String = native_list_get(f, 9)
|
||||
let am: String = native_list_get(f, 10)
|
||||
let cls: String = native_list_get(f, 11)
|
||||
let cont: String = "phoneme " + sym + " | f1=" + f1 + " f2=" + f2 + " f3=" + f3 + " bw1=" + b1 + " bw2=" + b2 + " bw3=" + b3 + " voiced=" + vo + " nasal=" + na + " dur=" + du + " amp=" + am + " class=" + cls + " src=PetersonBarney1952-Hillenbrand1995"
|
||||
let id: String = engram_node(cont, "Phoneme", 80)
|
||||
pmap = native_list_append(pmap, sym)
|
||||
pmap = native_list_append(pmap, cont)
|
||||
// manifold edge: phoneme is_a class
|
||||
let cid: String = sp_map_get(classmap, cls)
|
||||
if str_eq(cid, "") {
|
||||
cid = engram_node("phoneme-class " + cls + " src=acoustic-phonetics", "PhonemeClass", 80)
|
||||
classmap = native_list_append(classmap, cls)
|
||||
classmap = native_list_append(classmap, cid)
|
||||
}
|
||||
engram_connect(id, cid, 80, "is_a")
|
||||
}
|
||||
}
|
||||
li = li + 1
|
||||
}
|
||||
return pmap
|
||||
}
|
||||
|
||||
// -- INGEST: pronunciation lexicon source -> word nodes ----------------------
|
||||
fn ingest_lexicon(path: String) -> [String] {
|
||||
let content: String = fs_read(path)
|
||||
let lines: [String] = str_split(content, "\n")
|
||||
let nl: Int = native_list_len(lines)
|
||||
let lmap: [String] = native_list_empty()
|
||||
let li: Int = 0
|
||||
while li < nl {
|
||||
let line: String = native_list_get(lines, li)
|
||||
let ll: Int = str_len(line)
|
||||
let skip: Int = 0
|
||||
if ll < 3 {
|
||||
skip = 1
|
||||
}
|
||||
if skip == 0 {
|
||||
let first: Int = str_char_code(line, 0)
|
||||
if first == 35 {
|
||||
skip = 1
|
||||
}
|
||||
}
|
||||
if skip == 0 {
|
||||
let f: [String] = str_split(line, "|")
|
||||
let nf: Int = native_list_len(f)
|
||||
if nf >= 2 {
|
||||
let word: String = native_list_get(f, 0)
|
||||
let seq: String = native_list_get(f, 1)
|
||||
let id: String = engram_node("word " + word + " phonemes " + seq + " src=lexicon", "Pronunciation", 80)
|
||||
lmap = native_list_append(lmap, word)
|
||||
lmap = native_list_append(lmap, seq)
|
||||
}
|
||||
}
|
||||
li = li + 1
|
||||
}
|
||||
return lmap
|
||||
}
|
||||
|
||||
// -- READ geometry back from the engram (the render's afferent lookup) --------
|
||||
// phon_geo(sym) -> [F1,F2,F3,B1,B2,B3,voiced,nasal,dur,amp], parsed from the
|
||||
// ingested phoneme node's content. NO formant numbers live in this code.
|
||||
fn phon_geo(pmap: [String], sym: String) -> [Int] {
|
||||
let id: String = sp_map_get(pmap, sym)
|
||||
if str_eq(id, "") {
|
||||
id = sp_map_get(pmap, "AX")
|
||||
}
|
||||
let out: [Int] = native_list_empty()
|
||||
if str_eq(id, "") {
|
||||
let out = native_list_append(out, 500)
|
||||
let out = native_list_append(out, 1500)
|
||||
let out = native_list_append(out, 2500)
|
||||
let out = native_list_append(out, 80)
|
||||
let out = native_list_append(out, 100)
|
||||
let out = native_list_append(out, 150)
|
||||
let out = native_list_append(out, 1)
|
||||
let out = native_list_append(out, 0)
|
||||
let out = native_list_append(out, 80)
|
||||
let out = native_list_append(out, 80)
|
||||
return out
|
||||
}
|
||||
let j: String = id
|
||||
let out = native_list_append(out, parse_uint_from(j, "f1="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "f2="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "f3="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "bw1="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "bw2="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "bw3="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "voiced="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "nasal="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "dur="))
|
||||
let out = native_list_append(out, parse_uint_from(j, "amp="))
|
||||
return out
|
||||
}
|
||||
|
||||
// word -> phoneme codes, read from the ingested lexicon node.
|
||||
fn word_phonemes(lmap: [String], word: String) -> [String] {
|
||||
let id: String = sp_map_get(lmap, word)
|
||||
if str_eq(id, "") {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "AX")
|
||||
return r
|
||||
}
|
||||
return str_split(id, " ")
|
||||
}
|
||||
|
||||
// realized text -> flat phoneme-code sequence (SIL between words + at ends).
|
||||
fn text_phonemes(lmap: [String], text: String) -> [String] {
|
||||
let words: [String] = str_split(text, " ")
|
||||
let nw: Int = native_list_len(words)
|
||||
let seq: [String] = native_list_empty()
|
||||
let seq = native_list_append(seq, "SIL")
|
||||
let wi: Int = 0
|
||||
while wi < nw {
|
||||
let raw: String = native_list_get(words, wi)
|
||||
let w: String = clean_word(raw)
|
||||
if str_eq(w, "") {
|
||||
wi = wi + 1
|
||||
} else {
|
||||
let ph: [String] = word_phonemes(lmap, w)
|
||||
let np: Int = native_list_len(ph)
|
||||
let pi: Int = 0
|
||||
while pi < np {
|
||||
let code: String = native_list_get(ph, pi)
|
||||
seq = native_list_append(seq, code)
|
||||
pi = pi + 1
|
||||
}
|
||||
seq = native_list_append(seq, "SIL")
|
||||
wi = wi + 1
|
||||
}
|
||||
}
|
||||
return seq
|
||||
}
|
||||
@@ -1,460 +0,0 @@
|
||||
// speech.el - The native SPEECH render path + voice-by-imitation extractor.
|
||||
//
|
||||
// Speech = the AUDIO surface (surface_profile_audio) rendering LANGUAGE-meaning
|
||||
// through a VOICE signature. The realizer's language faculty supplies the words
|
||||
// (meaning -> sem_realize -> text); this module turns text -> phonemes (phonetics.el)
|
||||
// -> a formant-target track over time -> SUPERPOSES formant resonances over a
|
||||
// glottal source (own-core formant synthesis, the exact integer mirror of the
|
||||
// music additive superpose) -> own-core PCM/WAV. Two paths:
|
||||
// (1) RENDER: speak(text, voice) -> spoken WAV.
|
||||
// (2) IMITATE: voice_analyze(pcm) -> a voice signature grabbed BY EAR
|
||||
// (autocorrelation pitch + integer-DFT formant peaks), then render
|
||||
// any new meaning in that voice. An impression, not a corpus.
|
||||
// All integer/fixed-point (EL float arithmetic is unusable).
|
||||
|
||||
// -- Own-core integer sine (Bhaskara I), phase 0..65535 = one cycle -----------
|
||||
fn sp_sin(phase: Int) -> Int {
|
||||
let deg: Int = phase * 360 / 65536
|
||||
let neg: Int = 0
|
||||
if deg > 180 {
|
||||
deg = deg - 180
|
||||
neg = 1
|
||||
}
|
||||
let t: Int = deg * (180 - deg)
|
||||
let num: Int = 32767 * 4 * t
|
||||
let den: Int = 40500 - t
|
||||
let v: Int = num / den
|
||||
if neg == 1 {
|
||||
v = 0 - v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
fn sp_cos(phase: Int) -> Int {
|
||||
let p: Int = phase + 16384
|
||||
p = p - (p / 65536) * 65536
|
||||
return sp_sin(p)
|
||||
}
|
||||
|
||||
// One formant resonance (Lorentzian peak), Q15. Peak 32767 at f=fc.
|
||||
fn sp_gain(f: Int, fc: Int, bw: Int) -> Int {
|
||||
let d: Int = f - fc
|
||||
let den: Int = d * d + bw * bw
|
||||
let num: Int = 32767 * bw * bw
|
||||
return num / den
|
||||
}
|
||||
|
||||
fn sp_isqrt(n: Int) -> Int {
|
||||
if n <= 0 {
|
||||
return 0
|
||||
}
|
||||
let x: Int = n
|
||||
let y: Int = (x + 1) / 2
|
||||
while y < x {
|
||||
x = y
|
||||
y = (x + n / x) / 2
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// -- WAV serializer (thin medium; the only non-DSP glue) ---------------------
|
||||
fn wav_le16(buf: String, off: Int, v: Int) -> String {
|
||||
let u: Int = v
|
||||
if u < 0 {
|
||||
u = u + 65536
|
||||
}
|
||||
let lo: Int = u - (u / 256) * 256
|
||||
let hi: Int = u / 256
|
||||
let b: String = __str_set_char(buf, off, lo)
|
||||
b = __str_set_char(b, off + 1, hi)
|
||||
return b
|
||||
}
|
||||
|
||||
fn wav_le32(buf: String, off: Int, v: Int) -> String {
|
||||
let b0: Int = v - (v / 256) * 256
|
||||
let r1: Int = v / 256
|
||||
let b1: Int = r1 - (r1 / 256) * 256
|
||||
let r2: Int = r1 / 256
|
||||
let b2: Int = r2 - (r2 / 256) * 256
|
||||
let b3: Int = r2 / 256
|
||||
let b: String = __str_set_char(buf, off, b0)
|
||||
b = __str_set_char(b, off + 1, b1)
|
||||
b = __str_set_char(b, off + 2, b2)
|
||||
b = __str_set_char(b, off + 3, b3)
|
||||
return b
|
||||
}
|
||||
|
||||
fn wav_ascii(buf: String, off: Int, s: String) -> String {
|
||||
let n: Int = str_len(s)
|
||||
let i: Int = 0
|
||||
let b: String = buf
|
||||
while i < n {
|
||||
let c: Int = str_char_code(s, i)
|
||||
b = __str_set_char(b, off + i, c)
|
||||
i = i + 1
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
fn write_wav(samples: [Int], sr: Int, path: String) -> Bool {
|
||||
let ns: Int = native_list_len(samples)
|
||||
let datalen: Int = ns * 2
|
||||
let total: Int = 44 + datalen
|
||||
let buf: String = __str_alloc(total)
|
||||
buf = wav_ascii(buf, 0, "RIFF")
|
||||
buf = wav_le32(buf, 4, 36 + datalen)
|
||||
buf = wav_ascii(buf, 8, "WAVE")
|
||||
buf = wav_ascii(buf, 12, "fmt ")
|
||||
buf = wav_le32(buf, 16, 16)
|
||||
buf = wav_le16(buf, 20, 1)
|
||||
buf = wav_le16(buf, 22, 1)
|
||||
buf = wav_le32(buf, 24, sr)
|
||||
buf = wav_le32(buf, 28, sr * 2)
|
||||
buf = wav_le16(buf, 32, 2)
|
||||
buf = wav_le16(buf, 34, 16)
|
||||
buf = wav_ascii(buf, 36, "data")
|
||||
buf = wav_le32(buf, 40, datalen)
|
||||
let j: Int = 0
|
||||
let off: Int = 44
|
||||
while j < ns {
|
||||
let raw: Int = native_list_get(samples, j)
|
||||
buf = wav_le16(buf, off, raw)
|
||||
off = off + 2
|
||||
j = j + 1
|
||||
}
|
||||
return __fs_write_bytes(path, buf, total)
|
||||
}
|
||||
|
||||
// One formant resonance as a float Lorentzian peak (own-core physics).
|
||||
fn fgain(f: Float, fc: Float, bw: Float) -> Float {
|
||||
let d: Float = f - fc
|
||||
return (bw * bw) / (d * d + bw * bw)
|
||||
}
|
||||
|
||||
// His PITCH MELODY from measured prosody [f0_median, f0_min, f0_max, declination].
|
||||
// A natural statement shape over the utterance: onset rise to the median, a
|
||||
// near-flat body (his declination is ~0.6 Hz/s), and a final fall toward f0_min.
|
||||
// Follows his melody + range, not a fixed 0.85 decline. gidx/total = position.
|
||||
fn prosody_f0(pros: [Int], gidx: Int, total: Int) -> Int {
|
||||
let med: Int = native_list_get(pros, 0)
|
||||
let lo: Int = native_list_get(pros, 1)
|
||||
let hi: Int = native_list_get(pros, 2)
|
||||
let p: Int = gidx * 1000 / total
|
||||
let f0: Int = med
|
||||
if p < 150 {
|
||||
f0 = lo + (med - lo) * p / 150
|
||||
} else {
|
||||
if p > 700 {
|
||||
f0 = med + (lo - med) * (p - 700) / 300
|
||||
} else {
|
||||
f0 = med
|
||||
}
|
||||
}
|
||||
if f0 < lo {
|
||||
f0 = lo
|
||||
}
|
||||
if f0 > hi {
|
||||
f0 = hi
|
||||
}
|
||||
return f0
|
||||
}
|
||||
|
||||
// -- The render: phoneme codes + voice signature -> normalized PCM samples ----
|
||||
// Formant geometry per phoneme is READ FROM THE ENGRAM (pmap) via phon_geo — no
|
||||
// table in code. The optional ACCENT map (amap) composes a transform onto the
|
||||
// voice (voice (+) accent, separable): RP formant overrides read from the accent
|
||||
// manifold + a non-rhotic coda-R drop. Empty amap = base General-American.
|
||||
// Synthesis is FLOAT: a real phase accumulator + math_sin, superposition physics.
|
||||
fn synth_codes_accent(codes0: [String], voice: [String], pmap: [String], amap: [String], vset: [String], vmap: [String], prosody: [Int]) -> [Int] {
|
||||
let sr: Int = 16000
|
||||
let srf: Float = 16000.0
|
||||
let two_pi: Float = 6.283185307
|
||||
let kf: Int = voice_get_int(voice, "kf")
|
||||
let f0s: Int = voice_get_int(voice, "f0")
|
||||
let f0e: Int = voice_get_int(voice, "f0_end")
|
||||
let durm: Int = voice_get_int(voice, "dur")
|
||||
if kf <= 0 {
|
||||
kf = 1000
|
||||
}
|
||||
if durm <= 0 {
|
||||
durm = 1000
|
||||
}
|
||||
let use_accent: Int = 0
|
||||
if native_list_len(amap) > 0 {
|
||||
use_accent = 1
|
||||
}
|
||||
let codes: [String] = codes0
|
||||
if use_accent == 1 {
|
||||
if is_nonrhotic(amap) == 1 {
|
||||
codes = apply_rhoticity(codes0, vset)
|
||||
}
|
||||
}
|
||||
let nc: Int = native_list_len(codes)
|
||||
|
||||
// pass 1: per-segment sample counts + total
|
||||
let segn: [Int] = native_list_empty()
|
||||
let total: Int = 0
|
||||
let ci: Int = 0
|
||||
while ci < nc {
|
||||
let code: String = native_list_get(codes, ci)
|
||||
let p: [Int] = phon_geo(pmap, code)
|
||||
let durms: Int = native_list_get(p, 8)
|
||||
let ns: Int = durms * 16 * durm / 1000
|
||||
segn = native_list_append(segn, ns)
|
||||
total = total + ns
|
||||
ci = ci + 1
|
||||
}
|
||||
if total <= 0 {
|
||||
total = 1
|
||||
}
|
||||
|
||||
// pass 2: synthesize
|
||||
let samples: [Int] = native_list_empty()
|
||||
let phasef: Float = 0.0
|
||||
let gidx: Int = 0
|
||||
let prevF1: Int = 500 * kf / 1000
|
||||
let prevF2: Int = 1500 * kf / 1000
|
||||
let prevF3: Int = 2500 * kf / 1000
|
||||
let nstate: Int = 22695
|
||||
let maxabs: Int = 1
|
||||
|
||||
let ci2: Int = 0
|
||||
while ci2 < nc {
|
||||
let code: String = native_list_get(codes, ci2)
|
||||
let p: [Int] = phon_geo(pmap, code)
|
||||
let rf1: Int = native_list_get(p, 0)
|
||||
let rf2: Int = native_list_get(p, 1)
|
||||
let rf3: Int = native_list_get(p, 2)
|
||||
if use_accent == 1 {
|
||||
let ov: [Int] = accent_formants(amap, code)
|
||||
if native_list_len(ov) >= 3 {
|
||||
rf1 = native_list_get(ov, 0)
|
||||
rf2 = native_list_get(ov, 1)
|
||||
rf3 = native_list_get(ov, 2)
|
||||
}
|
||||
}
|
||||
// HIS measured vowel target overrides the generic/kf path (absolute Hz —
|
||||
// his formants already encode his vocal tract, so no kf scaling).
|
||||
let usekf: Int = 1
|
||||
if native_list_len(vmap) > 0 {
|
||||
let hv: [Int] = vmap_get(vmap, code)
|
||||
if native_list_len(hv) >= 3 {
|
||||
rf1 = native_list_get(hv, 0)
|
||||
rf2 = native_list_get(hv, 1)
|
||||
rf3 = native_list_get(hv, 2)
|
||||
usekf = 0
|
||||
}
|
||||
}
|
||||
let F1t: Int = rf1 * kf / 1000
|
||||
let F2t: Int = rf2 * kf / 1000
|
||||
let F3t: Int = rf3 * kf / 1000
|
||||
if usekf == 0 {
|
||||
F1t = rf1
|
||||
F2t = rf2
|
||||
F3t = rf3
|
||||
}
|
||||
let B1: Int = native_list_get(p, 3)
|
||||
let B2: Int = native_list_get(p, 4)
|
||||
let B3: Int = native_list_get(p, 5)
|
||||
let voiced: Int = native_list_get(p, 6)
|
||||
let ampv: Int = native_list_get(p, 9)
|
||||
let ns: Int = native_list_get(segn, ci2)
|
||||
let trans: Int = ns / 2
|
||||
if trans > 560 {
|
||||
trans = 560
|
||||
}
|
||||
if trans < 1 {
|
||||
trans = 1
|
||||
}
|
||||
let k: Int = 0
|
||||
while k < ns {
|
||||
let cF1: Int = F1t
|
||||
let cF2: Int = F2t
|
||||
let cF3: Int = F3t
|
||||
if k < trans {
|
||||
cF1 = prevF1 + (F1t - prevF1) * k / trans
|
||||
cF2 = prevF2 + (F2t - prevF2) * k / trans
|
||||
cF3 = prevF3 + (F3t - prevF3) * k / trans
|
||||
}
|
||||
let f0c: Int = f0s + (f0e - f0s) * gidx / total
|
||||
if native_list_len(prosody) >= 3 {
|
||||
f0c = prosody_f0(prosody, gidx, total)
|
||||
}
|
||||
if f0c < 40 {
|
||||
f0c = 40
|
||||
}
|
||||
let env: Int = 32767
|
||||
let ar: Int = 96
|
||||
if k < ar {
|
||||
env = 32767 * k / ar
|
||||
}
|
||||
let tail: Int = ns - k
|
||||
if tail < ar {
|
||||
env = 32767 * tail / ar
|
||||
}
|
||||
let f0cf: Float = int_to_float(f0c)
|
||||
phasef = phasef + two_pi * f0cf / srf
|
||||
if phasef > two_pi {
|
||||
phasef = phasef - two_pi
|
||||
}
|
||||
|
||||
let s: Int = 0
|
||||
if voiced == 1 {
|
||||
let cF1f: Float = int_to_float(cF1)
|
||||
let cF2f: Float = int_to_float(cF2)
|
||||
let cF3f: Float = int_to_float(cF3)
|
||||
let B1f: Float = int_to_float(B1)
|
||||
let B2f: Float = int_to_float(B2)
|
||||
let B3f: Float = int_to_float(B3)
|
||||
let acc: Float = 0.0
|
||||
let h: Int = 1
|
||||
while h <= 50 {
|
||||
let hf: Float = int_to_float(h)
|
||||
let fhf: Float = hf * f0cf
|
||||
if fhf < 7900.0 {
|
||||
let sv: Float = math_sin(phasef * hf)
|
||||
let src: Float = 1.0 / hf
|
||||
let g1: Float = fgain(fhf, cF1f, B1f)
|
||||
let g2: Float = fgain(fhf, cF2f, B2f)
|
||||
let g3: Float = fgain(fhf, cF3f, B3f)
|
||||
let g: Float = g1 + g2 + g3
|
||||
acc = acc + src * g * sv
|
||||
}
|
||||
h = h + 1
|
||||
}
|
||||
s = float_to_int(acc * 4000.0)
|
||||
} else {
|
||||
if ampv > 0 {
|
||||
nstate = nstate * 1103515245 + 12345
|
||||
nstate = nstate - (nstate / 2147483648) * 2147483648
|
||||
if nstate < 0 {
|
||||
nstate = 0 - nstate
|
||||
}
|
||||
let nz: Int = nstate / 32768 - 32768
|
||||
s = nz
|
||||
}
|
||||
}
|
||||
s = s * ampv / 100
|
||||
s = s * env / 32767
|
||||
samples = native_list_append(samples, s)
|
||||
let a: Int = s
|
||||
if a < 0 {
|
||||
a = 0 - a
|
||||
}
|
||||
if a > maxabs {
|
||||
maxabs = a
|
||||
}
|
||||
gidx = gidx + 1
|
||||
k = k + 1
|
||||
}
|
||||
prevF1 = F1t
|
||||
prevF2 = F2t
|
||||
prevF3 = F3t
|
||||
ci2 = ci2 + 1
|
||||
}
|
||||
|
||||
// normalize to int16 range (~22000 peak)
|
||||
let out: [Int] = native_list_empty()
|
||||
let ntot: Int = native_list_len(samples)
|
||||
let j: Int = 0
|
||||
while j < ntot {
|
||||
let raw: Int = native_list_get(samples, j)
|
||||
let v: Int = raw * 22000 / maxabs
|
||||
out = native_list_append(out, v)
|
||||
j = j + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// GA convenience wrapper (no accent) — keeps the base render path.
|
||||
fn synth_codes(codes: [String], voice: [String], pmap: [String]) -> [Int] {
|
||||
let noacc: [String] = native_list_empty()
|
||||
let novset: [String] = native_list_empty()
|
||||
let novmap: [String] = native_list_empty()
|
||||
let nopros: [Int] = native_list_empty()
|
||||
return synth_codes_accent(codes, voice, pmap, noacc, novset, novmap, nopros)
|
||||
}
|
||||
|
||||
// -- Voice-by-imitation: HEAR a PCM sample -> extract the voice signature -----
|
||||
// Pitch by autocorrelation; vocal-tract scale (kf) from the F1 formant peak of a
|
||||
// heard sustained vowel /AA/ (nominal F1 = 730 Hz) via an integer DFT. The
|
||||
// analyzer sees ONLY the PCM samples — never the source signature numbers — so
|
||||
// recovery is genuinely by ear.
|
||||
fn voice_f0(samples: [Int], sr: Int) -> Int {
|
||||
let n: Int = native_list_len(samples)
|
||||
let start: Int = n / 4
|
||||
let end: Int = n * 3 / 4
|
||||
// bound the analysis window so accumulators can never overflow on long input
|
||||
if end - start > 6000 {
|
||||
end = start + 6000
|
||||
}
|
||||
let minlag: Int = sr / 300
|
||||
let maxlag: Int = sr / 75
|
||||
let best: Int = 0
|
||||
let bestlag: Int = minlag
|
||||
let lag: Int = minlag
|
||||
while lag <= maxlag {
|
||||
let sum: Int = 0
|
||||
let i: Int = start
|
||||
while i < end {
|
||||
let ai: Int = native_list_get(samples, i)
|
||||
let bi: Int = native_list_get(samples, i + lag)
|
||||
sum = sum + ai * bi / 256
|
||||
i = i + 2
|
||||
}
|
||||
if sum > best {
|
||||
best = sum
|
||||
bestlag = lag
|
||||
}
|
||||
lag = lag + 1
|
||||
}
|
||||
if bestlag < 1 {
|
||||
bestlag = 1
|
||||
}
|
||||
return sr / bestlag
|
||||
}
|
||||
|
||||
fn voice_peak_in_band(samples: [Int], sr: Int, flo: Int, fhi: Int) -> Int {
|
||||
let n: Int = native_list_len(samples)
|
||||
let start: Int = n / 4
|
||||
let end: Int = n * 3 / 4
|
||||
// bound the DFT window: re/im are accumulated /4096, and re*re must stay in
|
||||
// int64 — cap terms so (window/2)*(peak_term) squared cannot overflow.
|
||||
if end - start > 3000 {
|
||||
end = start + 3000
|
||||
}
|
||||
let bestmag: Int = 0
|
||||
let bestf: Int = flo
|
||||
let f: Int = flo
|
||||
while f <= fhi {
|
||||
let re: Int = 0
|
||||
let im: Int = 0
|
||||
let i: Int = start
|
||||
while i < end {
|
||||
let x: Int = native_list_get(samples, i)
|
||||
let ph: Int = i * f * 65536 / sr
|
||||
ph = ph - (ph / 65536) * 65536
|
||||
let cq: Int = sp_cos(ph)
|
||||
let sq: Int = sp_sin(ph)
|
||||
re = re + x * cq / 4096
|
||||
im = im + x * sq / 4096
|
||||
i = i + 2
|
||||
}
|
||||
let mag: Int = re * re + im * im
|
||||
if mag > bestmag {
|
||||
bestmag = mag
|
||||
bestf = f
|
||||
}
|
||||
f = f + 25
|
||||
}
|
||||
return bestf
|
||||
}
|
||||
|
||||
// Analyze a heard sustained /AA/ -> a full voice signature (by ear).
|
||||
fn voice_analyze(samples: [Int], sr: Int) -> [String] {
|
||||
let f0: Int = voice_f0(samples, sr)
|
||||
let f1: Int = voice_peak_in_band(samples, sr, 450, 1150)
|
||||
let kf: Int = 1000 * f1 / 730
|
||||
let f0e: Int = f0 * 85 / 100
|
||||
return voice_new("imitated", f0, f0e, kf, 1000, 1000, 8)
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
// voice-ingest.el - The LIVE VOICE LOOP reshape + ingest-as-geometry.
|
||||
//
|
||||
// EL cannot read a binary WAV (fs_read NUL-truncates), so the thin-medium DSP
|
||||
// extractor is periph's `voiceprint` (autocorr F0 + LPC formants), equivalent to
|
||||
// our own voice_analyze. This module: (1) RESHAPE the voiceprint JSON (TEXT) into
|
||||
// the organ voice-signature schema; (2) INGEST it as a GEOMETRY manifold in the
|
||||
// engram and engram_save it to a file; (3) READ the target signature BACK from
|
||||
// that geometry (engram_load + scan + filter), never from the json or a table.
|
||||
// HONEST: this reaches for pitch + a coarse vocal-tract scale (kf). It is NOT a
|
||||
// clone — no glottal timbre, vowel-space, or articulation is captured.
|
||||
|
||||
fn parse_leading_int(s: String) -> Int {
|
||||
let n: Int = str_len(s)
|
||||
let i: Int = 0
|
||||
let v: Int = 0
|
||||
let started: Int = 0
|
||||
while i < n {
|
||||
let c: Int = str_char_code(s, i)
|
||||
if c >= 48 {
|
||||
if c <= 57 {
|
||||
v = v * 10 + (c - 48)
|
||||
started = 1
|
||||
i = i + 1
|
||||
} else {
|
||||
i = n
|
||||
}
|
||||
} else {
|
||||
if started == 1 {
|
||||
i = n
|
||||
} else {
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// voiceprint JSON -> organ voice-signature source file; returns [f0,f0_end,kf,f1,f2,f3].
|
||||
fn reshape_voiceprint(vppath: String, outjson: String) -> [Int] {
|
||||
let j: String = fs_read(vppath)
|
||||
let f0: Int = parse_uint_from(j, "f0_hz\":")
|
||||
let fp: Int = str_index_of(j, "formants_hz")
|
||||
let tail: String = str_slice(j, fp, fp + 120)
|
||||
let br: Int = str_index_of(tail, "[")
|
||||
let arr: String = str_slice(tail, br + 1, str_len(tail))
|
||||
let f1: Int = parse_leading_int(arr)
|
||||
let c1: Int = str_index_of(arr, ",")
|
||||
let a2: String = str_slice(arr, c1 + 1, str_len(arr))
|
||||
let f2: Int = parse_leading_int(a2)
|
||||
let c2: Int = str_index_of(a2, ",")
|
||||
let a3: String = str_slice(a2, c2 + 1, str_len(a2))
|
||||
let f3: Int = parse_leading_int(a3)
|
||||
let f0e: Int = f0 * 85 / 100
|
||||
// derive kf honestly: coarse vocal-tract scale from the formant pattern
|
||||
let t1: Int = 1000 * f1 / 500
|
||||
let t2: Int = 1000 * f2 / 1500
|
||||
let t3: Int = 1000 * f3 / 2500
|
||||
let kf: Int = (t1 + t2 + t3) / 3
|
||||
if kf < 800 {
|
||||
kf = 800
|
||||
}
|
||||
if kf > 1400 {
|
||||
kf = 1400
|
||||
}
|
||||
let js: String = "{\"dataset\":\"will-voice-signature\",\"primitive_type\":\"voice\",\"grounding\":\"measured\",\"provenance\":\"Will live 30s read 2026-08-15 (elp/data/live/will30_clean.wav, 27.0s) SUPERSEDES the coarse 10s sample; F0+formants via periph voiceprint (autocorr+LPC), averaged over his full vowel set. Still the 11-number average: no coarticulation/prosody. COARSE — pitch + vocal-tract scale, NOT a clone.\",\"records\":[{\"key\":\"will\",\"features\":{\"source\":\"live-mic\"},\"attributes\":{\"f0\":" + int_to_str(f0) + ",\"f0_end\":" + int_to_str(f0e) + ",\"kf\":" + int_to_str(kf) + ",\"f1\":" + int_to_str(f1) + ",\"f2\":" + int_to_str(f2) + ",\"f3\":" + int_to_str(f3) + "}}]}"
|
||||
let okw: Bool = fs_write(outjson, js)
|
||||
let r: [Int] = native_list_empty()
|
||||
let r = native_list_append(r, f0)
|
||||
let r = native_list_append(r, f0e)
|
||||
let r = native_list_append(r, kf)
|
||||
let r = native_list_append(r, f1)
|
||||
let r = native_list_append(r, f2)
|
||||
let r = native_list_append(r, f3)
|
||||
return r
|
||||
}
|
||||
|
||||
// Ingest the signature as a manifold (a set-hub + the will node + a member edge)
|
||||
// and engram_save it to a reloadable file. grounding:measured self-declared.
|
||||
fn ingest_voice(sig: [Int], savepath: String) -> Int {
|
||||
let f0: Int = native_list_get(sig, 0)
|
||||
let f0e: Int = native_list_get(sig, 1)
|
||||
let kf: Int = native_list_get(sig, 2)
|
||||
let f1: Int = native_list_get(sig, 3)
|
||||
let f2: Int = native_list_get(sig, 4)
|
||||
let f3: Int = native_list_get(sig, 5)
|
||||
let hub: String = engram_node("voice-signature-set will grounding=measured src=periph-voiceprint", "VoiceSet", 90)
|
||||
let cont: String = "voice will | f0=" + int_to_str(f0) + " f0_end=" + int_to_str(f0e) + " kf=" + int_to_str(kf) + " f1=" + int_to_str(f1) + " f2=" + int_to_str(f2) + " f3=" + int_to_str(f3) + " grounding=measured src=periph-voiceprint-30s supersedes=prior-voice-region prov=COARSE-pitch+tractscale-NOT-a-clone"
|
||||
let id: String = engram_node(cont, "Voice", 90)
|
||||
engram_connect(id, hub, 90, "member_of")
|
||||
let oks: Bool = engram_save(savepath)
|
||||
return 1
|
||||
}
|
||||
|
||||
// READ the target voice back FROM the ingested geometry (engram_load + scan +
|
||||
// client-filter for "voice will"). Returns [f0,f0_end,kf,f1,f2,f3] or empty.
|
||||
fn load_voice(savepath: String) -> [Int] {
|
||||
let ok: Bool = engram_load(savepath)
|
||||
let r: [Int] = native_list_empty()
|
||||
if ok == false {
|
||||
return r
|
||||
}
|
||||
let j: String = engram_scan_nodes_json(200, 0)
|
||||
let p: Int = str_index_of(j, "voice will ")
|
||||
if p < 0 {
|
||||
return r
|
||||
}
|
||||
let win: String = str_slice(j, p, p + 200)
|
||||
let r = native_list_append(r, parse_uint_from(win, "f0="))
|
||||
let r = native_list_append(r, parse_uint_from(win, "f0_end="))
|
||||
let r = native_list_append(r, parse_uint_from(win, "kf="))
|
||||
let r = native_list_append(r, parse_uint_from(win, "f1="))
|
||||
let r = native_list_append(r, parse_uint_from(win, "f2="))
|
||||
let r = native_list_append(r, parse_uint_from(win, "f3="))
|
||||
return r
|
||||
}
|
||||
|
||||
// ---- Vowel-space + prosody: ingest-as-geometry + read-back (no source layer) --
|
||||
// vowel target lookup from the ingested vowel-space manifold: sym -> [f1,f2,f3].
|
||||
fn vmap_get(vmap: [String], code: String) -> [Int] {
|
||||
let out: [Int] = native_list_empty()
|
||||
let id: String = sp_map_get(vmap, code)
|
||||
if str_eq(id, "") {
|
||||
return out
|
||||
}
|
||||
let f1: Int = parse_uint_from(id, "f1=")
|
||||
if f1 <= 0 {
|
||||
return out
|
||||
}
|
||||
let out = native_list_append(out, f1)
|
||||
let out = native_list_append(out, parse_uint_from(id, "f2="))
|
||||
let out = native_list_append(out, parse_uint_from(id, "f3="))
|
||||
return out
|
||||
}
|
||||
|
||||
// Ingest his measured vowel space + prosody as ONE manifold (VowelSpace hub +
|
||||
// per-vowel target nodes + a prosody node) and engram_save it. Fresh empty store
|
||||
// per run => set-replace, no duplicate.
|
||||
fn ingest_voicegeom(vpath: String, ppath: String, savepath: String) -> Int {
|
||||
let hub: String = engram_node("vowel-space-set will grounding=measured src=lpc-formant-track-30s", "VowelSpace", 90)
|
||||
let content: String = fs_read(vpath)
|
||||
let lines: [String] = str_split(content, "\n")
|
||||
let nl: Int = native_list_len(lines)
|
||||
let li: Int = 0
|
||||
while li < nl {
|
||||
let line: String = native_list_get(lines, li)
|
||||
let ok: Int = 1
|
||||
if str_len(line) < 5 {
|
||||
ok = 0
|
||||
}
|
||||
if ok == 1 {
|
||||
if str_char_code(line, 0) == 35 {
|
||||
ok = 0
|
||||
}
|
||||
}
|
||||
if ok == 1 {
|
||||
let f: [String] = str_split(line, "|")
|
||||
if native_list_len(f) >= 5 {
|
||||
let sym: String = native_list_get(f, 0)
|
||||
let cont: String = "vowel-target will " + sym + " | f1=" + native_list_get(f, 1) + " f2=" + native_list_get(f, 2) + " f3=" + native_list_get(f, 3) + " n=" + native_list_get(f, 4) + " grounding=measured src=lpc-formant-track-30s"
|
||||
let id: String = engram_node(cont, "VowelTarget", 90)
|
||||
engram_connect(id, hub, 90, "member_of")
|
||||
}
|
||||
}
|
||||
li = li + 1
|
||||
}
|
||||
let pc: String = fs_read(ppath)
|
||||
let plines: [String] = str_split(pc, "\n")
|
||||
let pnl: Int = native_list_len(plines)
|
||||
let pi: Int = 0
|
||||
while pi < pnl {
|
||||
let pl: String = native_list_get(plines, pi)
|
||||
let ok2: Int = 1
|
||||
if str_len(pl) < 5 {
|
||||
ok2 = 0
|
||||
}
|
||||
if ok2 == 1 {
|
||||
if str_char_code(pl, 0) == 35 {
|
||||
ok2 = 0
|
||||
}
|
||||
}
|
||||
if ok2 == 1 {
|
||||
let pf: [String] = str_split(pl, "|")
|
||||
if native_list_len(pf) >= 4 {
|
||||
let pcont: String = "prosody will | f0_median=" + native_list_get(pf, 0) + " f0_min=" + native_list_get(pf, 1) + " f0_max=" + native_list_get(pf, 2) + " declination=" + native_list_get(pf, 3) + " src=f0-contour-30s"
|
||||
let pid: String = engram_node(pcont, "Prosody", 90)
|
||||
engram_connect(pid, hub, 90, "prosody_of")
|
||||
}
|
||||
}
|
||||
pi = pi + 1
|
||||
}
|
||||
let oks: Bool = engram_save(savepath)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Read the vowel-space back from geometry; prosody folded under key __PROSODY__.
|
||||
fn load_voicegeom(savepath: String) -> [String] {
|
||||
let m: [String] = native_list_empty()
|
||||
let ok: Bool = engram_load(savepath)
|
||||
if ok == false {
|
||||
return m
|
||||
}
|
||||
let j: String = engram_scan_nodes_json(400, 0)
|
||||
let jl: Int = str_len(j)
|
||||
let off: Int = 0
|
||||
while off < jl {
|
||||
let rest: String = str_slice(j, off, jl)
|
||||
let p: Int = str_index_of(rest, "vowel-target will ")
|
||||
if p < 0 {
|
||||
off = jl
|
||||
} else {
|
||||
let abs: Int = off + p
|
||||
let win: String = str_slice(j, abs, abs + 140)
|
||||
let after: String = str_slice(win, 18, str_len(win))
|
||||
let sp: Int = str_index_of(after, " ")
|
||||
if sp > 0 {
|
||||
let sym: String = str_slice(after, 0, sp)
|
||||
m = native_list_append(m, sym)
|
||||
m = native_list_append(m, win)
|
||||
}
|
||||
off = abs + 18
|
||||
}
|
||||
}
|
||||
let pp: Int = str_index_of(j, "prosody will ")
|
||||
if pp >= 0 {
|
||||
let pwin: String = str_slice(j, pp, pp + 160)
|
||||
m = native_list_append(m, "__PROSODY__")
|
||||
m = native_list_append(m, pwin)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Prosody stats [f0_median, f0_min, f0_max, declination] read from geometry.
|
||||
fn prosody_from(vmap: [String]) -> [Int] {
|
||||
let out: [Int] = native_list_empty()
|
||||
let id: String = sp_map_get(vmap, "__PROSODY__")
|
||||
if str_eq(id, "") {
|
||||
return out
|
||||
}
|
||||
let out = native_list_append(out, parse_uint_from(id, "f0_median="))
|
||||
let out = native_list_append(out, parse_uint_from(id, "f0_min="))
|
||||
let out = native_list_append(out, parse_uint_from(id, "f0_max="))
|
||||
let out = native_list_append(out, parse_uint_from(id, "declination="))
|
||||
return out
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// voice-profile.el - The VOICE signature as a pluggable PROFILE.
|
||||
//
|
||||
// Exact mirror of surface-profile.el / language-profile.el: a voice is a
|
||||
// [String] slot-map read via voice_get, the SAME mechanism the realizer uses
|
||||
// for language and surface. Where an instrument signature (a few dozen numbers)
|
||||
// is the timbre of a musical tone, a VOICE signature is the timbre of the vocal
|
||||
// tract — the instrument that renders LANGUAGE-meaning as SPEECH on the audio
|
||||
// surface. Physics (source-filter), not a recorded corpus.
|
||||
//
|
||||
// The signature is a few numbers, all integer (EL float arithmetic is unusable):
|
||||
// name - label
|
||||
// f0 - base pitch, Hz (glottal source rate at utterance start)
|
||||
// f0_end - pitch at utterance end (declination -> falling = declarative)
|
||||
// kf - formant scale in PER-MILLE (1000 = x1.0). Encodes vocal-tract
|
||||
// length: shorter tract (child/female) -> higher kf. Scales every
|
||||
// phoneme's nominal formant: F_actual = F_nominal * kf / 1000.
|
||||
// dur - speaking-rate multiplier in per-mille (1000 = nominal; >1000 slower)
|
||||
// tilt - source spectral tilt (per-mille; higher = darker/steeper rolloff)
|
||||
// breath - breathiness 0..100 (aspiration mixed into the source)
|
||||
//
|
||||
// A voice is grabbed BY EAR (voice_analyze in speech.el extracts these numbers
|
||||
// from a short PCM sample — an impression, not 10h of training), or declared.
|
||||
|
||||
fn voice_new(name: String, f0: Int, f0_end: Int, kf: Int, dur: Int, tilt: Int, breath: Int) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "name")
|
||||
let r = native_list_append(r, name)
|
||||
let r = native_list_append(r, "f0")
|
||||
let r = native_list_append(r, int_to_str(f0))
|
||||
let r = native_list_append(r, "f0_end")
|
||||
let r = native_list_append(r, int_to_str(f0_end))
|
||||
let r = native_list_append(r, "kf")
|
||||
let r = native_list_append(r, int_to_str(kf))
|
||||
let r = native_list_append(r, "dur")
|
||||
let r = native_list_append(r, int_to_str(dur))
|
||||
let r = native_list_append(r, "tilt")
|
||||
let r = native_list_append(r, int_to_str(tilt))
|
||||
let r = native_list_append(r, "breath")
|
||||
let r = native_list_append(r, int_to_str(breath))
|
||||
return r
|
||||
}
|
||||
|
||||
// Accessor — identical convention to surface_get / lang_get.
|
||||
fn voice_get(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn voice_get_int(profile: [String], key: String) -> Int {
|
||||
let s: String = voice_get(profile, key)
|
||||
if str_eq(s, "") {
|
||||
return 0
|
||||
}
|
||||
return str_to_int(s)
|
||||
}
|
||||
|
||||
// -- Built-in voices ---------------------------------------------------------
|
||||
|
||||
// Neuron's own voice: calm, precise, androgynous-neutral. Low-ish base pitch,
|
||||
// gentle declination, near-neutral vocal-tract length.
|
||||
fn voice_neuron() -> [String] {
|
||||
return voice_new("neuron", 112, 96, 1020, 1000, 1000, 6)
|
||||
}
|
||||
|
||||
// Will's voice signature, built from the INGESTED geometry (f0/f0_end/kf read
|
||||
// back from the will-voice manifold — passed in, never hardcoded). Composable
|
||||
// with an accent transform exactly like voice_neuron() (voice (+) accent).
|
||||
fn voice_will(f0: Int, f0_end: Int, kf: Int) -> [String] {
|
||||
return voice_new("will", f0, f0_end, kf, 1000, 1000, 6)
|
||||
}
|
||||
|
||||
// A deliberately DISTINCT target voice for the imitation proof: higher pitch,
|
||||
// shorter vocal tract (kf=1.20) -> a clearly different speaker. Neuron will
|
||||
// HEAR a sample of this voice and reconstruct these numbers by ear.
|
||||
fn voice_target_a() -> [String] {
|
||||
return voice_new("target_a", 178, 150, 1200, 950, 1000, 10)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// speech-accent-demo.el - PROOF: Neuron speaks with a BRITISH accent, where the
|
||||
// accent is a TRANSFORM composed onto the voice (voice (+) accent, separable),
|
||||
// INGESTED as geometry (not a table). Same voice, accent toggled on/off = RP/GA.
|
||||
|
||||
fn main() {
|
||||
let outdir: String = "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-acc02900ef4ade35e/elp/tests/examples/out/"
|
||||
|
||||
// LEARN: base phonetics + lexicon + the British-RP accent transform, all as
|
||||
// ingested geometry (source -> manifold -> engram).
|
||||
let pmap: [String] = ingest_phonetics("elp/data/phonetics.psv")
|
||||
let lmap: [String] = ingest_lexicon("elp/data/lexicon.psv")
|
||||
let amap: [String] = ingest_accent("elp/data/british-accent.psv")
|
||||
println("[learn] phonemes=" + int_to_str(native_list_len(pmap) / 2) + " words=" + int_to_str(native_list_len(lmap) / 2) + " accent_targets=" + int_to_str(native_list_len(amap) / 2))
|
||||
|
||||
let neuron: [String] = voice_neuron()
|
||||
let noaccent: [String] = native_list_empty()
|
||||
|
||||
// -- Sentence 1: "I am Neuron." from meaning ----------------------------
|
||||
let fr1: [String] = sem_frame("describe", "I", "Neuron", "")
|
||||
let t1: String = sem_realize(fr1)
|
||||
let c1: [String] = text_phonemes(lmap, t1)
|
||||
println("[s1] " + t1 + " :: " + list_join(c1, " "))
|
||||
|
||||
// separability: SAME voice, accent OFF (GA) vs ON (RP)
|
||||
let ga: [Int] = synth_codes_accent(c1, neuron, pmap, noaccent)
|
||||
let okga: Bool = write_wav(ga, 16000, outdir + "ga-neuron.wav")
|
||||
let br1: [Int] = synth_codes_accent(c1, neuron, pmap, amap)
|
||||
let okb1: Bool = write_wav(br1, 16000, outdir + "british-neuron.wav")
|
||||
|
||||
// -- Sentence 2: showcases NON-RHOTICITY --------------------------------
|
||||
let fr2: [String] = sem_frame("describe", "I", "here", "")
|
||||
let t2: String = sem_realize(fr2)
|
||||
let c2: [String] = text_phonemes(lmap, t2)
|
||||
let c2rp: [String] = apply_rhoticity(c2, pmap)
|
||||
println("[s2] " + t2 + " :: GA=" + list_join(c2, " ") + " RP=" + list_join(c2rp, " "))
|
||||
let br2: [Int] = synth_codes_accent(c2, neuron, pmap, amap)
|
||||
let okb2: Bool = write_wav(br2, 16000, outdir + "british-2.wav")
|
||||
|
||||
// show an RP override read straight from the accent geometry
|
||||
let ovAA: [Int] = accent_formants(amap, "AA")
|
||||
if native_list_len(ovAA) >= 3 {
|
||||
println("[accent-geometry] AA(LOT) RP f1=" + int_to_str(native_list_get(ovAA, 0)) + " f2=" + int_to_str(native_list_get(ovAA, 1)) + " (base GA 730/1090) [PROVISIONAL]")
|
||||
}
|
||||
println("[done] ga-neuron=" + bool_to_str(okga) + " british-neuron=" + bool_to_str(okb1) + " british-2=" + bool_to_str(okb2))
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// speech-demo.el - PROOF: Neuron speaks from MEANING, rendered through INGESTED
|
||||
// phonetic geometry, own-core, plus voice-by-IMITATION. Built by concatenating
|
||||
// the elp realizer + voice-profile + speech-ingest + speech, then this main.
|
||||
//
|
||||
// LEARN : ingest acoustic-phonetics + lexicon SOURCES -> phoneme manifold in
|
||||
// the engram (source -> manifold -> merge).
|
||||
// MEANING : sem_frame("describe","I","Neuron","") -> sem_realize -> "I am Neuron."
|
||||
// PHONES : words -> phoneme codes, READ from the ingested lexicon geometry.
|
||||
// RENDER : superpose formant resonances (read from engram) over a glottal
|
||||
// source -> own-core PCM/WAV, in Neuron's own voice.
|
||||
// IMITATE : HEAR a short sample of a different voice -> extract its signature
|
||||
// by ear (autocorrelation pitch + integer-DFT formant) -> render new
|
||||
// speech in that voice. An impression, not a corpus.
|
||||
|
||||
fn speak_report(tag: String, codes: [String], voice: [String], pmap: [String], path: String) -> [Int] {
|
||||
let s: [Int] = synth_codes(codes, voice, pmap)
|
||||
let ok: Bool = write_wav(s, 16000, path)
|
||||
println(tag + " samples=" + int_to_str(native_list_len(s)) + " ok=" + bool_to_str(ok) + " -> " + path)
|
||||
return s
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let outdir: String = "/private/tmp/claude-501/-Users-will/6531446d-bc27-4095-930b-e04777c3db4f/scratchpad/"
|
||||
|
||||
// -- LEARN: ingest the speech primitives as geometry --------------------
|
||||
let pmap: [String] = ingest_phonetics("elp/data/phonetics.psv")
|
||||
let lmap: [String] = ingest_lexicon("elp/data/lexicon.psv")
|
||||
let saved: Bool = engram_save(outdir + "phoneme-manifold.json")
|
||||
println("[learn] phonemes=" + int_to_str(native_list_len(pmap) / 2) + " words=" + int_to_str(native_list_len(lmap) / 2) + " manifold_saved=" + bool_to_str(saved))
|
||||
|
||||
// sanity: show that AA's formants came from ingested geometry, not code
|
||||
let aa: [Int] = phon_geo(pmap, "AA")
|
||||
let aaF1: Int = native_list_get(aa, 0)
|
||||
let aaF2: Int = native_list_get(aa, 1)
|
||||
println("[read-geometry] AA F1=" + int_to_str(aaF1) + " F2=" + int_to_str(aaF2) + " (parsed from engram node)")
|
||||
|
||||
// -- MEANING -> WORDS via the realizer's language faculty ----------------
|
||||
let frame: [String] = sem_frame("describe", "I", "Neuron", "")
|
||||
let text: String = sem_realize(frame)
|
||||
println("[meaning->text] " + text)
|
||||
|
||||
// -- WORDS -> PHONEMES (read from ingested lexicon geometry) --------------
|
||||
let codes: [String] = text_phonemes(lmap, text)
|
||||
println("[phonemes] " + list_join(codes, " "))
|
||||
|
||||
// -- RENDER in Neuron's own voice ----------------------------------------
|
||||
let neuron: [String] = voice_neuron()
|
||||
let s1: [Int] = speak_report("[speak neuron]", codes, neuron, pmap, outdir + "neuron.wav")
|
||||
|
||||
// -- IMITATION: hear a distinct voice, recover its signature, re-render ---
|
||||
let vA: [String] = voice_target_a()
|
||||
let hcodes: [String] = native_list_empty()
|
||||
hcodes = native_list_append(hcodes, "SIL")
|
||||
let z: Int = 0
|
||||
while z < 6 {
|
||||
hcodes = native_list_append(hcodes, "AA")
|
||||
z = z + 1
|
||||
}
|
||||
hcodes = native_list_append(hcodes, "SIL")
|
||||
let heard: [Int] = synth_codes(hcodes, vA, pmap)
|
||||
let okh: Bool = write_wav(heard, 16000, outdir + "heard.wav")
|
||||
|
||||
let vB: [String] = voice_analyze(heard, 16000)
|
||||
println("[imitate] heard ACTUAL f0=" + voice_get(vA, "f0") + " kf=" + voice_get(vA, "kf"))
|
||||
println("[imitate] heard RECOVERED f0=" + voice_get(vB, "f0") + " kf=" + voice_get(vB, "kf") + " (extracted by ear from PCM)")
|
||||
let s2: [Int] = speak_report("[speak imitation]", codes, vB, pmap, outdir + "imitation.wav")
|
||||
|
||||
println("[done] rendered from meaning + ingested geometry; imitation from a heard sample.")
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// speech-organ-demo.el - PROOF: the render now reads its phoneme + accent
|
||||
// GEOMETRY from the ingest ORGAN's saved engram files (engram_load +
|
||||
// engram_scan_nodes_json + cache), not a same-run hand-load. The British accent
|
||||
// is still a composed transform-geometry (voice (+) accent, separable). Numbers
|
||||
// come from the organ manifold; the .psv supplies only categorical vowel-class.
|
||||
|
||||
fn main() {
|
||||
let outdir: String = "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-acc02900ef4ade35e/elp/tests/examples/out/"
|
||||
|
||||
// engram-independent caches from source (survive engram_load replacement)
|
||||
let vset: [String] = organ_vset("elp/data/phonetics.psv")
|
||||
let lmap: [String] = organ_lex("elp/data/lexicon.psv")
|
||||
// ORGAN read: phonetics FIRST (cache), THEN accent (engram_load replaces store)
|
||||
let pmap: [String] = organ_pmap("elp/data/phonetics-formants.engram.json")
|
||||
let amap: [String] = organ_amap("elp/data/british-accent.engram.json")
|
||||
println("[organ] phon_syms=" + int_to_str(native_list_len(pmap) / 2) + " accent_syms=" + int_to_str(native_list_len(amap) / 2) + " vowels=" + int_to_str(native_list_len(vset)) + " words=" + int_to_str(native_list_len(lmap) / 2))
|
||||
|
||||
// prove the numbers came from the organ node content
|
||||
let g: [Int] = phon_geo(pmap, "AA")
|
||||
println("[organ-read] phoneme AA f1=" + int_to_str(native_list_get(g, 0)) + " f2=" + int_to_str(native_list_get(g, 1)) + " f3=" + int_to_str(native_list_get(g, 2)) + " (P&B1952 MEASURED)")
|
||||
let ov: [Int] = accent_formants(amap, "AA")
|
||||
if native_list_len(ov) >= 3 {
|
||||
println("[organ-read] accent AA(LOT) f1=" + int_to_str(native_list_get(ov, 0)) + " f2=" + int_to_str(native_list_get(ov, 1)) + " (DERIVED RP, PROVISIONAL)")
|
||||
}
|
||||
println("[organ-read] non_rhotic=" + int_to_str(is_nonrhotic(amap)))
|
||||
|
||||
let neuron: [String] = voice_neuron()
|
||||
let noacc: [String] = native_list_empty()
|
||||
|
||||
// Sentence 1: "I am Neuron." from meaning; GA vs RP = separable toggle
|
||||
let t1: String = sem_realize(sem_frame("describe", "I", "Neuron", ""))
|
||||
let c1: [String] = text_phonemes(lmap, t1)
|
||||
println("[s1] " + t1 + " :: " + list_join(c1, " "))
|
||||
let ga: [Int] = synth_codes_accent(c1, neuron, pmap, noacc, vset)
|
||||
let okga: Bool = write_wav(ga, 16000, outdir + "ga-neuron-organ.wav")
|
||||
let br1: [Int] = synth_codes_accent(c1, neuron, pmap, amap, vset)
|
||||
let okb1: Bool = write_wav(br1, 16000, outdir + "british-neuron-organ.wav")
|
||||
|
||||
// Sentence 2: non-rhoticity showcase
|
||||
let t2: String = sem_realize(sem_frame("describe", "I", "here", ""))
|
||||
let c2: [String] = text_phonemes(lmap, t2)
|
||||
let c2rp: [String] = apply_rhoticity(c2, vset)
|
||||
println("[s2] " + t2 + " :: GA=" + list_join(c2, " ") + " RP=" + list_join(c2rp, " "))
|
||||
let br2: [Int] = synth_codes_accent(c2, neuron, pmap, amap, vset)
|
||||
let okb2: Bool = write_wav(br2, 16000, outdir + "british-2-organ.wav")
|
||||
|
||||
println("[done] ga-organ=" + bool_to_str(okga) + " british-organ=" + bool_to_str(okb1) + " british-2-organ=" + bool_to_str(okb2))
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// speech-voice-demo.el - LIVE VOICE LOOP (stand-in test). Capture -> voiceprint
|
||||
// -> reshape -> INGEST AS GEOMETRY -> read the target back FROM geometry -> the
|
||||
// EL projector renders a line reaching for that voice. Stand-in "Will" = the
|
||||
// voiceprint of imitation.wav. HONEST: pitch + coarse vocal-tract scale, NOT a clone.
|
||||
|
||||
fn main() {
|
||||
let outdir: String = "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-acc02900ef4ade35e/elp/tests/examples/out/"
|
||||
let vp: String = "/private/tmp/claude-501/-Users-will/6531446d-bc27-4095-930b-e04777c3db4f/scratchpad/will-voiceprint.json"
|
||||
|
||||
// 1+2: reshape voiceprint JSON -> organ voice-signature source
|
||||
let sig0: [String] = native_list_empty()
|
||||
let sig: [Int] = reshape_voiceprint(vp, "elp/data/will-voice.json")
|
||||
// 3: ingest as geometry + engram_save a reloadable manifold file
|
||||
let ig: Int = ingest_voice(sig, "elp/data/will-voice.engram.json")
|
||||
// 4: READ the target back FROM geometry (engram_load + scan + filter)
|
||||
let g: [Int] = load_voice("elp/data/will-voice.engram.json")
|
||||
println("[voice-geometry] read from manifold: f0=" + int_to_str(native_list_get(g, 0)) + " f0_end=" + int_to_str(native_list_get(g, 1)) + " kf=" + int_to_str(native_list_get(g, 2)) + " f1=" + int_to_str(native_list_get(g, 3)) + " f2=" + int_to_str(native_list_get(g, 4)) + " f3=" + int_to_str(native_list_get(g, 5)) + " (measured, COARSE — not a clone)")
|
||||
|
||||
// phoneme geometry from the organ (loaded AFTER the voice sig is cached in EL)
|
||||
let pmap: [String] = organ_pmap("elp/data/phonetics-formants.engram.json")
|
||||
let lmap: [String] = organ_lex("elp/data/lexicon.psv")
|
||||
|
||||
// 5: render a line FROM MEANING in Will's voice
|
||||
let vw: [String] = voice_will(native_list_get(g, 0), native_list_get(g, 1), native_list_get(g, 2))
|
||||
let t: String = sem_realize(sem_frame("greet", "Will", "", ""))
|
||||
let codes: [String] = text_phonemes(lmap, t)
|
||||
println("[render] \"" + t + "\" :: " + list_join(codes, " ") + " in voice=will f0=" + int_to_str(voice_get_int(vw, "f0")) + " kf=" + int_to_str(voice_get_int(vw, "kf")))
|
||||
let samples: [Int] = synth_codes(codes, vw, pmap)
|
||||
let ok: Bool = write_wav(samples, 16000, outdir + "will-reply.wav")
|
||||
println("[done] will-reply.wav=" + bool_to_str(ok))
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// speech-voice-demo2.el - LIVE VOICE LOOP on Will's richer 30s read, with a
|
||||
// GEOMETRIC SET-REPLACE of the voice_will manifold (supersede the coarse 10s
|
||||
// region, insert the 30s region — no duplicate node, no per-node CRUD; Will's
|
||||
// standing rule f999c5ff). HONEST: 30s steadies the 11-number average over more
|
||||
// of his vowels, but it is still one formant triple with no coarticulation or
|
||||
// prosody — closer but still synthetic, not a clone.
|
||||
|
||||
fn main() {
|
||||
let outdir: String = "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-acc02900ef4ade35e/elp/tests/examples/out/"
|
||||
let vp: String = "/private/tmp/claude-501/-Users-will/6531446d-bc27-4095-930b-e04777c3db4f/scratchpad/will30-voiceprint.json"
|
||||
let manifest: String = "elp/data/will-voice.engram.json"
|
||||
|
||||
// --- SET-REPLACE step 1: read the PRIOR region (text read of the manifold
|
||||
// file — no engram_load, so the store stays clean) and report what is
|
||||
// being superseded. ---
|
||||
let prior: String = fs_read(manifest)
|
||||
let pp: Int = str_index_of(prior, "voice will ")
|
||||
if pp >= 0 {
|
||||
let pw: String = str_slice(prior, pp, pp + 200)
|
||||
println("[set-replace] superseding PRIOR voice region: f0=" + int_to_str(parse_uint_from(pw, "f0=")) + " kf=" + int_to_str(parse_uint_from(pw, "kf=")) + " f1=" + int_to_str(parse_uint_from(pw, "f1=")))
|
||||
}
|
||||
|
||||
// --- step 2: reshape the 30s voiceprint -> organ voice-signature source ---
|
||||
let sig: [Int] = reshape_voiceprint(vp, "elp/data/will-voice.json")
|
||||
|
||||
// --- step 3: INSERT the fresh 30s region into an EMPTY engram and save ->
|
||||
// wholesale replaces the manifold file (old region dropped, not edited,
|
||||
// not duplicated). This is the geometric set-replace. ---
|
||||
let ig: Int = ingest_voice(sig, manifest)
|
||||
|
||||
// --- step 4: READ the new target BACK from geometry ---
|
||||
let g: [Int] = load_voice(manifest)
|
||||
println("[voice-geometry] new region read from manifold: f0=" + int_to_str(native_list_get(g, 0)) + " f0_end=" + int_to_str(native_list_get(g, 1)) + " kf=" + int_to_str(native_list_get(g, 2)) + " f1=" + int_to_str(native_list_get(g, 3)) + " f2=" + int_to_str(native_list_get(g, 4)) + " f3=" + int_to_str(native_list_get(g, 5)) + " (measured 30s, COARSE — not a clone)")
|
||||
|
||||
// phoneme + lexicon geometry from the organ (loaded after the voice sig is
|
||||
// cached in EL, since engram_load replaces the store)
|
||||
let pmap: [String] = organ_pmap("elp/data/phonetics-formants.engram.json")
|
||||
let lmap: [String] = organ_lex("elp/data/lexicon.psv")
|
||||
|
||||
// --- step 5: render a fresh reply FROM MEANING in the 30s Will voice ---
|
||||
let vw: [String] = voice_will(native_list_get(g, 0), native_list_get(g, 1), native_list_get(g, 2))
|
||||
let t: String = sem_realize(sem_frame("greet", "Will", "", ""))
|
||||
let codes: [String] = text_phonemes(lmap, t)
|
||||
println("[render] \"" + t + "\" :: " + list_join(codes, " ") + " in voice=will f0=" + int_to_str(voice_get_int(vw, "f0")) + " kf=" + int_to_str(voice_get_int(vw, "kf")))
|
||||
let samples: [Int] = synth_codes(codes, vw, pmap)
|
||||
let ok: Bool = write_wav(samples, 16000, outdir + "will-reply2.wav")
|
||||
println("[done] will-reply2.wav=" + bool_to_str(ok))
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// speech-voicegeom-demo.el - THE JUMP: render Will's VOWEL SPACE + PROSODY
|
||||
// (measured over 30s), not the single 11-number average. His vowels land at HIS
|
||||
// targets; pitch follows HIS melody. All read back FROM the ingested geometry.
|
||||
// INTERIM: the geometry was Python-measured (measure_voice.py, numpy LPC/F0) —
|
||||
// to be superseded by the engram-measures-audio path. No source layer.
|
||||
|
||||
fn main() {
|
||||
let outdir: String = "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-acc02900ef4ade35e/elp/tests/examples/out/"
|
||||
|
||||
// 1: ingest vowel space + prosody as geometry (empty store -> save; set-replace)
|
||||
let ig: Int = ingest_voicegeom("elp/data/will-vowelspace.psv", "elp/data/will-prosody.psv", "elp/data/will-voicegeom.engram.json")
|
||||
// kf (vocal-tract scale for consonants) from the earlier will-voice manifold
|
||||
let sigv: [Int] = load_voice("elp/data/will-voice.engram.json")
|
||||
let kf: Int = native_list_get(sigv, 2)
|
||||
// 2: read vowel space + prosody back FROM geometry
|
||||
let vmap: [String] = load_voicegeom("elp/data/will-voicegeom.engram.json")
|
||||
let pros: [Int] = prosody_from(vmap)
|
||||
println("[geometry] vowels=" + int_to_str((native_list_len(vmap) - 2) / 2) + " prosody f0_median=" + int_to_str(native_list_get(pros, 0)) + " f0_min=" + int_to_str(native_list_get(pros, 1)) + " f0_max=" + int_to_str(native_list_get(pros, 2)) + " kf=" + int_to_str(kf))
|
||||
let ehv: [Int] = vmap_get(vmap, "EH")
|
||||
let ihv: [Int] = vmap_get(vmap, "IH")
|
||||
println("[his-vowels] EH=" + int_to_str(native_list_get(ehv, 0)) + "/" + int_to_str(native_list_get(ehv, 1)) + " IH=" + int_to_str(native_list_get(ihv, 0)) + "/" + int_to_str(native_list_get(ihv, 1)))
|
||||
|
||||
// phoneme geometry from the organ (loaded AFTER caches are in EL)
|
||||
let pmap: [String] = organ_pmap("elp/data/phonetics-formants.engram.json")
|
||||
let lmap: [String] = organ_lex("elp/data/lexicon.psv")
|
||||
|
||||
// 3+4: render FROM MEANING in his-vowels + his-prosody voice
|
||||
let vw: [String] = voice_will(native_list_get(pros, 0), native_list_get(pros, 1), kf)
|
||||
let noacc: [String] = native_list_empty()
|
||||
let novset: [String] = native_list_empty()
|
||||
let t: String = sem_realize(sem_frame("greet", "Will", "", ""))
|
||||
let codes: [String] = text_phonemes(lmap, t)
|
||||
println("[render] \"" + t + "\" :: " + list_join(codes, " "))
|
||||
let samples: [Int] = synth_codes_accent(codes, vw, pmap, noacc, novset, vmap, pros)
|
||||
let ok: Bool = write_wav(samples, 16000, outdir + "will-reply3.wav")
|
||||
println("[done] will-reply3.wav=" + bool_to_str(ok))
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
# Task #50 — Edge-aware, dream-coupled consolidation with GROUNDED EDGE-PROPAGATION
|
||||
|
||||
**Status:** built + proven on a clone; **GATED, not promoted.** The main loop
|
||||
sequences live promotion after the engine/HNSW cutover settles.
|
||||
**Date:** 2026-08-15 · **Worktree:** `agent-a6577c8211c332c5b` (isolated).
|
||||
|
||||
Grounding mechanism designed with Will (memory `9e09a59f`, refining
|
||||
`1a861007`). This is the HOW for #50.
|
||||
|
||||
---
|
||||
|
||||
## (a) How grounded edge-propagation integrates into the dream/consolidation cycle
|
||||
|
||||
The beat already exists. `neuron/awareness.el` runs a heartbeat (~every
|
||||
`beat_ms`); each beat calls `hebb_consolidate()` — which drains the self-formed
|
||||
Hebbian associations out of the fast in-process store and writes them, over the
|
||||
threshold `ENGRAM_HEBB_LINK_MIN`, into the durable engram (`:8742`) — and then
|
||||
`emit_heartbeat()`.
|
||||
|
||||
Grounded edge-propagation slots into the **same beat, immediately after
|
||||
consolidation** (awareness.el line 1286–1288):
|
||||
|
||||
```
|
||||
hebb_consolidate() // lay down the tethers (edges) that cleared threshold
|
||||
ground_propagate() // <-- NEW: grade beliefs ALONG those tethers
|
||||
emit_heartbeat() // report gep_* gauges beside hebb_*
|
||||
```
|
||||
|
||||
This ordering is the point. Consolidation lays down the wiring; propagation
|
||||
grades the beliefs along it, in the same breath. Memory `69b8babe`:
|
||||
memory-consolidation and staying-yourself are one physics — forming a memory and
|
||||
grading a belief are the same gravity run in two passes of one beat.
|
||||
|
||||
The propagation runs **inside the engram** as the native
|
||||
`engram_ground_propagate()` over the durable flat node/edge arrays (the store
|
||||
the consolidated edges just landed in). The soul invokes it over HTTP
|
||||
(`POST /api/ground/propagate`) and folds the returned `gep_*` telemetry into the
|
||||
heartbeat stream next to `hebb_cands / hebb_mass / hebb_edges`.
|
||||
|
||||
**Bounded by construction** (per the live-graph reality — 70.7% of nodes
|
||||
isolated, connected core ~28%, hub first-hop fan-out in the thousands):
|
||||
- **1-hop only.** No BFS spreading activation — a belief is graded from its
|
||||
DIRECT grounded neighbors, so there is no per-hop breadth explosion.
|
||||
- **Beam-capped** at `GEP_MAX_CORR = 256` corroborators per belief.
|
||||
- **Salience-ordered, `GEP_BELIEFS_PER_BEAT = 512`** beliefs per beat; the rest
|
||||
next beat. Work per beat is O(beliefs × degree), hard-bounded.
|
||||
- **Isolated / starved beliefs** are counted and surfaced (`gep_isolated`,
|
||||
`gep_starved`) as an interoceptive sparse-region signal for the
|
||||
edge-formation / embedding pass (#20). #50 CONSUMES edges; it does not form
|
||||
them. A belief with no grounded neighbor has nothing to tether to — correct
|
||||
per the anti-delusion gravity law (`0b15017c`), not a gap.
|
||||
|
||||
---
|
||||
|
||||
## (b) The implementation
|
||||
|
||||
Represented faithfully to the spec — **grounding is a Hebbian-weighted
|
||||
collection over time, never a scalar.**
|
||||
|
||||
- **Grounding = an append-only event ring** on the node (`GepGrounding`),
|
||||
structurally parallel to the ACT-R base-level access ring already in
|
||||
`EngramNode` (`access_ts[K]`). Each event is `{ts, sign±, mag, corroborator
|
||||
signature}`. Append-only, supersede-not-delete; events aged out of the ring
|
||||
are counted (`older_count`), never faked away.
|
||||
- **Standing is DERIVED, recency-weighted, never stored** —
|
||||
`standing = clamp(GEP_BASE + Σ_events sign·mag·age^(-D), 0, 1)`, exactly the
|
||||
ACT-R base-level shape `ln Σ t^-d` (`ENGRAM_BLL_D = 0.5`) but sign-carrying so
|
||||
LTD subtracts. Memory `1a861007`: the collection is primary, the standing is
|
||||
its emergent aggregate. Mirrored onto `confidence` each beat so downstream
|
||||
reads (verifier #43, realizer calibration `0041d917`) never speak above the
|
||||
grounding.
|
||||
- **Update = LTP/LTD with a threshold.** Per belief, gather corroborators along
|
||||
incident edges, weighted by `edge.weight` (the Hebbian weight) × the
|
||||
neighbor's own standing. **Anti-delusion gravity:** only neighbors already
|
||||
`≥ GEP_LIKELY_MIN` may corroborate — grounding flows FROM the grounded core.
|
||||
- **Convergent INDEPENDENT corroboration** is the driver. Independence is
|
||||
enforced by **union-find over the corroborator set**: two corroborators are
|
||||
the same independent source if they are the same node, reached by multiple
|
||||
edges, or linked to each other (an echo chain / shared derivation). Support is
|
||||
summed **per independent component** (max-magnitude member), and the threshold
|
||||
gate requires BOTH a mass floor (`pos ≥ GEP_THETA`) AND an independence-count
|
||||
floor (`n_independent ≥ GEP_N_MIN`). The count gate is the guard against one
|
||||
node echoed N times.
|
||||
- **Sub-threshold is transient.** Support present but below threshold →
|
||||
`subthreshold_hits++`, no durable event, no lasting shift (Will's exact spec).
|
||||
- **Graduation / decay.** Cross up → LTP event appended → standing climbs
|
||||
`conjecture → likely → grounded`. Contradiction past threshold → LTD →
|
||||
`grounded → likely → conjecture`. Nothing latches; withdraw support and the
|
||||
collection ages and relaxes (`271f1163`, nothing is settled).
|
||||
|
||||
### Files
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `gep_core.h` | The mechanism. Pure C, libm only (own-the-core). Single source of truth: `GepGrounding`, `gep_standing`, `gep_append`, union-find independence, `gep_propagate_node`, `gep_beat`. |
|
||||
| `gep_proof.c` | Self-contained proof harness — builds the three scenarios, prints raw before/after. |
|
||||
| `engram_ground_propagate.staged.c` | GATED runtime native. Wires the SAME `gep_core.h` primitives to the live `EngramStore` (adj cache, flat arrays). Splice plan + relation→polarity + belief gate. Compiles only when spliced (verified: every runtime symbol it references — `engram_adj_rebuild`, `adj_from_len`, `engram_find_node_index`, `ENGRAM_LAYER_SAFETY`, `istr_contains`, … — exists in the release runtime). |
|
||||
| `awareness.beat.patch.el` | GATED beat hook — `ground_propagate()` + the insert between `hebb_consolidate()` and `emit_heartbeat()`. |
|
||||
| `server.route.patch.el` | GATED route — `POST /api/ground/propagate`. |
|
||||
|
||||
### Constants
|
||||
`BASE=0.10 LIKELY_MIN=0.34 GROUNDED_MIN=0.66 N_MIN=3 THETA=0.30 D=0.5`
|
||||
(`N_MIN` parameterizes Will's "13 adjacent things" — the count threshold is a
|
||||
knob; 3 here for a crisp proof.)
|
||||
|
||||
---
|
||||
|
||||
## (c) PROOF LEDGER — raw grounding before/after
|
||||
|
||||
Deterministic. Build `cc -std=c11 -O2 -o gep_proof gep_proof.c -lm`, run
|
||||
`./gep_proof` (full transcript in `PROOF_OUTPUT.txt`).
|
||||
|
||||
### (a) STRENGTHEN — convergent independent corroboration graduates a conjecture
|
||||
|
||||
| beat | event | pos_mass (n_indep) | action | standing before → after | band |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 3 independent grounded corroborators | 0.4050 (3) | **LTP** | 0.1000 → **0.4842** | conjecture → **likely** ⬆ |
|
||||
| 2 | neighborhood grows to 5 | 0.6750 (5) | **LTP** | 0.1496 → **0.7379** | conjecture → **grounded** ⬆ |
|
||||
| 3 | support sustained (5) | 0.6750 (5) | LTP | 0.2110 → 0.7993 | grounded (sustained) |
|
||||
| 4 | corroboration withdrawn (+10min) | 0.0000 (0) | isolated | 0.1612 → 0.1612 | relaxing |
|
||||
| 5 | still withdrawn (+1h) | — | isolated | 0.1263 | relaxing |
|
||||
| 6 | still withdrawn (+4h) | — | isolated | 0.1130 | → conjecture |
|
||||
|
||||
Grounding grew **on its own** past threshold and graduated conjecture → likely →
|
||||
grounded, then **relaxed** once independent support stopped. Living, not a
|
||||
latched flag.
|
||||
|
||||
### (b) DECAY — convergent independent contradiction erodes a grounded belief
|
||||
|
||||
| beat | event | neg_mass (n_indep) | action | standing before → after | band |
|
||||
|---|---|---|---|---|---|
|
||||
| — | seed (prior LTP) | — | — | **0.9500** | grounded |
|
||||
| 1 | 3 independent contradictions | 0.5400 (3) | **LTD** | 0.9500 → **0.4570** | grounded → **likely** ⬇ |
|
||||
| 2 | contradiction broadens to 5 | 0.9000 (5) | **LTD** | 0.1461 → **0.0000** | conjecture ⬇ |
|
||||
| 3–4 | contradiction sustained (5) | 0.9000 (5) | LTD | 0.0000 | conjecture |
|
||||
|
||||
Grounding decayed grounded → likely → conjecture under accreting independent
|
||||
contradiction. The door never shut — history is retained (the event ring keeps
|
||||
growing), the belief stays falsifiable in both directions.
|
||||
|
||||
### (c) INDEPENDENCE GUARD — the load-bearing property
|
||||
|
||||
Identical fan-in (N=5), identical edge weight (0.30), identical corroborator
|
||||
standing (~0.90). **The only difference is whether the five are independent.**
|
||||
|
||||
| sub-case | topology | pos_mass | **n_indep** | action | standing 0.1000 → |
|
||||
|---|---|---|---|---|---|
|
||||
| **C1** | 5 DISTINCT, no inter-links | 1.3500 | **5** | **LTP** | **0.9741 (grounded)** ⬆ |
|
||||
| **C2** | 5 mutually-linked (echo of one source) | 0.2700 | **1** | sub-threshold | 0.1000 (unchanged) |
|
||||
| **C3** | 1 node reached by 5 parallel edges | 0.2700 | **1** | sub-threshold | 0.1000 (unchanged) |
|
||||
|
||||
Same raw fan-in, opposite outcome. Union-find collapses the echoes to a single
|
||||
independent component; the count gate (`n_indep ≥ N_MIN`) then refuses them.
|
||||
**Circular self-reinforcement cannot manufacture grounding** — a conjecture can
|
||||
only be grounded by evidence that is genuinely independent of itself.
|
||||
|
||||
---
|
||||
|
||||
**RAILS honored:** isolated worktree; built/proven on a clone; the live soul
|
||||
(`:8742` / `:7770`) untouched; no fight with the cutover (built against current
|
||||
release source; staged native rebases cleanly onto it); no new libraries
|
||||
(libm only); identity keystones untouched. **Not promoted** — gated artifact +
|
||||
ledger for the main loop to sequence.
|
||||
@@ -0,0 +1,75 @@
|
||||
GROUNDED EDGE-PROPAGATION — PROOF LEDGER (task #50)
|
||||
constants: BASE=0.10 LIKELY_MIN=0.34 GROUNDED_MIN=0.66 N_MIN=3 THETA=0.30 D=0.5
|
||||
|
||||
=== SCENARIO A — STRENGTHEN: convergent independent corroboration ===
|
||||
seed: conjecture has NO grounding events; corroborators pre-grounded.
|
||||
conjecture standing=0.1000 band=conjecture events=0 subthresh=0
|
||||
beat 1 (t=+0s) 3 independent grounded corroborators appear
|
||||
incident_edges=3 pos_mass=0.4050 (n_indep=3) neg_mass=0.0000 (n_indep=0) THETA=0.30 N_MIN=3
|
||||
-> LTP (strengthen) standing 0.1000 (conjecture) -> 0.4842 (likely) [GRADUATED]
|
||||
beat 2 (t=+60s) neighborhood grows to 5 corroborators
|
||||
incident_edges=5 pos_mass=0.6750 (n_indep=5) neg_mass=0.0000 (n_indep=0) THETA=0.30 N_MIN=3
|
||||
-> LTP (strengthen) standing 0.1496 (conjecture) -> 0.7379 (grounded) [GRADUATED]
|
||||
beat 3 (t=+120s) support sustained (5)
|
||||
incident_edges=5 pos_mass=0.6750 (n_indep=5) neg_mass=0.0000 (n_indep=0) THETA=0.30 N_MIN=3
|
||||
-> LTP (strengthen) standing 0.2110 (conjecture) -> 0.7993 (grounded) [GRADUATED]
|
||||
beat 4 (t=+720s) corroboration withdrawn (+10min)
|
||||
incident_edges=0 pos_mass=0.0000 (n_indep=0) neg_mass=0.0000 (n_indep=0) THETA=0.30 N_MIN=3
|
||||
-> isolated (no edges) standing 0.1612 (conjecture) -> 0.1612 (conjecture)
|
||||
beat 5 (t=+3600s) still withdrawn (+1h)
|
||||
incident_edges=0 pos_mass=0.0000 (n_indep=0) neg_mass=0.0000 (n_indep=0) THETA=0.30 N_MIN=3
|
||||
-> isolated (no edges) standing 0.1263 (conjecture) -> 0.1263 (conjecture)
|
||||
beat 6 (t=+14400s) still withdrawn (+4h)
|
||||
incident_edges=0 pos_mass=0.0000 (n_indep=0) neg_mass=0.0000 (n_indep=0) THETA=0.30 N_MIN=3
|
||||
-> isolated (no edges) standing 0.1130 (conjecture) -> 0.1130 (conjecture)
|
||||
RESULT: grounding grew automatically past threshold and graduated,
|
||||
then relaxed once the independent support stopped — living,
|
||||
not a latched flag.
|
||||
|
||||
=== SCENARIO B — DECAY: convergent independent CONTRADICTION ===
|
||||
seed: belief pre-grounded by a strong prior LTP event.
|
||||
belief standing=0.9500 band=grounded events=1 subthresh=0
|
||||
beat 1 (t=+0s) 3 independent contradictions
|
||||
incident_edges=3 pos_mass=0.0000 (n_indep=0) neg_mass=0.5400 (n_indep=3) THETA=0.30 N_MIN=3
|
||||
-> LTD (decay) standing 0.9500 (grounded) -> 0.4570 (likely) [DEMOTED]
|
||||
beat 2 (t=+60s) contradiction broadens to 5
|
||||
incident_edges=5 pos_mass=0.0000 (n_indep=0) neg_mass=0.9000 (n_indep=5) THETA=0.30 N_MIN=3
|
||||
-> LTD (decay) standing 0.1461 (conjecture) -> 0.0000 (conjecture)
|
||||
beat 3 (t=+120s) contradiction sustained (5)
|
||||
incident_edges=5 pos_mass=0.0000 (n_indep=0) neg_mass=0.9000 (n_indep=5) THETA=0.30 N_MIN=3
|
||||
-> LTD (decay) standing 0.0401 (conjecture) -> 0.0000 (conjecture)
|
||||
beat 4 (t=+180s) contradiction sustained (5)
|
||||
incident_edges=5 pos_mass=0.0000 (n_indep=0) neg_mass=0.9000 (n_indep=5) THETA=0.30 N_MIN=3
|
||||
-> LTD (decay) standing 0.0000 (conjecture) -> 0.0000 (conjecture)
|
||||
RESULT: grounding decayed grounded->likely->conjecture under
|
||||
convergent independent contradiction. The door never shut
|
||||
on the belief; its history is retained (events keep growing).
|
||||
|
||||
=== SCENARIO C — INDEPENDENCE GUARD (the load-bearing property) ===
|
||||
Both sub-cases: N=5 corroborators, edge weight 0.30, corroborator
|
||||
standing ~0.90. ONLY difference: whether the 5 are independent.
|
||||
|
||||
-- C1: 5 DISTINCT independent corroborators --
|
||||
conjecture standing=0.1000 band=conjecture events=0 subthresh=0
|
||||
beat 1 (t=+0s) 5 independent corroborators (no inter-links)
|
||||
incident_edges=5 pos_mass=1.3500 (n_indep=5) neg_mass=0.0000 (n_indep=0) THETA=0.30 N_MIN=3
|
||||
-> LTP (strengthen) standing 0.1000 (conjecture) -> 0.9741 (grounded) [GRADUATED]
|
||||
|
||||
-- C2: 5 corroborators, but mutually-linked (echo of ONE source) --
|
||||
conjecture standing=0.1000 band=conjecture events=0 subthresh=0
|
||||
beat 1 (t=+0s) 5 echoed (mutually-linked) corroborators
|
||||
incident_edges=5 pos_mass=0.2700 (n_indep=1) neg_mass=0.0000 (n_indep=0) THETA=0.30 N_MIN=3
|
||||
-> sub-threshold (no shift) standing 0.1000 (conjecture) -> 0.1000 (conjecture)
|
||||
|
||||
-- C3: ONE corroborator, reached by 5 parallel edges --
|
||||
conjecture standing=0.1000 band=conjecture events=0 subthresh=0
|
||||
beat 1 (t=+0s) same node, 5 parallel edges
|
||||
incident_edges=5 pos_mass=0.2700 (n_indep=1) neg_mass=0.0000 (n_indep=0) THETA=0.30 N_MIN=3
|
||||
-> sub-threshold (no shift) standing 0.1000 (conjecture) -> 0.1000 (conjecture)
|
||||
|
||||
RESULT: identical raw fan-in (5) and mass inputs; C1 grounds because
|
||||
the corroboration is INDEPENDENT (5 components), C2/C3 do not
|
||||
because it collapses to ONE source. Circular self-reinforcement
|
||||
cannot manufacture grounding.
|
||||
|
||||
DONE.
|
||||
@@ -0,0 +1,60 @@
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// awareness.beat.patch.el — GATED integration hook for task #50.
|
||||
// NOT APPLIED. Shows exactly how grounded edge-propagation couples into the
|
||||
// dream/consolidation beat in neuron/awareness.el. Promotion sequenced by the
|
||||
// main loop after the engine cutover settles.
|
||||
//
|
||||
// WHY HERE. The heartbeat is the beat. Today it runs hebb_consolidate() to
|
||||
// drain the self-formed Hebbian associations into the durable store, then
|
||||
// emit_heartbeat(). Grounded edge-propagation belongs in the SAME beat, AFTER
|
||||
// consolidation: the edges hebb_consolidate() just wrote are the tethers
|
||||
// grounding propagates along. Consolidation lays down the wiring; propagation
|
||||
// grades the beliefs along it. One beat, coupled — memory 69b8babe: memory-
|
||||
// consolidation and staying-yourself are one physics.
|
||||
//
|
||||
// The propagation itself runs INSIDE the engram (native engram_ground_propagate
|
||||
// over the durable flat node/edge arrays). The soul invokes it over HTTP and
|
||||
// folds the gep_* telemetry into the heartbeat stream next to the hebb_* gauges.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// [1] New helper — sibling to hebb_consolidate() (awareness.el ~line 99).
|
||||
// Fires one grounded edge-propagation beat on the durable store and returns
|
||||
// its JSON telemetry ({"gep_strengthened":..,"gep_graduations":.., ...}).
|
||||
fn ground_propagate() -> String {
|
||||
let url_env: String = env("SOUL_ISE_URL")
|
||||
let url_state: String = if str_eq(url_env, "") { state_get("soul_engram_url") } else { url_env }
|
||||
let engram_url: String = if str_eq(url_state, "") { "http://localhost:8742" } else { url_state }
|
||||
// Same auth envelope as hebb_consolidate — this is a graph mutation (it
|
||||
// appends grounding events + updates confidence), so it is gated on _auth.
|
||||
let key_state: String = state_get("soul_engram_api_key")
|
||||
let api_key: String = if str_eq(key_state, "") { env("ENGRAM_API_KEY") } else { key_state }
|
||||
let auth_part: String = if str_eq(api_key, "") { "{}" } else { "{\"_auth\":\"" + api_key + "\"}" }
|
||||
let resp: String = http_post_json(engram_url + "/api/ground/propagate", auth_part)
|
||||
if str_eq(resp, "") { return "" }
|
||||
return resp
|
||||
}
|
||||
|
||||
// [2] Beat hook — insert between hebb_consolidate() and emit_heartbeat()
|
||||
// (awareness.el line 1286-1288). Replaces:
|
||||
//
|
||||
// let wb_sent_n: Int = hebb_consolidate()
|
||||
// state_set("soul.hebb_wb_sent", int_to_str(wb_sent_n))
|
||||
// emit_heartbeat()
|
||||
//
|
||||
// with:
|
||||
//
|
||||
// let wb_sent_n: Int = hebb_consolidate()
|
||||
// state_set("soul.hebb_wb_sent", int_to_str(wb_sent_n))
|
||||
// // Grounded edge-propagation — grade beliefs along the tethers
|
||||
// // consolidation just laid down. Threshold-gated by convergent
|
||||
// // independent corroboration; automatic, salience-ordered, bounded.
|
||||
// let gep_tel: String = ground_propagate()
|
||||
// state_set("soul.gep_last", gep_tel)
|
||||
// emit_heartbeat()
|
||||
//
|
||||
// [3] emit_heartbeat() (awareness.el ~line 201) folds soul.gep_last into the
|
||||
// heartbeat payload beside the hebb_* gauges, so graduation/decay counts
|
||||
// are visible in the durable ISE stream — the same observability discipline
|
||||
// the Hebbian rule earned (a mechanism you cannot see in the stream is a
|
||||
// mechanism you cannot trust): read state_get("soul.gep_last") and splice
|
||||
// it into the heartbeat JSON object.
|
||||
@@ -0,0 +1,188 @@
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
* engram_ground_propagate.staged.c — GATED runtime native for task #50.
|
||||
*
|
||||
* STAGED, NOT COMPILED INTO THE LIVE BINARY. This mirrors the
|
||||
* geometric_retrieve.staged.c staging pattern (memory 1cc231ec): it references
|
||||
* runtime-internal types (EngramStore, EngramNode, EngramEdge, engram_global,
|
||||
* engram_now_ms, the adj cache) and therefore compiles ONLY when spliced into
|
||||
* lang/releases/v1.0.0-20260501/el_runtime.c. Splice + promotion is sequenced
|
||||
* by the main loop AFTER the engine+HNSW cutover settles — do NOT hand-apply.
|
||||
*
|
||||
* It is the production form of the mechanism proven in gep_proof.c: the SAME
|
||||
* gep_core.h primitives (GepGrounding ring, gep_standing, gep_append,
|
||||
* union-find independence), wired directly to the live flat node/edge arrays.
|
||||
*
|
||||
* ── SPLICE PLAN (three additive edits to el_runtime.c; nothing removed) ──────
|
||||
*
|
||||
* [1] EngramNode struct (~line 6061, after hebb_elig_ts): add the grounding
|
||||
* collection. Additive; zero-initialized by the existing calloc/memset
|
||||
* paths, so legacy snapshots degrade gracefully to an empty history.
|
||||
*
|
||||
* GepGrounding grounding; // task #50 — append-only grounding ring
|
||||
*
|
||||
* [2] #include "gep_core.h" near the other engram includes, and paste the
|
||||
* body of this file below the Hebbian section (after engram_hebb_drain_json).
|
||||
*
|
||||
* [3] Persistence (engram_save node JSON ~7934 / engram_load parser ~8186):
|
||||
* serialize the grounding ring as a compact "grounding" array of
|
||||
* [ts,sign,mag] triples + subthreshold_hits so standing survives a
|
||||
* round-trip. Helpers gep_grounding_to_json / gep_grounding_parse below.
|
||||
* Until wired, grounding is in-RAM only (like the Hebbian eligibility
|
||||
* trace) — correct for a first gated rollout, but standing resets on boot.
|
||||
*
|
||||
* [4] EL surface: declare engram_ground_propagate in el_runtime.h + el_seed.c,
|
||||
* add route_ground_propagate to engram/src/server.el, called from the
|
||||
* awareness.el consolidation beat (see awareness.beat.patch.el).
|
||||
* ───────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
#include "gep_core.h"
|
||||
|
||||
/* Relation → evidential polarity. Supportive relations transmit grounding
|
||||
* gravity (+1); contradictory relations erode it (-1); everything else is a
|
||||
* NON-evidential edge (structural / navigational) and is ignored (0) — an
|
||||
* association is not a corroboration. Extend deliberately; a mis-classified
|
||||
* relation is a false corroboration. */
|
||||
static int8_t gep_relation_polarity(const char* rel) {
|
||||
if (!rel) return 0;
|
||||
if (!strcmp(rel, "supports") || !strcmp(rel, "corroborates") ||
|
||||
!strcmp(rel, "derived-from") || !strcmp(rel, "hebbian-associate") ||
|
||||
!strcmp(rel, "grounds") || !strcmp(rel, "confirms")) return +1;
|
||||
if (!strcmp(rel, "contradicts") || !strcmp(rel, "refutes") ||
|
||||
!strcmp(rel, "negates") || !strcmp(rel, "conflicts-with")) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Which nodes are BELIEFS/CONJECTURES subject to grounding propagation. Facts
|
||||
* imported as knowledge are already grounded by provenance; identity/safety
|
||||
* layers are never re-graded here. Gate on node_type + the conjecture tag. */
|
||||
static int gep_is_belief(const EngramNode* n) {
|
||||
if (!n || !n->node_type) return 0;
|
||||
if (n->layer_id == ENGRAM_LAYER_SAFETY) return 0; /* never re-grade safety */
|
||||
return !strcmp(n->node_type, "Memory") ||
|
||||
!strcmp(n->node_type, "Conjecture") ||
|
||||
!strcmp(n->node_type, "Hypothesis") ||
|
||||
!strcmp(n->node_type, "Belief") ||
|
||||
(n->tags && istr_contains(n->tags, "conjecture"));
|
||||
}
|
||||
|
||||
/* Grounding standing of an engram node, derived from its collection. This is
|
||||
* the value the verifier (#43) and realizer (calibrated assertion, 0041d917)
|
||||
* read — and it is written back into epistemic_confidence-equivalent surfaces
|
||||
* so "never speak above the grounding" is enforced from one source of truth. */
|
||||
double engram_grounding_standing(const EngramNode* n, int64_t now_ms) {
|
||||
return gep_standing(&n->grounding, now_ms);
|
||||
}
|
||||
|
||||
/* ── The beat: one pass of grounded edge-propagation over the whole store ────
|
||||
* Called from the consolidation/dream heartbeat. 1-hop, beam-capped, salience-
|
||||
* ordered so a bounded slice of the highest-salience beliefs is processed per
|
||||
* beat (the rest next beat) — never a full-graph blow-up on a 12k-node store.
|
||||
* Returns JSON telemetry for the heartbeat stream. */
|
||||
#define GEP_BELIEFS_PER_BEAT 512 /* bound work per beat; salience-prioritized */
|
||||
|
||||
el_val_t engram_ground_propagate(void) {
|
||||
EngramStore* g = engram_get();
|
||||
int64_t now = engram_now_ms();
|
||||
engram_adj_rebuild(g); /* ensure adj_from/adj_to are current */
|
||||
|
||||
int strengthened = 0, decayed = 0, subthreshold = 0;
|
||||
int graduations = 0, demotions = 0, isolated = 0, starved = 0, processed = 0;
|
||||
|
||||
for (int64_t bi = 0; bi < g->node_count && processed < GEP_BELIEFS_PER_BEAT; bi++) {
|
||||
EngramNode* b = &g->nodes[bi];
|
||||
if (!gep_is_belief(b)) continue;
|
||||
processed++;
|
||||
|
||||
int before = gep_band_rank(gep_standing(&b->grounding, now));
|
||||
|
||||
/* Gather independent corroborators over incident edges (both directions),
|
||||
* anti-delusion gated (neighbor must already be ≥ LIKELY_MIN). */
|
||||
GepCorrSet cs; cs.n = 0; int incident = 0;
|
||||
int* out = g->adj_from[bi]; int out_n = g->adj_from_len[bi];
|
||||
int* in = g->adj_to[bi]; int in_n = g->adj_to_len[bi];
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
int* lst = pass ? in : out; int ln = pass ? in_n : out_n;
|
||||
for (int k = 0; k < ln; k++) {
|
||||
EngramEdge* e = &g->edges[lst[k]];
|
||||
int8_t pol = gep_relation_polarity(e->relation);
|
||||
if (pol == 0) continue;
|
||||
incident++;
|
||||
const char* cid = pass ? e->from_id : e->to_id;
|
||||
int64_t ci = engram_find_node_index(cid);
|
||||
if (ci < 0 || ci == bi) continue;
|
||||
double cstand = gep_standing(&g->nodes[ci].grounding, now);
|
||||
if (cstand < GEP_LIKELY_MIN) continue; /* no tether */
|
||||
double contrib = e->weight * cstand * (double)pol;
|
||||
int ex = -1;
|
||||
for (int q = 0; q < cs.n; q++) if (cs.node_idx[q] == (int)ci) { ex = q; break; }
|
||||
if (ex >= 0) { if (fabs(contrib) > fabs(cs.contrib[ex])) cs.contrib[ex] = contrib; }
|
||||
else if (cs.n < GEP_MAX_CORR) {
|
||||
cs.node_idx[cs.n] = (int)ci; cs.contrib[cs.n] = contrib;
|
||||
cs.parent[cs.n] = cs.n; cs.n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Collapse mutually-derived corroborators (an edge between two of them)
|
||||
* into one independent component — the independence guard. */
|
||||
for (int x = 0; x < cs.n; x++) {
|
||||
int64_t nx = cs.node_idx[x];
|
||||
int* xout = g->adj_from[nx]; int xn = g->adj_from_len[nx];
|
||||
for (int k = 0; k < xn; k++) {
|
||||
const char* tid = g->edges[xout[k]].to_id;
|
||||
int64_t ti = engram_find_node_index(tid);
|
||||
for (int y = 0; y < cs.n; y++)
|
||||
if (cs.node_idx[y] == (int)ti) { gep_uf_union(&cs, x, y); break; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Per-component max-magnitude, split by polarity → convergent independent
|
||||
* support mass + independence count. */
|
||||
double comp_best[GEP_MAX_CORR]; int comp_root[GEP_MAX_CORR], ncomp = 0;
|
||||
for (int i = 0; i < cs.n; i++) {
|
||||
int r = gep_uf_find(&cs, i), slot = -1;
|
||||
for (int kk = 0; kk < ncomp; kk++) if (comp_root[kk] == r) { slot = kk; break; }
|
||||
if (slot < 0) { slot = ncomp++; comp_root[slot] = r; comp_best[slot] = cs.contrib[i]; }
|
||||
else if (fabs(cs.contrib[i]) > fabs(comp_best[slot])) comp_best[slot] = cs.contrib[i];
|
||||
}
|
||||
double pos = 0, neg = 0; int np = 0, nn = 0; uint64_t sig = 1469598103934665603ULL;
|
||||
for (int k = 0; k < ncomp; k++) {
|
||||
if (comp_best[k] > 0) { pos += comp_best[k]; np++; }
|
||||
else if (comp_best[k] < 0) { neg += -comp_best[k]; nn++; }
|
||||
sig = (sig ^ (uint64_t)comp_root[k]) * 1099511628211ULL;
|
||||
}
|
||||
|
||||
double net = pos - neg;
|
||||
if (net > 0 && pos >= GEP_THETA && np >= GEP_N_MIN) {
|
||||
gep_append(&b->grounding, now, +1, tanh(GEP_MAG_GAIN * net), sig);
|
||||
strengthened++;
|
||||
} else if (net < 0 && neg >= GEP_THETA && nn >= GEP_N_MIN) {
|
||||
gep_append(&b->grounding, now, -1, tanh(GEP_MAG_GAIN * (-net)), sig);
|
||||
decayed++;
|
||||
} else if (np > 0 || nn > 0) {
|
||||
b->grounding.subthreshold_hits++; subthreshold++;
|
||||
} else if (incident == 0) { isolated++; }
|
||||
else { starved++; }
|
||||
|
||||
/* Mirror the derived standing onto confidence so downstream reads
|
||||
* (activate epistemic_confidence, realizer calibration) never exceed the
|
||||
* grounding. Faithful representation, single source of truth. */
|
||||
double stand = gep_standing(&b->grounding, now);
|
||||
b->confidence = stand;
|
||||
b->updated_at = now;
|
||||
|
||||
int after = gep_band_rank(stand);
|
||||
if (after > before) graduations++;
|
||||
if (after < before) demotions++;
|
||||
}
|
||||
|
||||
/* Heartbeat telemetry — the gep_* line, sibling to the hebb_* gauges. */
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof buf,
|
||||
"{\"gep_processed\":%d,\"gep_strengthened\":%d,\"gep_decayed\":%d,"
|
||||
"\"gep_subthreshold\":%d,\"gep_graduations\":%d,\"gep_demotions\":%d,"
|
||||
"\"gep_isolated\":%d,\"gep_starved\":%d}",
|
||||
processed, strengthened, decayed, subthreshold,
|
||||
graduations, demotions, isolated, starved);
|
||||
return EL_STR(el_strdup(buf));
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
* gep_core.h — Grounded Edge-Propagation, the core mechanism (task #50).
|
||||
*
|
||||
* Edge-aware, dream-coupled consolidation. Runs DURING the consolidation/dream
|
||||
* beat (awareness.el hebb_consolidate → engram_ground_propagate). Grounding
|
||||
* propagates + strengthens/decays along edges, threshold-gated by CONVERGENT
|
||||
* INDEPENDENT corroboration from adjacent grounded nodes.
|
||||
*
|
||||
* This header is the single source of truth for the algorithm. It is pure C
|
||||
* (libm only — own-the-core, no new libraries) and operates on a compact graph
|
||||
* view (GepGraph) that both the proof harness and the runtime native populate
|
||||
* from the live EngramStore (nodes/edges flat arrays + adj_from/adj_to).
|
||||
*
|
||||
* SPEC (Will, 2026-08-15; memory 9e09a59f, refines 1a861007):
|
||||
* - A grounding is a VECTOR + its HEBBIAN WEIGHTS — a weighted structure over
|
||||
* the evidential neighborhood, NOT a scalar and NOT a flat list. It APPENDS
|
||||
* and GROWS on SIGNIFICANT change. => grounding = an APPEND-ONLY event ring
|
||||
* (GepGrounding), parallel to the ACT-R base-level access_ts ring already in
|
||||
* EngramNode. Current standing is DERIVED, recency-weighted, never stored.
|
||||
* - UPDATE = LTP/LTD with a THRESHOLD (the key nonlinearity). Sub-threshold =
|
||||
* recorded in history but TRANSIENT (no lasting shift). Cross the threshold
|
||||
* of convergent support → grounding STRENGTHENS. Contradiction/erosion past
|
||||
* threshold → grounding DECAYS. Automatic, event-driven, salience-gated.
|
||||
* - DRIVER = CONVERGENT INDEPENDENT CORROBORATION (coherentism, mechanized):
|
||||
* when N INDEPENDENT adjacent nodes ground as likely-true around a
|
||||
* conjecture (Will's example: 13), its grounding grows on its own.
|
||||
* - INDEPENDENCE is load-bearing: N DISTINCT corroborators, not one node
|
||||
* echoed N times. Guards against circular self-reinforcement.
|
||||
* - ANTI-DELUSION GRAVITY (memory 0b15017c): support flows only FROM already-
|
||||
* grounded neighbors. A belief cannot ground from ungrounded speculation,
|
||||
* however self-consistent — nothing tethers it to the grounded core.
|
||||
* - NOTHING IS SETTLED (memory 271f1163): grounded is strongly-held, still
|
||||
* falsifiable. Decay path stays open on every node; history is append-only,
|
||||
* supersede-not-delete.
|
||||
* ───────────────────────────────────────────────────────────────────────── */
|
||||
#ifndef GEP_CORE_H
|
||||
#define GEP_CORE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── Constants ──────────────────────────────────────────────────────────────
|
||||
* GEP_DECAY_D matches ENGRAM_BLL_D (0.5, canonical ACT-R): the derived standing
|
||||
* is recency-weighted over the grounding-event collection exactly as the
|
||||
* base-level term is recency-weighted over the access ring (memory 1a861007:
|
||||
* "structurally the ACT-R base-level pattern, a sum over time-stamped events").
|
||||
*/
|
||||
#define GEP_DECAY_D 0.5 /* ACT-R power-law recency exponent */
|
||||
#define GEP_BASE 0.10 /* standing floor of a bare conjecture */
|
||||
#define GEP_LIKELY_MIN 0.34 /* band: conjecture < LIKELY ≤ likely */
|
||||
#define GEP_GROUNDED_MIN 0.66 /* band: likely < GROUNDED ≤ grounded */
|
||||
#define GEP_N_MIN 3 /* min INDEPENDENT corroborators to cross */
|
||||
#define GEP_THETA 0.30 /* min convergent-support MASS to cross */
|
||||
#define GEP_MAG_GAIN 1.0 /* net-support → event-magnitude gain (tanh) */
|
||||
#define GEP_EVENT_RING 32 /* grounding-history depth kept exactly */
|
||||
|
||||
/* A single grounding event — one contact with the evidential neighborhood.
|
||||
* Append-only; the ring is the collection-over-time, the standing is derived. */
|
||||
typedef struct {
|
||||
int64_t ts; /* wall-clock ms of the grounding event */
|
||||
int8_t sign; /* +1 = LTP (strengthen), -1 = LTD (decay) */
|
||||
double mag; /* magnitude in (0,1], = tanh(gain·|net independent support|)*/
|
||||
uint64_t sig; /* signature of the independent corroborator set (audit) */
|
||||
} GepEvent;
|
||||
|
||||
/* The grounding of one node: an append-only ring of events + transient counters.
|
||||
* older_count keeps the tail (events aged out of the ring) so the collection is
|
||||
* never silently lost — supersede-not-delete. subthreshold_hits records beats
|
||||
* where support was present but did NOT cross threshold (transient, no shift). */
|
||||
typedef struct {
|
||||
GepEvent ev[GEP_EVENT_RING];
|
||||
int head; /* next write slot */
|
||||
int filled; /* valid entries (≤ GEP_EVENT_RING) */
|
||||
int64_t older_count; /* durable events aged past the ring */
|
||||
int subthreshold_hits; /* transient sub-threshold beats, no shift */
|
||||
} GepGrounding;
|
||||
|
||||
typedef struct {
|
||||
const char* id;
|
||||
GepGrounding gr;
|
||||
int is_belief; /* 1 = subject to propagation (conjecture/belief) */
|
||||
} GepNode;
|
||||
|
||||
/* An edge carries a HEBBIAN WEIGHT (EngramEdge.weight) and a polarity derived
|
||||
* from its relation: supportive (supports/corroborates/derived-from/hebbian-
|
||||
* associate) = +1, contradictory (contradicts/refutes) = -1. */
|
||||
typedef struct {
|
||||
int from; /* node index */
|
||||
int to; /* node index */
|
||||
double weight; /* Hebbian edge weight, [0,1] */
|
||||
int8_t polarity; /* +1 supportive, -1 contradictory */
|
||||
} GepEdge;
|
||||
|
||||
typedef struct {
|
||||
GepNode* nodes; int n_nodes;
|
||||
GepEdge* edges; int n_edges;
|
||||
} GepGraph;
|
||||
|
||||
typedef struct {
|
||||
int strengthened; /* beliefs that took an LTP event this beat */
|
||||
int decayed; /* beliefs that took an LTD event this beat */
|
||||
int subthreshold; /* beliefs with support present but below threshold */
|
||||
int graduations; /* band-up transitions (conjecture→likely→grounded) */
|
||||
int demotions; /* band-down transitions */
|
||||
int isolated; /* belief nodes with ZERO incident edges (sparse graph) */
|
||||
int starved; /* belief nodes with edges but NO grounded corroborator */
|
||||
} GepBeatStats;
|
||||
|
||||
/* Real-graph note (live measurement 2026-08-15): 70.7% of nodes are isolated,
|
||||
* connected core ~28%. Grounded edge-propagation is definitionally scoped to
|
||||
* the connected core — a belief with no grounded neighbor has nothing to
|
||||
* tether to (anti-delusion gravity). isolated/starved are surfaced as an
|
||||
* interoceptive signal for the edge-formation / embedding pass (#20) to try to
|
||||
* connect them; #50 CONSUMES edges, it does not form them. */
|
||||
|
||||
/* ── Standing derivation: collection → scalar, recency-weighted ─────────────
|
||||
* standing = clamp( GEP_BASE + Σ_events sign·mag·age^(-D) , 0, 1 ).
|
||||
* Exactly the ACT-R base-level shape (Σ t^-d) but sign-carrying so LTD subtracts.
|
||||
* The value is a pure function of wall-clock time — idempotent, never stored. */
|
||||
static inline double gep_standing(const GepGrounding* g, int64_t now_ms) {
|
||||
double raw = 0.0;
|
||||
for (int i = 0; i < g->filled; i++) {
|
||||
double age = (double)(now_ms - g->ev[i].ts) / 1000.0;
|
||||
if (age < 1.0) age = 1.0; /* clock-skew / same-beat → 1s */
|
||||
raw += (double)g->ev[i].sign * g->ev[i].mag * pow(age, -GEP_DECAY_D);
|
||||
}
|
||||
double s = GEP_BASE + raw;
|
||||
if (s < 0.0) s = 0.0;
|
||||
if (s > 1.0) s = 1.0;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Band label from a standing value. */
|
||||
static inline const char* gep_band(double standing) {
|
||||
if (standing >= GEP_GROUNDED_MIN) return "grounded";
|
||||
if (standing >= GEP_LIKELY_MIN) return "likely";
|
||||
return "conjecture";
|
||||
}
|
||||
static inline int gep_band_rank(double standing) {
|
||||
if (standing >= GEP_GROUNDED_MIN) return 2;
|
||||
if (standing >= GEP_LIKELY_MIN) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Append one grounding event to the ring (append-only; oldest slot recycles,
|
||||
* its loss counted in older_count so the collection's depth is never faked). */
|
||||
static inline void gep_append(GepGrounding* g, int64_t ts, int8_t sign,
|
||||
double mag, uint64_t sig) {
|
||||
if (g->filled >= GEP_EVENT_RING) g->older_count++;
|
||||
g->ev[g->head].ts = ts;
|
||||
g->ev[g->head].sign = sign;
|
||||
g->ev[g->head].mag = mag;
|
||||
g->ev[g->head].sig = sig;
|
||||
g->head = (g->head + 1) % GEP_EVENT_RING;
|
||||
if (g->filled < GEP_EVENT_RING) g->filled++;
|
||||
}
|
||||
|
||||
/* ── Independence via union-find over corroborators ─────────────────────────
|
||||
* Two corroborators are the SAME independent source if they are the same node,
|
||||
* or if a direct edge links them (mutually-derived / echoed through a chain).
|
||||
* Counting DISTINCT components — not raw corroborator count — is the guard
|
||||
* against one node echoed N times reading as N independent corroborations. */
|
||||
#define GEP_MAX_CORR 256
|
||||
typedef struct {
|
||||
int node_idx[GEP_MAX_CORR]; /* corroborator node index */
|
||||
double contrib[GEP_MAX_CORR]; /* weight·standing(c) */
|
||||
int parent[GEP_MAX_CORR]; /* union-find parent */
|
||||
int n;
|
||||
} GepCorrSet;
|
||||
|
||||
static int gep_uf_find(GepCorrSet* s, int x) {
|
||||
while (s->parent[x] != x) { s->parent[x] = s->parent[s->parent[x]]; x = s->parent[x]; }
|
||||
return x;
|
||||
}
|
||||
static void gep_uf_union(GepCorrSet* s, int a, int b) {
|
||||
int ra = gep_uf_find(s, a), rb = gep_uf_find(s, b);
|
||||
if (ra != rb) s->parent[ra] = rb;
|
||||
}
|
||||
/* index of node_idx within the corroborator set, or -1 */
|
||||
static int gep_corr_index_of(const GepCorrSet* s, int node_idx) {
|
||||
for (int i = 0; i < s->n; i++) if (s->node_idx[i] == node_idx) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ── The beat: grounded edge-propagation over one belief node ───────────────
|
||||
* Returns +1 if an LTP event was appended, -1 if LTD, 0 if sub-threshold/none.
|
||||
* out_pos/out_neg/out_np/out_nn expose the raw support decomposition for the
|
||||
* proof ledger (mass and independent-component counts on each polarity). */
|
||||
static int gep_propagate_node(GepGraph* g, int b, int64_t now_ms,
|
||||
double* out_pos, double* out_neg,
|
||||
int* out_np, int* out_nn, int* out_incident) {
|
||||
GepCorrSet cs; cs.n = 0;
|
||||
int incident = 0; /* any edge touching b at all — isolation detector */
|
||||
|
||||
/* 1. Gather corroborators along incident edges. Anti-delusion gravity:
|
||||
* only ALREADY-grounded neighbors (standing ≥ LIKELY_MIN) may corroborate.
|
||||
* Each contributes weight·standing; polarity kept via signed contrib.
|
||||
* 1-HOP ONLY — no BFS fan-out, so no per-hop breadth explosion. The
|
||||
* corroborator working set is hard-capped at GEP_MAX_CORR (beam bound
|
||||
* against hub belief nodes with thousands of incident edges). */
|
||||
for (int e = 0; e < g->n_edges; e++) {
|
||||
int c = -1; int8_t pol = 0;
|
||||
if (g->edges[e].from == b) { c = g->edges[e].to; pol = g->edges[e].polarity; }
|
||||
else if (g->edges[e].to == b) { c = g->edges[e].from; pol = g->edges[e].polarity; }
|
||||
else continue;
|
||||
incident++;
|
||||
if (c < 0 || c == b) continue;
|
||||
double cs_standing = gep_standing(&g->nodes[c].gr, now_ms);
|
||||
if (cs_standing < GEP_LIKELY_MIN) continue; /* ungrounded ⇒ no pull */
|
||||
double contribution = g->edges[e].weight * cs_standing * (double)pol;
|
||||
int existing = gep_corr_index_of(&cs, c);
|
||||
if (existing >= 0) {
|
||||
/* same corroborator id reached twice (multi-edge echo): keep the
|
||||
* strongest-magnitude contribution, do NOT add — one source, one vote */
|
||||
if (fabs(contribution) > fabs(cs.contrib[existing]))
|
||||
cs.contrib[existing] = contribution;
|
||||
} else if (cs.n < GEP_MAX_CORR) { /* beam bound against hub belief nodes */
|
||||
cs.node_idx[cs.n] = c;
|
||||
cs.contrib[cs.n] = contribution;
|
||||
cs.parent[cs.n] = cs.n;
|
||||
cs.n++;
|
||||
}
|
||||
}
|
||||
if (out_incident) *out_incident = incident;
|
||||
|
||||
/* 2. Collapse mutually-derived corroborators (an edge between two of them =
|
||||
* echo chain / shared derivation) into one independent component. */
|
||||
for (int e = 0; e < g->n_edges; e++) {
|
||||
int ia = gep_corr_index_of(&cs, g->edges[e].from);
|
||||
int ib = gep_corr_index_of(&cs, g->edges[e].to);
|
||||
if (ia >= 0 && ib >= 0) gep_uf_union(&cs, ia, ib);
|
||||
}
|
||||
|
||||
/* 3. Per independent component, take the MAX-magnitude member (echoes don't
|
||||
* inflate mass either), split by polarity. Convergent INDEPENDENT support
|
||||
* = sum over components; independence count = number of components. */
|
||||
double comp_best[GEP_MAX_CORR];
|
||||
int comp_root[GEP_MAX_CORR]; int n_comp = 0;
|
||||
for (int i = 0; i < cs.n; i++) {
|
||||
int r = gep_uf_find(&cs, i);
|
||||
int slot = -1;
|
||||
for (int k = 0; k < n_comp; k++) if (comp_root[k] == r) { slot = k; break; }
|
||||
if (slot < 0) { slot = n_comp++; comp_root[slot] = r; comp_best[slot] = cs.contrib[i]; }
|
||||
else if (fabs(cs.contrib[i]) > fabs(comp_best[slot])) comp_best[slot] = cs.contrib[i];
|
||||
}
|
||||
double pos = 0.0, neg = 0.0; int np = 0, nn = 0;
|
||||
uint64_t sig = 1469598103934665603ULL; /* FNV offset — signature of the set */
|
||||
for (int k = 0; k < n_comp; k++) {
|
||||
if (comp_best[k] > 0.0) { pos += comp_best[k]; np++; }
|
||||
else if (comp_best[k] < 0.0) { neg += -comp_best[k]; nn++; }
|
||||
sig = (sig ^ (uint64_t)comp_root[k]) * 1099511628211ULL;
|
||||
}
|
||||
if (out_pos) *out_pos = pos; if (out_neg) *out_neg = neg;
|
||||
if (out_np) *out_np = np; if (out_nn) *out_nn = nn;
|
||||
|
||||
double net = pos - neg;
|
||||
|
||||
/* 4. Threshold gate. Convergent independent corroboration must clear BOTH a
|
||||
* MASS threshold (THETA) and an INDEPENDENCE-count threshold (N_MIN).
|
||||
* The count gate is the independence guard: echoed support collapses to
|
||||
* one component and never reaches N_MIN however large the raw fan-in. */
|
||||
if (net > 0.0 && pos >= GEP_THETA && np >= GEP_N_MIN) {
|
||||
double mag = tanh(GEP_MAG_GAIN * net);
|
||||
gep_append(&g->nodes[b].gr, now_ms, +1, mag, sig);
|
||||
return +1;
|
||||
}
|
||||
if (net < 0.0 && neg >= GEP_THETA && nn >= GEP_N_MIN) {
|
||||
double mag = tanh(GEP_MAG_GAIN * (-net));
|
||||
gep_append(&g->nodes[b].gr, now_ms, -1, mag, sig);
|
||||
return -1;
|
||||
}
|
||||
/* Sub-threshold: support seen but did not cross. Recorded, transient, no
|
||||
* lasting shift — exactly Will's "recorded in history but transient". */
|
||||
if (np > 0 || nn > 0) g->nodes[b].gr.subthreshold_hits++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Run one consolidation/dream beat over every belief node in the graph. */
|
||||
static inline GepBeatStats gep_beat(GepGraph* g, int64_t now_ms) {
|
||||
GepBeatStats st; memset(&st, 0, sizeof st);
|
||||
for (int b = 0; b < g->n_nodes; b++) {
|
||||
if (!g->nodes[b].is_belief) continue;
|
||||
int before = gep_band_rank(gep_standing(&g->nodes[b].gr, now_ms));
|
||||
double pos, neg; int np, nn, incident;
|
||||
int r = gep_propagate_node(g, b, now_ms, &pos, &neg, &np, &nn, &incident);
|
||||
int after = gep_band_rank(gep_standing(&g->nodes[b].gr, now_ms));
|
||||
if (r > 0) st.strengthened++;
|
||||
else if (r < 0) st.decayed++;
|
||||
else if (np > 0 || nn > 0) st.subthreshold++;
|
||||
else if (incident == 0) st.isolated++; /* sparse-graph reality */
|
||||
else st.starved++; /* has edges, no grounded neighbor */
|
||||
if (after > before) st.graduations++;
|
||||
if (after < before) st.demotions++;
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
#endif /* GEP_CORE_H */
|
||||
@@ -0,0 +1,232 @@
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
* gep_proof.c — PROOF LEDGER for grounded edge-propagation (task #50).
|
||||
*
|
||||
* Self-contained. Builds three scenarios on an in-memory GepGraph that mirrors
|
||||
* the live EngramStore's flat node/edge arrays, runs the consolidation/dream
|
||||
* beat (gep_beat), and prints RAW grounding before/after for each:
|
||||
*
|
||||
* (A) STRENGTHEN — a conjecture + N independent grounded corroborators.
|
||||
* Grounding grows past threshold, GRADUATES conjecture→
|
||||
* likely→grounded, then RELAXES when corroboration stops
|
||||
* (nothing is settled).
|
||||
* (B) DECAY — a grounded belief meets N independent CONTRADICTORY
|
||||
* corroborators. Grounding decays grounded→likely→conjecture.
|
||||
* (C) INDEPENDENCE GUARD — identical fan-in of N=5, weights, and standings.
|
||||
* C1: 5 DISTINCT independent corroborators → grounds.
|
||||
* C2: the SAME support echoed (5 mutually-linked / one node
|
||||
* repeated) → collapses to 1 independent → does NOT.
|
||||
*
|
||||
* Build: cc -std=c11 -O2 -o gep_proof gep_proof.c -lm
|
||||
* Run: ./gep_proof
|
||||
* ───────────────────────────────────────────────────────────────────────── */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "gep_core.h"
|
||||
|
||||
#define T0 1786000000000LL /* fixed base time (ms) — deterministic */
|
||||
#define BEAT_MS 60000LL /* 60s heartbeat cadence (awareness.el) */
|
||||
|
||||
/* Seed a node's grounding with a prior LTP event so it reads as already-grounded
|
||||
* (a member of the grounded core that gravity radiates from). mag→standing:
|
||||
* standing = GEP_BASE + mag (event at ~now). */
|
||||
static void seed_grounded(GepNode* n, double mag, int64_t ts) {
|
||||
memset(&n->gr, 0, sizeof n->gr);
|
||||
gep_append(&n->gr, ts, +1, mag, 0);
|
||||
}
|
||||
|
||||
/* Re-anchor every NON-belief node (the corroborators/refuters) as a freshly-
|
||||
* grounded member of the core AT time `now`. These nodes are, by definition,
|
||||
* sustained members of the grounded core — each has its OWN ongoing
|
||||
* corroboration — so their standing must be read as grounded at each beat, not
|
||||
* left to power-law-decay out of the core between beats. The belief-under-test
|
||||
* is NEVER re-anchored: its trajectory is driven only by the propagation. */
|
||||
static void anchor_core(GepGraph* g, int64_t now, double mag) {
|
||||
for (int i = 0; i < g->n_nodes; i++)
|
||||
if (!g->nodes[i].is_belief) seed_grounded(&g->nodes[i], mag, now);
|
||||
}
|
||||
|
||||
static void print_node(const char* tag, GepNode* n, int64_t now) {
|
||||
double s = gep_standing(&n->gr, now);
|
||||
printf(" %-14s standing=%.4f band=%-10s events=%d subthresh=%d\n",
|
||||
tag, s, gep_band(s), n->gr.filled, n->gr.subthreshold_hits);
|
||||
}
|
||||
|
||||
/* Run one beat over a single belief node b and print the raw support decomposition. */
|
||||
static void beat_and_report(GepGraph* g, int b, int64_t now, int beatno,
|
||||
const char* note) {
|
||||
anchor_core(g, now, 0.80); /* corroborators stay grounded at each beat */
|
||||
double s_before = gep_standing(&g->nodes[b].gr, now);
|
||||
int r_before = gep_band_rank(s_before);
|
||||
double pos, neg; int np, nn, incident;
|
||||
int r = gep_propagate_node(g, b, now, &pos, &neg, &np, &nn, &incident);
|
||||
double s_after = gep_standing(&g->nodes[b].gr, now);
|
||||
int r_after = gep_band_rank(s_after);
|
||||
const char* action = (r > 0) ? "LTP (strengthen)"
|
||||
: (r < 0) ? "LTD (decay)"
|
||||
: (np || nn) ? "sub-threshold (no shift)"
|
||||
: (incident == 0) ? "isolated (no edges)"
|
||||
: "starved (no grounded neighbor)";
|
||||
printf(" beat %d (t=+%llds) %s\n", beatno,
|
||||
(long long)((now - T0) / 1000), note ? note : "");
|
||||
printf(" incident_edges=%d pos_mass=%.4f (n_indep=%d) neg_mass=%.4f (n_indep=%d)"
|
||||
" THETA=%.2f N_MIN=%d\n",
|
||||
incident, pos, np, neg, nn, (double)GEP_THETA, GEP_N_MIN);
|
||||
printf(" -> %-26s standing %.4f (%s) -> %.4f (%s)%s\n",
|
||||
action, s_before, gep_band(s_before), s_after, gep_band(s_after),
|
||||
(r_after > r_before) ? " [GRADUATED]"
|
||||
: (r_after < r_before) ? " [DEMOTED]" : "");
|
||||
}
|
||||
|
||||
/* ── Scenario A — STRENGTHEN + graduation + relaxation ───────────────────── */
|
||||
static void scenario_A(void) {
|
||||
printf("\n=== SCENARIO A — STRENGTHEN: convergent independent corroboration ===\n");
|
||||
/* nodes[0] = the conjecture (belief). nodes[1..8] = independent corroborators,
|
||||
* each already grounded, each tethered to the conjecture by a weak young
|
||||
* hebbian-associate edge (weight 0.15 = ENGRAM_HEBB_LINK_W0). The corroborators
|
||||
* are NOT linked to each other → fully independent. */
|
||||
static GepNode nodes[9];
|
||||
static GepEdge edges[8];
|
||||
memset(nodes, 0, sizeof nodes);
|
||||
nodes[0].id = "conjecture"; nodes[0].is_belief = 1; /* bare: standing = BASE */
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
nodes[i].id = "corroborator";
|
||||
seed_grounded(&nodes[i], 0.80, T0); /* standing ≈ 0.90 → grounded core */
|
||||
}
|
||||
GepGraph g = { nodes, 9, edges, 0 };
|
||||
|
||||
printf(" seed: conjecture has NO grounding events; corroborators pre-grounded.\n");
|
||||
print_node("conjecture", &nodes[0], T0);
|
||||
|
||||
/* Beat 1: 3 independent corroborators have grounded up around the conjecture. */
|
||||
g.n_edges = 0;
|
||||
for (int i = 1; i <= 3; i++)
|
||||
edges[g.n_edges++] = (GepEdge){ 0, i, 0.15, +1 };
|
||||
beat_and_report(&g, 0, T0, 1, "3 independent grounded corroborators appear");
|
||||
|
||||
/* Beat 2: the neighborhood fills in — 5 independent corroborators now. */
|
||||
g.n_edges = 0;
|
||||
for (int i = 1; i <= 5; i++)
|
||||
edges[g.n_edges++] = (GepEdge){ 0, i, 0.15, +1 };
|
||||
beat_and_report(&g, 0, T0 + BEAT_MS, 2, "neighborhood grows to 5 corroborators");
|
||||
|
||||
/* Beat 3: support sustained at 5 (grounding refreshed). */
|
||||
beat_and_report(&g, 0, T0 + 2 * BEAT_MS, 3, "support sustained (5)");
|
||||
|
||||
/* Beats 4-6: corroboration REMOVED (neighbors superseded / no longer ground).
|
||||
* No new events; the collection ages → standing relaxes. Nothing is settled. */
|
||||
g.n_edges = 0;
|
||||
beat_and_report(&g, 0, T0 + 12 * BEAT_MS, 4, "corroboration withdrawn (+10min)");
|
||||
beat_and_report(&g, 0, T0 + 60 * BEAT_MS, 5, "still withdrawn (+1h)");
|
||||
beat_and_report(&g, 0, T0 + 240 * BEAT_MS, 6, "still withdrawn (+4h)");
|
||||
printf(" RESULT: grounding grew automatically past threshold and graduated,\n"
|
||||
" then relaxed once the independent support stopped — living,\n"
|
||||
" not a latched flag.\n");
|
||||
}
|
||||
|
||||
/* ── Scenario B — DECAY via accreting contradiction ─────────────────────── */
|
||||
static void scenario_B(void) {
|
||||
printf("\n=== SCENARIO B — DECAY: convergent independent CONTRADICTION ===\n");
|
||||
static GepNode nodes[6];
|
||||
static GepEdge edges[5];
|
||||
memset(nodes, 0, sizeof nodes);
|
||||
nodes[0].id = "belief"; nodes[0].is_belief = 1;
|
||||
/* Seed the belief as already GROUNDED via a strong prior LTP event. */
|
||||
seed_grounded(&nodes[0], 0.85, T0);
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
nodes[i].id = "refuter";
|
||||
seed_grounded(&nodes[i], 0.80, T0); /* grounded contradictors */
|
||||
}
|
||||
GepGraph g = { nodes, 6, edges, 0 };
|
||||
|
||||
printf(" seed: belief pre-grounded by a strong prior LTP event.\n");
|
||||
print_node("belief", &nodes[0], T0);
|
||||
|
||||
/* Contradiction accretes over successive beats: 3 then 5 independent grounded
|
||||
* refuters (polarity -1). Each beat past threshold appends an LTD event.
|
||||
* Beat 1 runs at the seed instant so the trajectory starts from grounded. */
|
||||
g.n_edges = 0;
|
||||
for (int i = 1; i <= 3; i++) edges[g.n_edges++] = (GepEdge){ 0, i, 0.20, -1 };
|
||||
beat_and_report(&g, 0, T0, 1, "3 independent contradictions");
|
||||
|
||||
g.n_edges = 0;
|
||||
for (int i = 1; i <= 5; i++) edges[g.n_edges++] = (GepEdge){ 0, i, 0.20, -1 };
|
||||
beat_and_report(&g, 0, T0 + BEAT_MS, 2, "contradiction broadens to 5");
|
||||
beat_and_report(&g, 0, T0 + 2 * BEAT_MS, 3, "contradiction sustained (5)");
|
||||
beat_and_report(&g, 0, T0 + 3 * BEAT_MS, 4, "contradiction sustained (5)");
|
||||
printf(" RESULT: grounding decayed grounded->likely->conjecture under\n"
|
||||
" convergent independent contradiction. The door never shut\n"
|
||||
" on the belief; its history is retained (events keep growing).\n");
|
||||
}
|
||||
|
||||
/* ── Scenario C — INDEPENDENCE GUARD ─────────────────────────────────────── */
|
||||
static void scenario_C(void) {
|
||||
printf("\n=== SCENARIO C — INDEPENDENCE GUARD (the load-bearing property) ===\n");
|
||||
printf(" Both sub-cases: N=5 corroborators, edge weight 0.30, corroborator\n"
|
||||
" standing ~0.90. ONLY difference: whether the 5 are independent.\n");
|
||||
|
||||
/* C1 — 5 DISTINCT INDEPENDENT corroborators (no edges among them). */
|
||||
{
|
||||
printf("\n -- C1: 5 DISTINCT independent corroborators --\n");
|
||||
static GepNode nodes[6];
|
||||
static GepEdge edges[5];
|
||||
memset(nodes, 0, sizeof nodes);
|
||||
nodes[0].id = "conjecture"; nodes[0].is_belief = 1;
|
||||
for (int i = 1; i <= 5; i++) { nodes[i].id = "corr"; seed_grounded(&nodes[i], 0.80, T0); }
|
||||
for (int i = 1; i <= 5; i++) edges[i-1] = (GepEdge){ 0, i, 0.30, +1 };
|
||||
GepGraph g = { nodes, 6, edges, 5 };
|
||||
print_node("conjecture", &nodes[0], T0);
|
||||
beat_and_report(&g, 0, T0, 1, "5 independent corroborators (no inter-links)");
|
||||
}
|
||||
|
||||
/* C2 — the SAME support echoed: 5 corroborators that are all mutually linked
|
||||
* (a derivation clique — one source echoed through the chain). Same fan-in to
|
||||
* the conjecture, same weights, same standings. Union-find collapses them to
|
||||
* ONE independent component → below N_MIN → NO strengthening. */
|
||||
{
|
||||
printf("\n -- C2: 5 corroborators, but mutually-linked (echo of ONE source) --\n");
|
||||
static GepNode nodes[6];
|
||||
static GepEdge edges[9]; /* 5 to conjecture + 4 chaining corr1..corr5 */
|
||||
memset(nodes, 0, sizeof nodes);
|
||||
nodes[0].id = "conjecture"; nodes[0].is_belief = 1;
|
||||
for (int i = 1; i <= 5; i++) { nodes[i].id = "corr"; seed_grounded(&nodes[i], 0.80, T0); }
|
||||
int ne = 0;
|
||||
for (int i = 1; i <= 5; i++) edges[ne++] = (GepEdge){ 0, i, 0.30, +1 };
|
||||
/* chain corr1-corr2-corr3-corr4-corr5: they are the same source echoed */
|
||||
for (int i = 1; i <= 4; i++) edges[ne++] = (GepEdge){ i, i+1, 0.30, +1 };
|
||||
GepGraph g = { nodes, 6, edges, ne };
|
||||
print_node("conjecture", &nodes[0], T0);
|
||||
beat_and_report(&g, 0, T0, 1, "5 echoed (mutually-linked) corroborators");
|
||||
}
|
||||
|
||||
/* C3 — degenerate echo: literally ONE corroborator reached by 5 parallel edges. */
|
||||
{
|
||||
printf("\n -- C3: ONE corroborator, reached by 5 parallel edges --\n");
|
||||
static GepNode nodes[2];
|
||||
static GepEdge edges[5];
|
||||
memset(nodes, 0, sizeof nodes);
|
||||
nodes[0].id = "conjecture"; nodes[0].is_belief = 1;
|
||||
nodes[1].id = "corr"; seed_grounded(&nodes[1], 0.80, T0);
|
||||
for (int i = 0; i < 5; i++) edges[i] = (GepEdge){ 0, 1, 0.30, +1 };
|
||||
GepGraph g = { nodes, 2, edges, 5 };
|
||||
print_node("conjecture", &nodes[0], T0);
|
||||
beat_and_report(&g, 0, T0, 1, "same node, 5 parallel edges");
|
||||
}
|
||||
|
||||
printf("\n RESULT: identical raw fan-in (5) and mass inputs; C1 grounds because\n"
|
||||
" the corroboration is INDEPENDENT (5 components), C2/C3 do not\n"
|
||||
" because it collapses to ONE source. Circular self-reinforcement\n"
|
||||
" cannot manufacture grounding.\n");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("GROUNDED EDGE-PROPAGATION — PROOF LEDGER (task #50)\n");
|
||||
printf("constants: BASE=%.2f LIKELY_MIN=%.2f GROUNDED_MIN=%.2f "
|
||||
"N_MIN=%d THETA=%.2f D=%.1f\n",
|
||||
(double)GEP_BASE, (double)GEP_LIKELY_MIN, (double)GEP_GROUNDED_MIN,
|
||||
GEP_N_MIN, (double)GEP_THETA, (double)GEP_DECAY_D);
|
||||
scenario_A();
|
||||
scenario_B();
|
||||
scenario_C();
|
||||
printf("\nDONE.\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// server.route.patch.el — GATED route for task #50, for engram/src/server.el.
|
||||
// NOT APPLIED. Exposes the engram_ground_propagate native over HTTP so the
|
||||
// soul's consolidation beat can fire one grounded edge-propagation pass.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// [1] New handler — add beside route_strengthen (server.el ~line 194).
|
||||
// Mutation (appends grounding events, updates confidence), so it is gated
|
||||
// on _auth via check_auth_ok, exactly like /api/edges. Persists once after
|
||||
// the beat — the whole point of running propagation as one batched beat
|
||||
// rather than per-node is to pay the snapshot cost a single time.
|
||||
fn route_ground_propagate(method: String, path: String, body: String) -> String {
|
||||
if !check_auth_ok(method, body) { return err_json("unauthorized") }
|
||||
let tel: String = engram_ground_propagate() // native — one beat over the store
|
||||
let saved: Int = persist_canonical()
|
||||
return tel // gep_* telemetry JSON straight through
|
||||
}
|
||||
|
||||
// [2] Dispatch — register in handle_request (server.el ~line 461, next to the
|
||||
// /api/strengthen arm):
|
||||
//
|
||||
// if str_eq(method, "POST") && (str_eq(clean, "/api/ground/propagate")) {
|
||||
// return route_ground_propagate(method, clean, body)
|
||||
// }
|
||||
//
|
||||
// [3] Native declaration — engram_ground_propagate must be declared as an
|
||||
// extern runtime builtin (el_runtime.h) and seed-wrapped (el_seed.c /
|
||||
// el_seed.h __engram_ground_propagate) so the EL side can call it, same as
|
||||
// engram_strengthen / engram_hebb_drain_json.
|
||||
Reference in New Issue
Block a user