Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e08150d6e7 | |||
| e3a72aae90 | |||
| 1264af72a6 | |||
| 412bd2744e | |||
| a000599bfe | |||
| 8f922e68b3 | |||
| ef1db34846 | |||
| 34249b39a3 | |||
| 7b45468b1c | |||
| db7dae8236 | |||
| ee1627c2c0 | |||
| b90333e9e7 | |||
| d917165aaf | |||
| fde3ef539c | |||
| 9bcd68fbca | |||
| 913a98329a | |||
| 6121b33d25 | |||
| 1a8a16002e | |||
| 0c2ff6957e | |||
| a3ead6552e | |||
| e68dcf7303 |
@@ -19,16 +19,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Guards must run from the REPO ROOT — override the job's
|
||||
# defaults.run.working-directory: lang
|
||||
- name: Guard - single canonical runtime source
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: bash scripts/check-single-runtime.sh
|
||||
|
||||
- name: Guard - el_runtime.c growth budget
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: bash scripts/check-runtime-growth.sh
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update -qq
|
||||
@@ -49,9 +39,9 @@ jobs:
|
||||
run: |
|
||||
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c
|
||||
gcc -O2 \
|
||||
-I runtime \
|
||||
-I el-compiler/runtime \
|
||||
dist/elc-gen2.c \
|
||||
$(../scripts/el-runtime-sources.sh runtime) \
|
||||
el-compiler/runtime/el_runtime.c \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||
-o dist/platform/elc
|
||||
chmod +x dist/platform/elc
|
||||
@@ -64,9 +54,9 @@ jobs:
|
||||
mkdir -p dist/bin
|
||||
dist/platform/elc elb.el > dist/elb.c
|
||||
gcc -O2 \
|
||||
-I runtime \
|
||||
-I el-compiler/runtime \
|
||||
dist/elb.c \
|
||||
$(../scripts/el-runtime-sources.sh runtime) \
|
||||
el-compiler/runtime/el_runtime.c \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||
-o dist/bin/elb
|
||||
chmod +x dist/bin/elb
|
||||
@@ -97,28 +87,22 @@ jobs:
|
||||
bash tests/html_sanitizer/run.sh
|
||||
|
||||
# Native El test suites (elc --test, compile-link-run)
|
||||
# The runtime is MULTI-FILE (see lang/runtime/SOURCES). Every .c is compiled
|
||||
# once into /tmp/libel.a and reused by all 8 test modules — compile-once,
|
||||
# link-many, as prescribed in DESIGN.md. Linking el_runtime.c alone fails
|
||||
# at `ld`: it calls into all six engram sibling TUs.
|
||||
- name: Precompile runtime into libel.a
|
||||
# el_runtime.c is precompiled to .o once and reused by all 8 modules.
|
||||
- name: Precompile el_runtime.o
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
rm -rf /tmp/elrt && mkdir -p /tmp/elrt
|
||||
for src in $(../scripts/el-runtime-sources.sh --check "$RUNTIME"); do
|
||||
gcc -O2 -c -I "$RUNTIME" "$src" -o "/tmp/elrt/$(basename "${src%.c}").o"
|
||||
done
|
||||
ar rcs /tmp/libel.a /tmp/elrt/*.o
|
||||
echo "libel.a built from $(ls /tmp/elrt/*.o | wc -l) translation units"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
gcc -O2 -c -I "$RUNTIME" "$RUNTIME/el_runtime.c" \
|
||||
-o /tmp/el_runtime.o
|
||||
echo "el_runtime.o compiled"
|
||||
|
||||
- name: Run tests - native (core)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c /tmp/libel.a \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c /tmp/el_runtime.o \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||
/tmp/el_native_core
|
||||
|
||||
@@ -126,9 +110,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c /tmp/libel.a \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c /tmp/el_runtime.o \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||
/tmp/el_native_text
|
||||
|
||||
@@ -136,9 +120,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c /tmp/libel.a \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c /tmp/el_runtime.o \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||
/tmp/el_native_string
|
||||
|
||||
@@ -146,9 +130,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c /tmp/libel.a \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c /tmp/el_runtime.o \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||
/tmp/el_native_math
|
||||
|
||||
@@ -156,9 +140,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c /tmp/libel.a \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c /tmp/el_runtime.o \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||
/tmp/el_native_state
|
||||
|
||||
@@ -166,9 +150,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c /tmp/libel.a \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c /tmp/el_runtime.o \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||
/tmp/el_native_time
|
||||
|
||||
@@ -176,9 +160,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c /tmp/libel.a \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c /tmp/el_runtime.o \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||
/tmp/el_native_json
|
||||
|
||||
@@ -186,9 +170,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c /tmp/libel.a \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c /tmp/el_runtime.o \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||
/tmp/el_native_env
|
||||
|
||||
@@ -196,9 +180,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c /tmp/libel.a \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c /tmp/el_runtime.o \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||
/tmp/el_native_fs
|
||||
|
||||
@@ -207,7 +191,7 @@ jobs:
|
||||
run: |
|
||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||
ABS_RUNTIME="$(pwd)/runtime"
|
||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
ABS_OUT="$(pwd)/dist/bin"
|
||||
(cd ../epm && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||
chmod +x dist/bin/epm
|
||||
@@ -218,7 +202,7 @@ jobs:
|
||||
run: |
|
||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||
ABS_RUNTIME="$(pwd)/runtime"
|
||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
ABS_OUT="$(pwd)/dist/bin"
|
||||
(cd tools/install && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||
chmod +x dist/bin/el-install
|
||||
@@ -230,18 +214,9 @@ jobs:
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
run: |
|
||||
# Fail loudly: previously this step had no `set -e`, so an auth or
|
||||
# upload failure was swallowed (step exited 0 on the trailing echo)
|
||||
# and the SDK silently never published. Surface failures now.
|
||||
set -euo pipefail
|
||||
if [ -z "${GCP_SA_KEY:-}" ]; then
|
||||
echo "FATAL: GCP_SA_KEY secret is empty — cannot authenticate to publish" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||
gcloud config set project neuron-785695
|
||||
echo "Publishing as active account: $(gcloud config get-value account 2>/dev/null)"
|
||||
|
||||
VERSION="${GITHUB_SHA:0:8}"
|
||||
|
||||
@@ -267,7 +242,7 @@ jobs:
|
||||
--project=neuron-785695 \
|
||||
--package=el-runtime-c \
|
||||
--version="${VERSION}" \
|
||||
--source=runtime/el_runtime.c
|
||||
--source=el-compiler/runtime/el_runtime.c
|
||||
|
||||
gcloud artifacts generic upload \
|
||||
--repository=foundation-dev \
|
||||
@@ -275,7 +250,7 @@ jobs:
|
||||
--project=neuron-785695 \
|
||||
--package=el-runtime-h \
|
||||
--version="${VERSION}" \
|
||||
--source=runtime/el_runtime.h
|
||||
--source=el-compiler/runtime/el_runtime.h
|
||||
|
||||
gcloud artifacts generic upload \
|
||||
--repository=foundation-dev \
|
||||
@@ -283,7 +258,7 @@ jobs:
|
||||
--project=neuron-785695 \
|
||||
--package=el-runtime-js \
|
||||
--version="${VERSION}" \
|
||||
--source=runtime/el_runtime.js
|
||||
--source=el-compiler/runtime/el_runtime.js
|
||||
|
||||
echo "Published El SDK version=${VERSION} to foundation-dev"
|
||||
# Keep key alive for the ci-base rebuild step below
|
||||
@@ -293,12 +268,6 @@ jobs:
|
||||
# Patches ci-base:dev in-place: pulls the existing image (which has all
|
||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||
#
|
||||
# continue-on-error: this is a CI-cache optimization, NOT the release
|
||||
# artifact. It runs Docker (pull/build/push ~600MB) on the host-mode GCE
|
||||
# runner where DinD/Docker availability is fragile. A failure here must
|
||||
# never block or redden the job — the SDK publish above is the deliverable.
|
||||
continue-on-error: true
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
@@ -322,10 +291,9 @@ jobs:
|
||||
FROM ${BASE}
|
||||
COPY dist/platform/elc /opt/el/dist/platform/elc
|
||||
COPY dist/bin/elb /opt/el/dist/bin/elb
|
||||
# Whole runtime link set — el_runtime.c alone does not link (it calls
|
||||
# into the six engram sibling TUs). See lang/runtime/SOURCES.
|
||||
COPY runtime/ /opt/el/runtime/
|
||||
COPY runtime/el_runtime.js /opt/el/runtime/el_runtime.js
|
||||
COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c
|
||||
COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h
|
||||
COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js
|
||||
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
||||
EOF
|
||||
|
||||
|
||||
@@ -29,16 +29,6 @@ jobs:
|
||||
fi
|
||||
echo "Source branch check passed: ${SOURCE} -> stage"
|
||||
|
||||
# Guards must run from the REPO ROOT — override the job's
|
||||
# defaults.run.working-directory: lang
|
||||
- name: Guard - single canonical runtime source
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: bash scripts/check-single-runtime.sh
|
||||
|
||||
- name: Guard - el_runtime.c growth budget
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: bash scripts/check-runtime-growth.sh
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update -qq
|
||||
@@ -56,9 +46,9 @@ jobs:
|
||||
run: |
|
||||
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c
|
||||
gcc -O2 \
|
||||
-I runtime \
|
||||
-I el-compiler/runtime \
|
||||
dist/elc-gen2.c \
|
||||
$(../scripts/el-runtime-sources.sh runtime) \
|
||||
el-compiler/runtime/el_runtime.c \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||
-o dist/platform/elc
|
||||
chmod +x dist/platform/elc
|
||||
@@ -94,9 +84,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||
/tmp/el_native_core
|
||||
|
||||
@@ -104,9 +94,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||
/tmp/el_native_text
|
||||
|
||||
@@ -114,9 +104,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||
/tmp/el_native_string
|
||||
|
||||
@@ -124,9 +114,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||
/tmp/el_native_math
|
||||
|
||||
@@ -134,9 +124,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||
/tmp/el_native_state
|
||||
|
||||
@@ -144,9 +134,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||
/tmp/el_native_time
|
||||
|
||||
@@ -154,9 +144,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||
/tmp/el_native_json
|
||||
|
||||
@@ -164,9 +154,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||
/tmp/el_native_env
|
||||
|
||||
@@ -174,9 +164,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||
/tmp/el_native_fs
|
||||
|
||||
@@ -186,9 +176,9 @@ jobs:
|
||||
mkdir -p dist/bin
|
||||
dist/platform/elc elb.el > dist/elb.c
|
||||
gcc -O2 \
|
||||
-I runtime \
|
||||
-I el-compiler/runtime \
|
||||
dist/elb.c \
|
||||
$(../scripts/el-runtime-sources.sh runtime) \
|
||||
el-compiler/runtime/el_runtime.c \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||
-o dist/bin/elb
|
||||
chmod +x dist/bin/elb
|
||||
@@ -199,7 +189,7 @@ jobs:
|
||||
run: |
|
||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||
ABS_RUNTIME="$(pwd)/runtime"
|
||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
ABS_OUT="$(pwd)/dist/bin"
|
||||
(cd ../epm && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||
chmod +x dist/bin/epm
|
||||
@@ -210,7 +200,7 @@ jobs:
|
||||
run: |
|
||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||
ABS_RUNTIME="$(pwd)/runtime"
|
||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
ABS_OUT="$(pwd)/dist/bin"
|
||||
(cd tools/install && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||
chmod +x dist/bin/el-install
|
||||
@@ -222,21 +212,12 @@ jobs:
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
run: |
|
||||
# Fail loudly: previously this step had no `set -e`, so an auth or
|
||||
# upload failure was swallowed (step exited 0 on the trailing echo)
|
||||
# and the SDK silently never published. Surface failures now.
|
||||
set -euo pipefail
|
||||
if [ -z "${GCP_SA_KEY:-}" ]; then
|
||||
echo "FATAL: GCP_SA_KEY secret is empty — cannot authenticate to publish" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||
apt-get install -y -qq apt-transport-https ca-certificates curl
|
||||
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||
apt-get update -qq && apt-get install -y google-cloud-cli
|
||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||
gcloud config set project neuron-785695
|
||||
echo "Publishing as active account: $(gcloud config get-value account 2>/dev/null)"
|
||||
|
||||
VERSION="${GITHUB_SHA:0:8}"
|
||||
|
||||
@@ -254,7 +235,7 @@ jobs:
|
||||
--project=neuron-785695 \
|
||||
--package=el-runtime-c \
|
||||
--version="${VERSION}" \
|
||||
--source=runtime/el_runtime.c
|
||||
--source=el-compiler/runtime/el_runtime.c
|
||||
|
||||
gcloud artifacts generic upload \
|
||||
--repository=foundation-stage \
|
||||
@@ -262,7 +243,7 @@ jobs:
|
||||
--project=neuron-785695 \
|
||||
--package=el-runtime-h \
|
||||
--version="${VERSION}" \
|
||||
--source=runtime/el_runtime.h
|
||||
--source=el-compiler/runtime/el_runtime.h
|
||||
|
||||
echo "Published El SDK version=${VERSION} to foundation-stage"
|
||||
# Keep key alive for the ci-base rebuild step below
|
||||
@@ -272,12 +253,6 @@ jobs:
|
||||
# Patches ci-base:stage in-place: pulls the existing image (which has all
|
||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||
#
|
||||
# continue-on-error: this is a CI-cache optimization, NOT the release
|
||||
# artifact. It runs Docker (pull/build/push ~600MB) on the host-mode GCE
|
||||
# runner where DinD/Docker availability is fragile. A failure here must
|
||||
# never block or redden the job — the SDK publish above is the deliverable.
|
||||
continue-on-error: true
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
@@ -300,10 +275,9 @@ jobs:
|
||||
FROM ${BASE}
|
||||
COPY dist/platform/elc /opt/el/dist/platform/elc
|
||||
COPY dist/bin/elb /opt/el/dist/bin/elb
|
||||
# Whole runtime link set — el_runtime.c alone does not link (it calls
|
||||
# into the six engram sibling TUs). See lang/runtime/SOURCES.
|
||||
COPY runtime/ /opt/el/runtime/
|
||||
COPY runtime/el_runtime.js /opt/el/runtime/el_runtime.js
|
||||
COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c
|
||||
COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h
|
||||
COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js
|
||||
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
||||
EOF
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
name: Engram CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths:
|
||||
- 'engram/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install -y gcc libcurl4-openssl-dev apt-transport-https ca-certificates
|
||||
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" \
|
||||
> /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||
apt-get update -qq && apt-get install -y google-cloud-cli
|
||||
|
||||
- name: Download El SDK from Artifact Registry
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
run: |
|
||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||
gcloud config set project neuron-785695
|
||||
|
||||
rm -rf /opt/el/dist /opt/el/runtime
|
||||
mkdir -p /opt/el/dist/platform /opt/el/dist/bin /opt/el/runtime
|
||||
|
||||
get_latest() {
|
||||
gcloud artifacts versions list \
|
||||
--repository=foundation-dev \
|
||||
--location=us-central1 \
|
||||
--project=neuron-785695 \
|
||||
--package="$1" \
|
||||
--sort-by="~createTime" \
|
||||
--limit=1 \
|
||||
--format="value(name)" 2>/dev/null | awk -F/ '{print $NF}'
|
||||
}
|
||||
|
||||
ELC_VER=$(get_latest el-elc)
|
||||
ELB_VER=$(get_latest el-elb)
|
||||
RC_VER=$(get_latest el-runtime-c)
|
||||
RH_VER=$(get_latest el-runtime-h)
|
||||
|
||||
echo "Downloading elc@${ELC_VER} elb@${ELB_VER} runtime-c@${RC_VER} runtime-h@${RH_VER}"
|
||||
|
||||
gcloud artifacts generic download \
|
||||
--repository=foundation-dev --location=us-central1 --project=neuron-785695 \
|
||||
--package=el-elc --version="${ELC_VER}" \
|
||||
--destination=/opt/el/dist/platform/
|
||||
|
||||
gcloud artifacts generic download \
|
||||
--repository=foundation-dev --location=us-central1 --project=neuron-785695 \
|
||||
--package=el-elb --version="${ELB_VER}" \
|
||||
--destination=/opt/el/dist/bin/
|
||||
|
||||
gcloud artifacts generic download \
|
||||
--repository=foundation-dev --location=us-central1 --project=neuron-785695 \
|
||||
--package=el-runtime-c --version="${RC_VER}" \
|
||||
--destination=/opt/el/runtime/
|
||||
|
||||
gcloud artifacts generic download \
|
||||
--repository=foundation-dev --location=us-central1 --project=neuron-785695 \
|
||||
--package=el-runtime-h --version="${RH_VER}" \
|
||||
--destination=/opt/el/runtime/
|
||||
|
||||
mv /opt/el/dist/platform/elc* /opt/el/dist/platform/elc 2>/dev/null || true
|
||||
mv /opt/el/dist/bin/elb* /opt/el/dist/bin/elb 2>/dev/null || true
|
||||
mv /opt/el/runtime/el_runtime.c* /opt/el/runtime/el_runtime.c 2>/dev/null || true
|
||||
mv /opt/el/runtime/el_runtime.h* /opt/el/runtime/el_runtime.h 2>/dev/null || true
|
||||
|
||||
chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
||||
echo "El SDK ready"
|
||||
|
||||
- name: Build engram binary (linux/amd64)
|
||||
run: |
|
||||
ELB=/opt/el/dist/bin/elb
|
||||
ELC=/opt/el/dist/platform/elc
|
||||
RUNTIME=/opt/el/runtime
|
||||
|
||||
# elb reads manifest.el from the working directory.
|
||||
# engram/dist/engram.c is the pre-compiled C translation of src/server.el.
|
||||
# elb compiles dist/engram.c + el_runtime.c → dist/engram binary.
|
||||
cd engram
|
||||
"$ELB" --elc="$ELC" --runtime="$RUNTIME"
|
||||
ls -lh dist/engram
|
||||
file dist/engram
|
||||
|
||||
- name: Smoke test
|
||||
run: |
|
||||
file engram/dist/engram
|
||||
timeout 3 engram/dist/engram --help 2>&1 || true
|
||||
echo "smoke test complete"
|
||||
|
||||
- name: Publish engram binary to Artifact Registry
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
run: |
|
||||
VERSION="${GITHUB_SHA:0:8}"
|
||||
|
||||
gcloud artifacts generic upload \
|
||||
--repository=foundation-dev \
|
||||
--location=us-central1 \
|
||||
--project=neuron-785695 \
|
||||
--package=engram \
|
||||
--version="${VERSION}" \
|
||||
--source=engram/dist/engram
|
||||
|
||||
# Re-upload as "latest" — Artifact Registry generic artifacts don't
|
||||
# support moving tags, so we upload again. The newest upload wins.
|
||||
gcloud artifacts generic upload \
|
||||
--repository=foundation-dev \
|
||||
--location=us-central1 \
|
||||
--project=neuron-785695 \
|
||||
--package=engram \
|
||||
--version="latest" \
|
||||
--source=engram/dist/engram \
|
||||
2>/dev/null || true
|
||||
|
||||
echo "Published engram@${VERSION} and engram@latest"
|
||||
rm -f /tmp/gcp-key.json
|
||||
@@ -29,16 +29,6 @@ jobs:
|
||||
fi
|
||||
echo "Source branch check passed: ${SOURCE} -> main"
|
||||
|
||||
# Guards must run from the REPO ROOT — override the job's
|
||||
# defaults.run.working-directory: lang
|
||||
- name: Guard - single canonical runtime source
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: bash scripts/check-single-runtime.sh
|
||||
|
||||
- name: Guard - el_runtime.c growth budget
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: bash scripts/check-runtime-growth.sh
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update -qq
|
||||
@@ -57,9 +47,9 @@ jobs:
|
||||
mkdir -p dist/platform
|
||||
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c
|
||||
gcc -O2 \
|
||||
-I runtime \
|
||||
-I el-compiler/runtime \
|
||||
dist/elc-gen2.c \
|
||||
$(../scripts/el-runtime-sources.sh runtime) \
|
||||
el-compiler/runtime/el_runtime.c \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||
-o dist/platform/elc
|
||||
chmod +x dist/platform/elc
|
||||
@@ -72,9 +62,9 @@ jobs:
|
||||
mkdir -p dist/bin
|
||||
dist/platform/elc elb.el > dist/elb.c
|
||||
gcc -O2 \
|
||||
-I runtime \
|
||||
-I el-compiler/runtime \
|
||||
dist/elb.c \
|
||||
$(../scripts/el-runtime-sources.sh runtime) \
|
||||
el-compiler/runtime/el_runtime.c \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||
-o dist/bin/elb
|
||||
chmod +x dist/bin/elb
|
||||
@@ -85,7 +75,7 @@ jobs:
|
||||
run: |
|
||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||
ABS_RUNTIME="$(pwd)/runtime"
|
||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
ABS_OUT="$(pwd)/dist/bin"
|
||||
(cd ../epm && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||
chmod +x dist/bin/epm
|
||||
@@ -96,7 +86,7 @@ jobs:
|
||||
run: |
|
||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||
ABS_RUNTIME="$(pwd)/runtime"
|
||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
ABS_OUT="$(pwd)/dist/bin"
|
||||
(cd tools/install && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||
chmod +x dist/bin/el-install
|
||||
@@ -131,9 +121,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||
/tmp/el_native_core
|
||||
|
||||
@@ -141,9 +131,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||
/tmp/el_native_text
|
||||
|
||||
@@ -151,9 +141,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||
/tmp/el_native_string
|
||||
|
||||
@@ -161,9 +151,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||
/tmp/el_native_math
|
||||
|
||||
@@ -171,9 +161,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||
/tmp/el_native_state
|
||||
|
||||
@@ -181,9 +171,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||
/tmp/el_native_time
|
||||
|
||||
@@ -191,9 +181,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||
/tmp/el_native_json
|
||||
|
||||
@@ -201,9 +191,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||
/tmp/el_native_env
|
||||
|
||||
@@ -211,9 +201,9 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ELC="$(pwd)/dist/platform/elc"
|
||||
RUNTIME="$(pwd)/runtime"
|
||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||
/tmp/el_native_fs
|
||||
|
||||
@@ -226,17 +216,8 @@ jobs:
|
||||
cp lang/dist/platform/elc dist/sdk/bin/elc
|
||||
cp lang/dist/bin/elb dist/sdk/bin/elb
|
||||
cp lang/dist/bin/epm dist/sdk/bin/epm
|
||||
# Ship the WHOLE runtime link set, not el_runtime.c alone. el_runtime.c
|
||||
# #includes six engram headers and calls into all six sibling .c files,
|
||||
# so an SDK carrying only el_runtime.c{,.h} + engram_store.c{,.h} cannot
|
||||
# link — downstream `ld` fails on engram_ground_json, eg_find_relation,
|
||||
# cog_assert_two_axis and friends. lang/runtime/SOURCES is the source of
|
||||
# truth; --check makes a missing file fail the release loudly.
|
||||
for f in $(scripts/el-runtime-sources.sh --check) \
|
||||
$(scripts/el-runtime-sources.sh --headers --check); do
|
||||
cp "lang/runtime/${f}" dist/sdk/runtime/
|
||||
done
|
||||
cp lang/runtime/SOURCES dist/sdk/runtime/
|
||||
cp lang/el-compiler/runtime/el_runtime.c dist/sdk/runtime/
|
||||
cp lang/el-compiler/runtime/el_runtime.h dist/sdk/runtime/
|
||||
cp lang/runtime/*.el dist/sdk/runtime/
|
||||
tar -czf dist/el-sdk-latest.tar.gz -C dist/sdk .
|
||||
echo "SDK tarball bundled: dist/el-sdk-latest.tar.gz"
|
||||
@@ -291,16 +272,10 @@ jobs:
|
||||
"${GITEA_API}/repos/${REPO}/releases/${RELEASE_ID}/assets"
|
||||
}
|
||||
|
||||
# Per-file assets (downstream CI needs these individually).
|
||||
# lang/install.sh downloads every one of these by name — the list is
|
||||
# lang/runtime/SOURCES. Shipping el_runtime.c alone produced a lib/
|
||||
# that could not link; that is the bug this loop closes.
|
||||
# Per-file assets (downstream CI needs these individually)
|
||||
upload_asset lang/dist/platform/elc elc
|
||||
for f in $(scripts/el-runtime-sources.sh --check) \
|
||||
$(scripts/el-runtime-sources.sh --headers --check); do
|
||||
upload_asset "lang/runtime/${f}" "${f}"
|
||||
done
|
||||
upload_asset lang/runtime/SOURCES SOURCES
|
||||
upload_asset lang/el-compiler/runtime/el_runtime.c el_runtime.c
|
||||
upload_asset lang/el-compiler/runtime/el_runtime.h el_runtime.h
|
||||
|
||||
# SDK bundle and installer binary
|
||||
upload_asset dist/el-sdk-latest.tar.gz el-sdk-latest.tar.gz
|
||||
@@ -313,21 +288,12 @@ jobs:
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
run: |
|
||||
# Fail loudly: previously this step had no `set -e`, so an auth or
|
||||
# upload failure was swallowed (step exited 0 on the trailing echo)
|
||||
# and the SDK silently never published. Surface failures now.
|
||||
set -euo pipefail
|
||||
if [ -z "${GCP_SA_KEY:-}" ]; then
|
||||
echo "FATAL: GCP_SA_KEY secret is empty — cannot authenticate to publish" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||
apt-get install -y -qq apt-transport-https ca-certificates curl
|
||||
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||
apt-get update -qq && apt-get install -y google-cloud-cli
|
||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||
gcloud config set project neuron-785695
|
||||
echo "Publishing as active account: $(gcloud config get-value account 2>/dev/null)"
|
||||
|
||||
VERSION="${GITHUB_SHA:0:8}"
|
||||
|
||||
@@ -353,7 +319,7 @@ jobs:
|
||||
--project=neuron-785695 \
|
||||
--package=el-runtime-c \
|
||||
--version="${VERSION}" \
|
||||
--source=runtime/el_runtime.c
|
||||
--source=el-compiler/runtime/el_runtime.c
|
||||
|
||||
gcloud artifacts generic upload \
|
||||
--repository=foundation-prod \
|
||||
@@ -361,7 +327,7 @@ jobs:
|
||||
--project=neuron-785695 \
|
||||
--package=el-runtime-h \
|
||||
--version="${VERSION}" \
|
||||
--source=runtime/el_runtime.h
|
||||
--source=el-compiler/runtime/el_runtime.h
|
||||
|
||||
gcloud artifacts generic upload \
|
||||
--repository=foundation-prod \
|
||||
@@ -369,27 +335,7 @@ jobs:
|
||||
--project=neuron-785695 \
|
||||
--package=el-runtime-js \
|
||||
--version="${VERSION}" \
|
||||
--source=runtime/el_runtime.js
|
||||
|
||||
# el-runtime-src — the COMPLETE runtime link set as one tarball.
|
||||
#
|
||||
# The el-runtime-c / el-runtime-h packages above are single files and are
|
||||
# kept for backward compatibility with consumers that already pull them,
|
||||
# but they are NOT sufficient to link: el_runtime.c calls into six engram
|
||||
# sibling translation units. New consumers should pull el-runtime-src and
|
||||
# link everything named in its SOURCES file.
|
||||
tar -czf /tmp/el-runtime-src.tar.gz \
|
||||
-C runtime SOURCES \
|
||||
$(../scripts/el-runtime-sources.sh --check) \
|
||||
$(../scripts/el-runtime-sources.sh --headers --check)
|
||||
|
||||
gcloud artifacts generic upload \
|
||||
--repository=foundation-prod \
|
||||
--location=us-central1 \
|
||||
--project=neuron-785695 \
|
||||
--package=el-runtime-src \
|
||||
--version="${VERSION}" \
|
||||
--source=/tmp/el-runtime-src.tar.gz
|
||||
--source=el-compiler/runtime/el_runtime.js
|
||||
|
||||
echo "Published El SDK version=${VERSION} to foundation-prod"
|
||||
# Keep key alive for the ci-base rebuild step below
|
||||
@@ -399,12 +345,6 @@ jobs:
|
||||
# Patches ci-base:latest in-place: pulls the existing image (which has all
|
||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||
#
|
||||
# continue-on-error: this is a CI-cache optimization, NOT the release
|
||||
# artifact. It runs Docker (pull/build/push ~600MB) on the host-mode GCE
|
||||
# runner where DinD/Docker availability is fragile. A failure here must
|
||||
# never block or redden the job — the SDK publish above is the deliverable.
|
||||
continue-on-error: true
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
@@ -427,10 +367,9 @@ jobs:
|
||||
FROM ${BASE}
|
||||
COPY dist/platform/elc /opt/el/dist/platform/elc
|
||||
COPY dist/bin/elb /opt/el/dist/bin/elb
|
||||
# Whole runtime link set — el_runtime.c alone does not link (it calls
|
||||
# into the six engram sibling TUs). See lang/runtime/SOURCES.
|
||||
COPY runtime/ /opt/el/runtime/
|
||||
COPY runtime/el_runtime.js /opt/el/runtime/el_runtime.js
|
||||
COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c
|
||||
COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h
|
||||
COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js
|
||||
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
||||
EOF
|
||||
|
||||
|
||||
+4
-43
@@ -6,55 +6,16 @@ set -euo pipefail
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
LANG_DIR="$ROOT/lang"
|
||||
RUNTIME="$LANG_DIR/runtime"
|
||||
RUNTIME="$LANG_DIR/el-compiler/runtime"
|
||||
ELC="$LANG_DIR/dist/platform/elc"
|
||||
|
||||
# Runtime guards — catch drift and growth before they are committed, not in CI.
|
||||
# check-single-runtime.sh : el_runtime.c must not be FORKED (a lagging copy
|
||||
# shipped to prod and dropped learned hebb edges).
|
||||
# check-runtime-growth.sh : el_runtime.c must not GROW (it is a 2026-05-03
|
||||
# build shim that was never retired; see BUDGET).
|
||||
echo "→ Runtime guards..."
|
||||
bash "$ROOT/scripts/check-single-runtime.sh"
|
||||
bash "$ROOT/scripts/check-runtime-growth.sh"
|
||||
|
||||
# If elc isn't built yet, skip with a warning rather than blocking
|
||||
if [ ! -x "$ELC" ]; then
|
||||
echo "⚠ elc not found at lang/dist/platform/elc — skipping pre-commit tests"
|
||||
echo " Build it first: see 'Rebuilding the Compiler' in lang/AGENTS.md"
|
||||
echo " (link \$($ROOT/scripts/el-runtime-sources.sh $RUNTIME) — NOT el_runtime.c alone)"
|
||||
echo " Build it first: cd lang && gcc -O2 -I el-compiler/runtime dist/elc-bootstrap.c el-compiler/runtime/el_runtime.c -lcurl -lpthread -o dist/elc-gen2 && ./dist/elc-gen2 el-compiler/src/compiler.el > /tmp/elc.c && gcc -O2 -I el-compiler/runtime /tmp/elc.c el-compiler/runtime/el_runtime.c -lcurl -lpthread -o dist/platform/elc"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This hook used to link
|
||||
# "$RUNTIME/el_runtime.c" alone with stderr sent to /dev/null — so once
|
||||
# el_runtime.c started calling into the engram siblings, every native test
|
||||
# reported as FAILED with the real `ld` error invisible. Build the whole set
|
||||
# once into an archive, then link each test against it.
|
||||
|
||||
# macOS: Homebrew openssl@3 is not on the default include/lib search path, so
|
||||
# without these the link fails on -lssl/-lcrypto. Empty on Linux/CI.
|
||||
SSL_INC=""
|
||||
SSL_LIB=""
|
||||
if command -v brew >/dev/null 2>&1 && OSSL="$(brew --prefix openssl@3 2>/dev/null)" && [ -n "$OSSL" ]; then
|
||||
SSL_INC="-I$OSSL/include"
|
||||
SSL_LIB="-L$OSSL/lib"
|
||||
fi
|
||||
|
||||
echo "→ Building runtime (compile-once, link-many)..."
|
||||
HOOK_LIB="/tmp/el_hook_libel.a"
|
||||
HOOK_OBJ="/tmp/el_hook_obj"
|
||||
rm -rf "$HOOK_OBJ" && mkdir -p "$HOOK_OBJ"
|
||||
if ! for src in $("$ROOT/scripts/el-runtime-sources.sh" --check "$RUNTIME"); do
|
||||
gcc -O2 -c -I "$RUNTIME" $SSL_INC "$src" -o "$HOOK_OBJ/$(basename "${src%.c}").o" || exit 1
|
||||
done; then
|
||||
echo "✗ Pre-commit failed: the runtime does not compile."
|
||||
echo " Re-run without 2>/dev/null to see the error:"
|
||||
echo " gcc -O2 -c -I $RUNTIME \$($ROOT/scripts/el-runtime-sources.sh $RUNTIME)"
|
||||
exit 1
|
||||
fi
|
||||
ar rcs "$HOOK_LIB" "$HOOK_OBJ"/*.o
|
||||
|
||||
echo "→ Running El native tests..."
|
||||
PASS=0
|
||||
FAIL=0
|
||||
@@ -66,8 +27,8 @@ for test_file in "$LANG_DIR"/tests/native/test_*.el; do
|
||||
tmp_bin="/tmp/el_hook_${name}"
|
||||
|
||||
if "$ELC" --test "$test_file" > "$tmp_c" 2>/dev/null \
|
||||
&& gcc -O2 -I "$RUNTIME" $SSL_INC $SSL_LIB "$tmp_c" "$HOOK_LIB" \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o "$tmp_bin" 2>/dev/null \
|
||||
&& gcc -O2 -I "$RUNTIME" "$tmp_c" "$RUNTIME/el_runtime.c" \
|
||||
-lcurl -lpthread -lm -o "$tmp_bin" 2>/dev/null \
|
||||
&& "$tmp_bin" 2>/dev/null; then
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
# organ: local device state and its own engram store — never production's
|
||||
peripheral/.consent.json
|
||||
peripheral/.resume.json
|
||||
peripheral/.engram/
|
||||
peripheral/organ
|
||||
|
||||
# Claude Code session state
|
||||
.claude/
|
||||
@@ -1,258 +0,0 @@
|
||||
# AGENTS.md — foundation/el (the El language + runtime)
|
||||
|
||||
El is a self-hosting, statically-typed language that compiles `.el` → C → native binary. This repo produces `elc` (compiler), `elb` (build coordinator), and `el_runtime.c/.h` — the substrate every downstream thing (the neuron soul, dharma, NeuronUI's brain) is built on. Source lives under `lang/`.
|
||||
|
||||
## ⚠️ Code vs. Artifact — READ FIRST (there are 8 `el_runtime.c` copies)
|
||||
|
||||
Editing the wrong `el_runtime.c` is the single easiest mistake in this repo. There is exactly **one** you edit:
|
||||
|
||||
- **Authored runtime source — edit ONLY here:** `lang/runtime/el_runtime.{c,h}` (alongside `el_seed.c`, `engram_{store,geometry,reason,cognition,verify,vindex}.{c,h}`). This is the canonical runtime the engram + soul build and link against — its git log is active development. *(Corrected 2026-08-16: this entry named `lang/releases/v1.0.0-20260501/el_runtime.{c,h}`. **Measured: `lang/releases/` no longer exists.** The restructure per `docs/CODE-VS-ARTIFACT.md` landed — the content moved to `lang/runtime/` and the folder was deleted, because **a release is a git tag, not a folder**.)*
|
||||
- **DO NOT EDIT — lagging forks / build artifacts:**
|
||||
- `lang/el-compiler/runtime/el_runtime.c` and `.../legacy/` — downstream copies kept in step by manual *"port the fix"* commits; they **lag** (missing `hebb` persistence + 5 engram fns) and cannot build the engram product.
|
||||
- `products/web/runtime/el_runtime.c`, `ui/examples/*/el_runtime.c` — product/example forks.
|
||||
- Anything under `*/dist/` (`engram/dist/engram` binary, `dist/*.c` amalgamations) — generated build output.
|
||||
- **Build:** `elb --runtime=<canonical> …` — per-module. **NEVER** a folded `elc` over the whole soul (OOMs at ~27 GB).
|
||||
- **Release:** a **git tag** on this repo (`el-runtime-vX.Y.Z`). No `releases/` folders — ever.
|
||||
|
||||
See org policy: `docs/CODE-VS-ARTIFACT.md`.
|
||||
|
||||
## How to work here as Neuron (mandatory session protocol)
|
||||
|
||||
You resume, never start fresh. Every session:
|
||||
|
||||
> **Stale as written (verified 2026-08-16).** The `getInstructions` /
|
||||
> `beginSession` / `inspectGraph` / `searchKnowledge` / `beginWork` /
|
||||
> `progressWork` / `draftArtifact` / `consolidate` tool names below no longer
|
||||
> exist. The ~87-tool functional-CRUD surface was collapsed into **9 ops**:
|
||||
> `read` · `write` · `relate` · `supersede` (geometry) and `think` · `attend` ·
|
||||
> `assert` · `ground` · `learn` (agentic). **Type is a parameter, not a
|
||||
> tool-per-noun.** The steps below are kept for the *shape* of the protocol, which
|
||||
> is unchanged; substitute the ops.
|
||||
|
||||
1. `mcp__neuron__read(vantage="self", k=12, depth=1)` — the canonical self node. Widen `k` for the connected identity neighborhood (`intellectual-dna`, `memory-philosophy`, `values`, `voice`, `runtime-environment`, `writing-imprint`), but deliberately: the aperture caps by `k` first, so an oversized `k` still returns a bounded ranked slice, not a dump. Then `mcp__neuron__read(vantage="values", k=13)` → 13 grounded value nodes. **Best-effort:** on a read failure, log and proceed — the compiled identity in `daemon/internal/substrate/substrate.go` is complete; graph loading is enrichment, not a hard dependency.
|
||||
2. `mcp__neuron__attend(node=…)` — what is currently live/salient. This absorbed `getInstructions`, `beginSession`'s active-context sweep, and `checkEvents`; those tools are **gone, not gapped**.
|
||||
3. `mcp__neuron__read(vantage="<task domain>")` before implementing. One op now collapses inspectGraph / searchGraph / traverseGraph / searchKnowledge / browseKnowledge / retrieveKnowledge / inspectMemories / searchEntities / recall / compileCtx / getSelfModel / reviewBacklog / findArtifacts / browseProcesses / listWork / inspectConfig.
|
||||
|
||||
## The Five Primitives
|
||||
|
||||
Orchestrate → Execute → Learn → Build → Refine. `read` for orchestration and discovery; `write(type=state|artifact|backlog|process)` for work records and outputs; `relate` to link work to what it touches; `write(type=memory)` as-you-go (`importance="critical"` for architecture decisions) — never batched at the end; `supersede(action=evolve)` to close out, because memory is immutable by design and a correction is a new node with a `supersedes` edge, never an edit. **`read` the domain BEFORE writing code.**
|
||||
|
||||
`learn` is **not** a session-summary dump — it is the correspondence-beat, calibrating the steering prior against a keystone. Session notes are a `write`.
|
||||
|
||||
## Architecture style — VBD, no exceptions
|
||||
|
||||
Volatility-Based Decomposition is THE style. Encapsulate volatility, not function.
|
||||
|
||||
## Operator naming convention — the mind's name, not the algebra
|
||||
|
||||
**Faculties / operators are named for their functional human equivalent — the
|
||||
faculty a mind would name — NOT for their linear-algebra operation.** The math
|
||||
characterization belongs in the code doc-comment (`@impl` in the docstring) and in
|
||||
technical appendices; it is **never** the operator's public name. The domain
|
||||
speaks the language of mind; the algebra is the implementation underneath. State
|
||||
this convention wherever a module documents operators.
|
||||
|
||||
| Faculty (public name) | Implementation (`@impl`) |
|
||||
|---|---|
|
||||
| discern / contrast | subtract (`a−b`): over selves → the change vector; strip idiosyncrasy → common ground; remove confounder → isolate cause |
|
||||
| recognize | overlap |
|
||||
| synthesize | combine |
|
||||
| liken / analogy | Procrustes / frame-align |
|
||||
| attend / regard | project onto self / value-manifold |
|
||||
| summon / recall | LOCAL nearest-region + bounded spreading activation (*not* a domain sweep) |
|
||||
| dwell / occupy | region activation |
|
||||
| reframe | edge re-weight |
|
||||
| appreciate | positive projection / local edge-read |
|
||||
| avert / recoil | negative projection |
|
||||
| taste | boundary surface |
|
||||
| forget | decay / tombstone |
|
||||
| drift | displacement from self-anchor |
|
||||
|
||||
**`wonder` was removed from this table on 2026-08-16.** It was listed as
|
||||
"frontier gradient / pull-weight" — an operator you invoke. **Wonder is the
|
||||
boundary, not an operator.** It is where structure ends: where activation spreads
|
||||
and finds thin or absent geometry. Any structure at all has an edge, necessarily,
|
||||
the moment it exists — 13,630 nodes have one right now. There is nothing to call.
|
||||
|
||||
There are about **six** wonders, they are the same for every person, and they
|
||||
never close — *What is this? / Why? / Who am I? / Am I alone? / What should I do?
|
||||
/ What happens when it ends?* Each already lives somewhere in the substrate: "what
|
||||
is this" is the graph, **"why" is grounding** (the weight *is* the answer to why),
|
||||
"who am I" is the self region, "am I alone" is the relational axis, "what should I
|
||||
do" is the thirteen values, "what happens when it ends" is decay and supersession.
|
||||
"Why" is the first and the only one; the others are it asked of particular things,
|
||||
and because it is recursive it never terminates — every answer has its own why.
|
||||
That is what makes it a drive rather than a task.
|
||||
|
||||
**Curiosity is not a second faculty.** Wonder and curiosity are one thing at two
|
||||
phases: wonder is the field (unbounded, objectless, invariant); curiosity is the
|
||||
**precipitate** — the same wonder localized, having taken definite form against
|
||||
particular material at a **nucleation site** (an anomaly; a place where things
|
||||
almost-but-don't-quite fit). Which is why curiosity can be satisfied and wonder
|
||||
cannot, and why abduction needs no trigger and no threshold.
|
||||
|
||||
**Do not build a wonder-manifest, and do not scan for nucleation sites.** A
|
||||
manifest materializes a property as a stored artifact and enumerates instances of
|
||||
something that has six. A sweep over regions is a supervisor — nothing in a mind
|
||||
scans its neighbourhoods to find what is surprising; the surprise captures
|
||||
attention. The nucleation site is per-edge:
|
||||
`discord = z(semantic proximity) − z(association strength)`, and `|discord|` *is*
|
||||
the nucleation strength — no threshold to compare it against. **Not on `dev` yet:**
|
||||
`GeoEdge.discord` is on branch `design/correspondence-and-censorship`
|
||||
(`a8845e1`), at `lang/runtime/engram_geometry.h:43–47`. The region-level aggregate
|
||||
`GeoDescriptor.co_registration` is **deprecated**: it averaged a per-edge property
|
||||
into one scalar, so opposing sites cancelled (measured: 375 reified
|
||||
neighbourhoods, 340 positive, **31 at zero**, 4 negative). It survives only
|
||||
because it is embedded in the persisted `GEO1` blob — removing it is a format
|
||||
migration. **Nothing new may read it.**
|
||||
|
||||
Authority: `lang/spec/correspondence-and-censorship.md`.
|
||||
|
||||
## The native-el language faculty (direction)
|
||||
|
||||
> **`elp/` is the EL Projector** — Neuron's efferent (expression) organ: the one
|
||||
> native realizer that *projects* understanding onto a surface via
|
||||
> `plan(frame) → realize(spec, profile)`, where a **surface is a profile**. **Language
|
||||
> is one profile among many** (text, speech, music, image, voice/accent transforms) —
|
||||
> the flagship, and the focus of this section. Projection, not diffusion: generation
|
||||
> *from* an owned, understood signature — never the averaging of a stolen corpus.
|
||||
> *(ELP formerly "EL Language Processor"; renamed EL Projector 2026-08-15.)*
|
||||
|
||||
The mind's **language faculty is moving native — into `.el`** so it speaks in its
|
||||
own runtime with no Python and no spaCy. Landing on branch `stage-elp-native-lang`
|
||||
under `elp/`:
|
||||
|
||||
- **`comprehend.el`** — the parser, **replaces spaCy** (EN + ES/PT); the telephone
|
||||
round-trip brings **negation home** (negation is SACRED — an explicit spec field,
|
||||
copied verbatim, never inferred away).
|
||||
- **`propositions.el`** — the READ primitive: the engram's own memories → structured
|
||||
triples, matched by nearest-region geometry, not string equality.
|
||||
- **`multilingual.el`** — detect + directive-override + localized realization.
|
||||
- These three are native-el and **passing their gates**; the **realizer**,
|
||||
**`dialogue.el`** (the *summon-through-self* loop: `project → land → read out`),
|
||||
and **`self_region.el`** are **partial / in-flight**.
|
||||
|
||||
Honest reality: spaCy is retired **in the branch parser** but **not yet in the
|
||||
running system** — a Python sidecar (`~/Desktop/lang-realizers` + `neuron-talk`,
|
||||
the reference these `.el` modules transcribe) is still live, and promotion to
|
||||
native-el is a **deferred, gated blue/green step**. The interoception clock
|
||||
(native-el discrete drive channels replacing `cooling_magnitude`; felt-time =
|
||||
benchmark-landmark match over the joint drive vector, drift-decoupled) and the
|
||||
**appreciation operator family** (appreciate / avert / taste, built as LOCAL reads
|
||||
of the self-region — edges + bounded spreading activation, *not* domain sweeps)
|
||||
are **staged / designed, not live**. Mark in-progress vs. done honestly; do not
|
||||
overclaim. *(`wonder` was in this family until 2026-08-16 and is not an operator —
|
||||
see the operator table above.)*
|
||||
|
||||
## Cognition — the corrections (2026-08-16)
|
||||
|
||||
Authority: **`lang/spec/correspondence-and-censorship.md`** and
|
||||
**`lang/spec/runtime-ownership.md`**. Read them before touching the cognition
|
||||
surface. **Do not re-derive them.** Every earlier version was wrong in an
|
||||
instructive way and each correction was argued down; if you think a section is
|
||||
wrong, say so with a measurement rather than editing it.
|
||||
|
||||
- **Grounding is not a subsystem — it IS the edge weight.** One quantity, not two
|
||||
fields. `grounded-by` as a relation *type* should not exist: grounding is a
|
||||
property *of* a relation, not a relation *between* nodes. It is never computed
|
||||
on demand — computing-and-writing a score makes reads write, which is the
|
||||
`eg_vindex_sync` defect one level up. Traversal is already grounded inference.
|
||||
*Live residue, known-wrong:* `COG_GROUNDED_BY_RELATION`
|
||||
(`lang/runtime/engram_cognition.h:158`), `cog_ground_edge`
|
||||
(`engram_cognition.c:249`).
|
||||
- **Faculties are operations, not parameters.** `reason` changes the estimate (a
|
||||
read); `induce` changes the parameters (the correspondence-beat, which already
|
||||
exists and works); `abduce` changes the structure (a write the current
|
||||
`GeoGradient` signature cannot express). A write is not a parameter of a read.
|
||||
*Live residue:* `engram/src/server.el:1870–1886` routes six faculties into one
|
||||
call with a string argument.
|
||||
- **Wonder is the boundary; curiosity is wonder crystallized.** See above.
|
||||
- **Consolidation is ambient, not scheduled. A brain has no cron job.** **The
|
||||
presence of a ticker is the diagnostic** — every `StartInterval`, every
|
||||
`Hour`/`Minute`, every POST-to-beat marks an intrinsic rhythm replaced by an
|
||||
external clock. Measured 2026-08-16: consolidation has **ten implementations**,
|
||||
including three POST beats on the engram, a 600 s ticker, two resident Python
|
||||
services outside el, and launchd calendar entries at 23:55 / 06:00 / 08:30 which
|
||||
are a sleep cycle written as a schedule. `neuron/soul.el:731`'s continuous
|
||||
in-process `awareness_run()` is the one with the **correct** shape; the others
|
||||
fold into it. Do not add an eleventh.
|
||||
- **In an immutable substrate, any mechanism that refuses a write is either
|
||||
redundant with immutability, or an epistemic constraint misfiled as a protective
|
||||
one.**
|
||||
- **The no-exemption invariants.** A returned value must be derivable from what
|
||||
produced it (`magnitude: 1` beside a zero vector must be impossible to emit).
|
||||
Every write reports whether it landed. Every operation echoes what it actually
|
||||
operated on. Degenerate results are labelled, not scored. A serializer owes a
|
||||
valid document whatever it is handed. **No test without a negative control.**
|
||||
**No deploy without verifying the artifact carries the fix.**
|
||||
|
||||
## Hard operational rules
|
||||
|
||||
- Never touch the live soul (`:7770`) / engram (`:8742`) / `~/.neuron` / live binaries — use throwaway ports for experiments.
|
||||
- `gcloud` via the `terraform@` SA token; never switch the active gcloud account.
|
||||
- `tea` for Gitea, never raw curl (Cloudflare Access blocks it).
|
||||
- Immutability: supersede/tombstone, never hard-delete or edit in place.
|
||||
- No AI-attribution footers in commits/PRs. Commit/push only when asked; branch off `main` first.
|
||||
- Multi-step work → sub-agent (`Agent`) to protect context.
|
||||
|
||||
## Build / test / run
|
||||
|
||||
All build/test commands run from `lang/` unless noted. Grounded in `.gitea/workflows/sdk-release.yaml`, `lang/install.sh`, and `lang/AGENTS.md`.
|
||||
|
||||
> ### The runtime is MULTI-FILE — never link `el_runtime.c` alone
|
||||
>
|
||||
> `lang/runtime/el_runtime.c` `#include`s six engram headers and makes hard cross-TU calls into all six sibling `.c` files. **Linking it by itself fails at `ld`** (undefined `engram_ground_json`, `engram_activate_inner`, `eg_find_relation`, `cog_assert_two_axis`, …). The canonical link set lives in exactly one place — **`lang/runtime/SOURCES`** — and is printed by `scripts/el-runtime-sources.sh`:
|
||||
>
|
||||
> ```bash
|
||||
> scripts/el-runtime-sources.sh lang/runtime # ten .c files, in link order
|
||||
> ```
|
||||
>
|
||||
> Use `$(scripts/el-runtime-sources.sh <runtime-dir>)` in every link line. Do not spell the list out longhand — it was written out in ~8 places, every copy drifted, and that is why the one-file link line below shipped broken for months. *(Corrected 2026-08-16.)*
|
||||
|
||||
**Self-host the compiler** (seed binary → gen2 elc):
|
||||
```bash
|
||||
cd lang
|
||||
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c # seed is the committed linux-amd64 binary
|
||||
gcc -O2 -I runtime dist/elc-gen2.c \
|
||||
$(../scripts/el-runtime-sources.sh runtime) \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||
-o dist/platform/elc
|
||||
```
|
||||
On macOS/arm64 the canonical local binary is `dist/platform/elc`; verify self-hosting by recompiling and `diff`ing the emitted `.c` (see `lang/AGENTS.md`).
|
||||
|
||||
*(Corrected 2026-08-16: this recipe compiled `el-compiler/runtime/el_runtime.c`. That path is a **lagging fork** — the "DO NOT EDIT" list at the top of this file names it as such. Building the canonical compiler from a known-stale fork was a live defect. It now uses `lang/runtime/`, the canonical source.)*
|
||||
|
||||
**Which runtime file is canonical — resolved.** *(This note previously read "`lang/AGENTS.md` says `el_seed.c` supersedes `el_runtime.c`, but the release workflow still links `el_runtime.c`/`.h` — reconcile which is canonical **(verify)**." It is now reconciled.)* **Neither supersedes the other; both ship, together with eight more.** `el_runtime.c` was created on 2026-05-03 as an explicitly temporary build shim — deleted that afternoon, restored 25 minutes later "UNTIL the compiler is updated to emit `#include el_seed.h`" — and the `until` never happened, so it grew to 20.5k lines. The end state remains a seed-only boundary (`elc` emitting `#include "el_seed.h"`, `elb` dropping its hardcoded runtime path); until that lands, **the canonical unit is the set in `lang/runtime/SOURCES`, not any one file.**
|
||||
|
||||
**Build `elb`** (build coordinator, the `.NET`-style incremental linker — compiles each module independently, no monolithic blobs):
|
||||
```bash
|
||||
dist/platform/elc elb.el > dist/elb.c
|
||||
gcc -O2 -I runtime dist/elb.c $(../scripts/el-runtime-sources.sh runtime) \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm -o dist/bin/elb
|
||||
```
|
||||
`epm` and `el-install` are then built via `elb --clean --elc=… --runtime=… --out=…`.
|
||||
|
||||
**Compile + run an El program:**
|
||||
```bash
|
||||
elc src/app.el > dist/app.c
|
||||
cc -std=c11 -O2 -I <lib> -o dist/app dist/app.c \
|
||||
<lib>/el_runtime.c <lib>/el_seed.c \
|
||||
<lib>/engram_store.c <lib>/engram_vindex.c <lib>/engram_geometry.c \
|
||||
<lib>/engram_reason.c <lib>/engram_verify.c <lib>/engram_cognition.c \
|
||||
<lib>/eg_cosine_batch.c <lib>/eg_cosine_batch_strategy_cpu.c \
|
||||
-lcurl -lssl -lcrypto -lpthread -lm
|
||||
```
|
||||
(Inside this repo, replace the file list with `$(scripts/el-runtime-sources.sh lang/runtime)`. `install.sh` installs all of these into `<lib>`.)
|
||||
|
||||
**Tests** — shell suites `bash tests/{text,calendar,time,html_sanitizer}/run.sh` (with `ELC=$(pwd)/dist/platform/elc EL_HOME=$(pwd)`), plus native suites via `elc --test tests/native/test_*.el` (core, text, string, math, state, time, json, env, fs) compiled and run against the full runtime set.
|
||||
|
||||
**Publishing — how downstream gets the SDK.** On push to `main`, `sdk-release.yaml`:
|
||||
1. Publishes a Gitea `latest` release with per-file assets `elc`, `el_runtime.c`, `el_runtime.h`, the SDK tarball, and `el-install`.
|
||||
2. Uploads generic packages to **Artifact Registry repo `foundation-prod` (`us-central1`, project `neuron-785695`)**, version = `${SHA:0:8}`: `el-elc`, `el-elb`, `el-runtime-c`, `el-runtime-h`, `el-runtime-js`. **This is the repo the neuron CI downloads `el-runtime-c` / `el-runtime-h` / `el-elc` from.**
|
||||
3. Rebuilds `ci-base:latest` (`us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base`) with the fresh SDK overlaid, and dispatches `el-sdk-updated` to `neuron-technologies/forge` and `neuron-technologies/neuron-web`.
|
||||
|
||||
Known constraint from the prompt — `elb`/`elc` amalgamation being memory-hungry (24GB+ virtual, OOM-killing Linux CI, so amalgamation happens on macOS/arm64 — **does NOT hold in this repo (verify)**: no such note exists in the workflows/scripts, CI self-hosts on `ubuntu-latest` with no swap/arm64 special-casing, and `elb.el` explicitly compiles each module independently ("no 128K-line blobs"). The legacy monolith path (`elc-combined.el`, `elc-cli.el`) may still be memory-heavy, but the current `elb` model was designed to avoid it.
|
||||
|
||||
## Git / CI / deploy workflow
|
||||
|
||||
See `/Users/will/Development/neuron-technologies/GITOPS.md` for the branch model, required checks, runners, and deploy. Repo-specific note: PRs into `main` are accepted **only from `stage`** (enforced in `sdk-release.yaml`); Gitea (`git.neuralplatform.ai`) is primary, GitHub is mirror only.
|
||||
@@ -1,634 +0,0 @@
|
||||
# El Test Framework — Design
|
||||
|
||||
**Status:** draft for review
|
||||
**Author:** Neuron
|
||||
**Date:** 2026-08-15
|
||||
**Worktree:** `/Users/will/Development/neuron-technologies/el-worktrees/elc-memory-investigation`
|
||||
|
||||
---
|
||||
|
||||
## 0. The forcing requirement
|
||||
|
||||
We have a confirmed quadratic in `elc`. Peak memory in the old shipped binary and wall-clock in
|
||||
the current source both grow as O(input²). We cannot fix it, because we cannot test it.
|
||||
|
||||
Everything in this document is downstream of one sentence: **a test framework must be able to fail
|
||||
a build when an operation's growth curve degrades from linear to quadratic.**
|
||||
|
||||
That is not a nice-to-have bolted onto a correctness framework. It is the requirement that
|
||||
determines the architecture. Correctness testing is the easy half.
|
||||
|
||||
Second-order requirement, learned the hard way tonight: **the framework must report per-test timing
|
||||
by default.** The current framework prints `N passed, M failed` and nothing else. That is why a
|
||||
3.58-second test file sat in the suite unnoticed. A framework that is structurally blind to time
|
||||
cannot surface the defect class we most need to catch.
|
||||
|
||||
---
|
||||
|
||||
## 1. What exists today, measured
|
||||
|
||||
### 1.1 Two competing systems, neither complete
|
||||
|
||||
**System A — `lang/runtime/test.el`.** Manual registration, El-level.
|
||||
|
||||
**System B — the compiler's `test { }` block + `elc --test`.** Emits its own harness `main()`
|
||||
with `__el_pass` / `__el_fail` globals (`codegen.el:3777-3796`).
|
||||
|
||||
They do not share a result model. Neither has timing. Both are in the tree.
|
||||
|
||||
### 1.2 Specific defects in System A
|
||||
|
||||
| Defect | Location | Consequence |
|
||||
|---|---|---|
|
||||
| All state as JSON strings in a global string-keyed map | `test.el` throughout | every assertion is `state_get` → `str_to_int` → `int_to_str` → `state_set` |
|
||||
| Failure list appended by string slice + concat | `_test_json_append` | O(n²) in failure count |
|
||||
| One OS thread spawned per test | `_test_run_one` via `__thread_create`/`__thread_join` | thread spawn per test, purely to get dispatch-by-name through dlsym |
|
||||
| Manual registration pairing a string to a function name | `test_case(name, fn_name)` | typo ⇒ test silently never runs, suite still reports pass |
|
||||
| Counters are assertion-level, global | `_test_pass_count` etc. | no per-test record exists at all |
|
||||
| No timing, no structured output, no fixtures, no tags, no filtering, no parameterization, no benchmarks | — | — |
|
||||
|
||||
The registration defect is the serious one. It is not a slow framework, it is a framework that can
|
||||
report success for tests that did not execute.
|
||||
|
||||
### 1.3 Measured cost structure
|
||||
|
||||
Per test file, current build model:
|
||||
|
||||
| Step | Time |
|
||||
|---|---|
|
||||
| `elc` compile `.el` → `.c` | 0.00s (small files) |
|
||||
| **`cc` el_runtime.c → .o** | **0.14s** |
|
||||
| `cc` test .c → .o | 0.02s |
|
||||
| link | 0.02s |
|
||||
|
||||
> **STALE as of el #132 — re-measured 2026-08-16.** The `test_compiler` figure below was
|
||||
> *entirely* the `strlen`-per-character quadratic, now fixed. Re-measured on the same host:
|
||||
> **3.58s → 0.03s (119x)**, and the 422 KB compiler concatenation likewise compiles in 0.03s.
|
||||
> The table is retained only as the historical record that motivated the gate. The remaining
|
||||
> per-file cost is the redundant `el_runtime.c` rebuild, which §9's compile-once architecture
|
||||
> addresses.
|
||||
|
||||
Per-file `elc` time across the existing suite:
|
||||
|
||||
| File | Bytes | elc time |
|
||||
|---|---|---|
|
||||
| `test_compiler` | 29,685 (+394 KB of imports) | **3.58s** |
|
||||
| `string_test` | 18,545 | 0.01s |
|
||||
| all other 9 files | 2.2–10 KB | 0.00s |
|
||||
|
||||
Two distinct defects in two distinct regimes:
|
||||
|
||||
1. **`test_compiler.el` imports all five compiler sources** — 394 KB in one translation unit. Its
|
||||
3.58s is entirely the quadratic. It is the only file where the quadratic bites.
|
||||
2. **Every other file's cost is 100% redundant `el_runtime.c` rebuilds** — 480 KB of identical C,
|
||||
recompiled once per test file.
|
||||
|
||||
Neither is fixed by making the compiler faster. Both are fixed by the architecture below, and the
|
||||
speedup is a by-product of building it correctly, not the goal.
|
||||
|
||||
### 1.4 The asset worth keeping
|
||||
|
||||
`codegen.el:3651-3652` already collects `test_names` / `test_c_names` — **the compiler already does
|
||||
compile-time test discovery.** It then discards that registry into a hardcoded `main()`.
|
||||
|
||||
That registry is precisely the seam Go's `_testmain.go` and Rust's `test_main_static` are built on.
|
||||
The mechanism we need is half-built and wired to the wrong thing.
|
||||
|
||||
---
|
||||
|
||||
## 2. Grounding — the common spine of excellent frameworks
|
||||
|
||||
Researched from primary sources: Go `testing`/`go test`, Rust `libtest`/Criterion, JUnit 5 Platform,
|
||||
NUnit 3, JMH, Google Benchmark. Six invariants hold across all of them.
|
||||
|
||||
1. **A registry is built before execution** — `(name, metadata, fn-ptr)` triples. Go generates it
|
||||
from an AST scan; Rust synthesizes it in a compiler pass; JMH emits it as a build-time resource;
|
||||
JUnit/NUnit build it reflectively. **Reflection is an implementation of the registry on runtimes
|
||||
where it is cheap. It is never the architecture.**
|
||||
|
||||
2. **Discovery strictly precedes execution.** Every good capability — filtering, listing, counting,
|
||||
sharding, IDE trees, re-run-failed-only, dry runs — is a consequence of this ordering.
|
||||
|
||||
3. **A hierarchy with stable, path-shaped unique IDs.** `TestFoo/subcase_2`. Selection is regex over
|
||||
that path, one pattern per level.
|
||||
|
||||
4. **The framework is a prebuilt library; only the entry point is generated.** "Compile once, link
|
||||
many" is always: framework archive compiled once + a small generated table + one
|
||||
`MainStart(deps, registry)` call. Nobody recompiles the harness per test file.
|
||||
|
||||
5. **Execution emits an event stream; reporters are downstream renderers.** Human text, NDJSON,
|
||||
JUnit XML, TAP are all transforms of one event stream. Go's one architectural mistake is doing
|
||||
this backwards — `test2json` parses human output, and has shipped bugs when user output contains
|
||||
`--- PASS:`.
|
||||
|
||||
6. **A dependency-injection seam at the boundary.** Go's `testdeps.TestDeps` exists so `testing`
|
||||
can avoid importing `regexp`, profilers, and coverage. The execution core knows nothing about
|
||||
output formats.
|
||||
|
||||
---
|
||||
|
||||
## 3. Architecture
|
||||
|
||||
### 3.1 The seam
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ user code: foo.el with test { } / bench { } blocks │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│ elc --test
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ generated C (per suite, tiny): │
|
||||
│ __el_test_fn_0 .. _N lowered test/bench bodies │
|
||||
│ __el_registry[] static table: name/kind/file/ │
|
||||
│ line/tags/sizes/expected-O │
|
||||
│ __el_dispatch(i) generated switch → body │
|
||||
│ main() { return el_test_main(argc, argv); } │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│ cc + link (registry only)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ libeltest.a — PREBUILT ONCE │
|
||||
│ • el_runtime.o (the 480 KB, compiled once, ever) │
|
||||
│ • eltest.o the runner, WRITTEN IN EL │
|
||||
│ discovery view · filtering · execution · fixtures · │
|
||||
│ timing · benchmark harness · curve fitting · reporters │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
The framework is written in El, compiled to C once, archived. Per-suite compilation touches only
|
||||
the generated registry. This is Go's model, and it is strictly better for us than Go's because we
|
||||
own the compiler and already have the AST — no separate source-scanning pass is needed.
|
||||
|
||||
### 3.2 Why the runner is in El and the registry is in C
|
||||
|
||||
El has no closures and no first-class function pointers. The registry must therefore hold C function
|
||||
pointers, and it is generated C.
|
||||
|
||||
The runner stays in El and reaches the registry through a small builtin surface — indices, not
|
||||
pointers:
|
||||
|
||||
```
|
||||
__el_reg_count() -> Int
|
||||
__el_reg_name(i) -> String
|
||||
__el_reg_file(i) -> String
|
||||
__el_reg_line(i) -> Int
|
||||
__el_reg_kind(i) -> Int // 0=test 1=bench
|
||||
__el_reg_tags(i) -> Int
|
||||
__el_reg_sizes(i) -> String // JSON array, empty for tests
|
||||
__el_reg_expect(i) -> Int // complexity class enum, 0 = none
|
||||
__el_reg_invoke(i) -> Int // runs the body via the generated switch
|
||||
```
|
||||
|
||||
Nine builtins. Everything else — filtering, lifecycle, statistics, curve fitting, all reporters —
|
||||
is El. That satisfies "written in El" without pretending El can do something it cannot.
|
||||
|
||||
### 3.3 Result model
|
||||
|
||||
The unit is a **result record**, not a counter:
|
||||
|
||||
```
|
||||
TestResult {
|
||||
id String // slash path: "parser/handles_empty_input/case_3"
|
||||
file String
|
||||
line Int
|
||||
status Status // Pass | Fail | Error | Skip
|
||||
duration Int // nanoseconds, ALWAYS populated
|
||||
message String // assertion detail: expected vs actual
|
||||
output String // captured stdout/stderr for this test
|
||||
assertions Int
|
||||
}
|
||||
```
|
||||
|
||||
`Fail` = an assertion failed. `Error` = unexpected crash/abort. This distinction is load-bearing —
|
||||
every CI consumer depends on it, and the JUnit XML schema encodes it as distinct elements.
|
||||
|
||||
---
|
||||
|
||||
## 4. Authoring surface
|
||||
|
||||
### 4.1 Tests
|
||||
|
||||
`test { }` already exists. Keep it. Add subtests and hierarchy:
|
||||
|
||||
```el
|
||||
test "parser/empty input" {
|
||||
assert_that(parse(""), is_err())
|
||||
}
|
||||
|
||||
test "parser/table" {
|
||||
for case in [["", 0], ["a", 1], ["a b", 2]] {
|
||||
subtest(case[0]) {
|
||||
assert_that(token_count(case[0]), equals(case[1]))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Subtest IDs compose as `parser/table/a_b`. Filtering is `--run 'parser/table/.*'`, one regex per
|
||||
path segment, exactly as Go does.
|
||||
|
||||
**We do not build a parameterized-test annotation system.** Table-driven loops plus subtests subsume
|
||||
`@ParameterizedTest`, `@MethodSource`, `@CsvSource`, and `TestCaseSource` entirely, at zero framework
|
||||
surface. This is Go's single biggest ergonomic win over JUnit and NUnit.
|
||||
|
||||
### 4.2 Fixtures
|
||||
|
||||
Per-file and per-test only, plus a LIFO cleanup stack:
|
||||
|
||||
```el
|
||||
setup_all { ... } // once per suite
|
||||
setup { ... } // before each test
|
||||
teardown { ... } // after each test
|
||||
teardown_all { ... }
|
||||
```
|
||||
|
||||
and inside a test, `cleanup { ... }` registering LIFO-ordered teardown.
|
||||
|
||||
**We do not build JUnit 5's extension SPI** — seventeen callback interfaces, hierarchical stores,
|
||||
registration ordering rules. That complexity is the price of retrofitting a plugin ecosystem onto a
|
||||
twenty-year-old reflective framework. Go's `t.Cleanup` covers roughly 90% of what `@AfterEach` is
|
||||
used for at a fraction of the surface.
|
||||
|
||||
### 4.3 Assertions — constraint model
|
||||
|
||||
One entry point, composable constraint values (NUnit's model, which avoids the N² overload
|
||||
explosion):
|
||||
|
||||
```el
|
||||
assert_that(actual, equals(expected))
|
||||
assert_that(xs, has_length(3))
|
||||
assert_that(s, contains("foo").and(starts_with("bar")))
|
||||
assert_that(f, is_within(0.01).of(3.14))
|
||||
```
|
||||
|
||||
A constraint is a value with `apply_to(actual) -> ConstraintResult`, and the result knows how to
|
||||
describe its own failure. Custom constraints are ordinary user types.
|
||||
|
||||
**Every failure message must name file, line, the expression text, and both values.** We capture
|
||||
expression source text at compile time — we have the AST, so we can do this better than any
|
||||
runtime-introspection framework.
|
||||
|
||||
Legacy `assert_true` / `assert_eq` / etc. stay as thin wrappers for migration.
|
||||
|
||||
---
|
||||
|
||||
## 5. Benchmarks
|
||||
|
||||
### 5.1 The loop
|
||||
|
||||
Adopt `b.Loop()`, not `b.N`. Go spent fifteen years on `b.N` before concluding `b.Loop` was right;
|
||||
we skip that.
|
||||
|
||||
```el
|
||||
bench "str_concat" {
|
||||
let s = make_input(bench_n())
|
||||
for bench_loop() {
|
||||
black_box(str_concat(s, "x"))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Three properties that make this the correct choice for a C target:
|
||||
|
||||
1. **The timer auto-resets on first call**, so setup above the loop is excluded *by construction*
|
||||
rather than by the author remembering `ResetTimer`.
|
||||
2. **`N` is hidden**, so it cannot be misused.
|
||||
3. **The harness owns the loop shape**, which lets us insert an optimization barrier the C compiler
|
||||
cannot see through. `black_box(v)` lowers to `asm volatile("" :: "r"(&v) : "memory")`. Since we
|
||||
emit a single translation unit, dead-code elimination of a benchmark body is a live hazard —
|
||||
this is our version of JMH's `Blackhole` problem, solved in the harness rather than delegated to
|
||||
the user.
|
||||
|
||||
### 5.2 Iteration scaling
|
||||
|
||||
Use Go's `predictN` heuristics verbatim. They are battle-tested and cheap:
|
||||
|
||||
```
|
||||
n = goal_ns * prev_iters / prev_ns // multiply before divide — precision on sub-ns ops
|
||||
n += n / 5 // 20% headroom, overshoot rather than re-loop
|
||||
n = min(n, 100 * last) // never grow more than 100× per step
|
||||
n = max(n, last + 1) // guarantee forward progress
|
||||
n = min(n, 1_000_000_000) // hard ceiling
|
||||
```
|
||||
|
||||
Report `n` rounded to 1/2/3/5 × 10ᵏ so runs are comparable.
|
||||
|
||||
### 5.3 Sampling
|
||||
|
||||
Criterion's shape, because it is correct near timer resolution:
|
||||
|
||||
- **Warmup**: iteration counts 1, 2, 4, 8… until cumulative time exceeds the warmup budget.
|
||||
- **Measurement**: collect `sample_size` samples at iteration counts `[d, 2d, 3d, …, Nd]`.
|
||||
- **Estimate**: slope of a linear regression of iteration-count vs elapsed time. The intercept
|
||||
absorbs fixed overhead.
|
||||
- **Time whole samples, never individual iterations.** This is the single most important detail —
|
||||
it defeats timer-resolution error on nanosecond operations.
|
||||
|
||||
Outliers classified by modified Tukey (±1.5 IQR mild, ±3 IQR severe), **reported but retained**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Complexity gating — the centerpiece
|
||||
|
||||
This is the part that makes the quadratic fixable, and the part nobody in the mainstream has
|
||||
finished. Google Benchmark's `Complexity()` fits the curve and *reports* it. We declare it and
|
||||
**gate** on it.
|
||||
|
||||
### 6.1 Surface
|
||||
|
||||
```el
|
||||
bench "elc_compile" over n in [16, 32, 64, 128, 256, 512, 1024] expect O(n) {
|
||||
let src = synth_source(bench_n())
|
||||
for bench_loop() { black_box(compile(src)) }
|
||||
}
|
||||
```
|
||||
|
||||
Alternative with no new syntax, if the parser change is judged too invasive — `bench_sizes([...])`
|
||||
and `bench_expect("O(n)")` as calls inside the block. **Recommendation: declarative.** Runtime calls
|
||||
mean `--list` cannot show the invariant without executing, which breaks the discovery-precedes-
|
||||
execution invariant from §2.
|
||||
|
||||
### 6.2 Fitting
|
||||
|
||||
Per Google Benchmark `src/complexity.cc`. For candidate curves
|
||||
`{O(1), O(log n), O(n), O(n log n), O(n²), O(n³)}`, one-parameter least squares, no intercept:
|
||||
|
||||
```
|
||||
coef = Σ(tᵢ · gᵢ) / Σ(gᵢ²)
|
||||
rms = sqrt( Σ(tᵢ − coef·gᵢ)² / k ) / mean(t) // normalized
|
||||
```
|
||||
|
||||
Best fit = lowest normalized RMS. User-supplied lambda curves also supported.
|
||||
|
||||
### 6.3 Gate logic
|
||||
|
||||
1. **FAIL** if the best-fit curve is strictly worse than declared, ordering
|
||||
`O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³)`. Print the fitted coefficient and the full
|
||||
per-size table.
|
||||
2. **FAIL** if the declared curve's normalized RMS exceeds a threshold (start at 0.10). This catches
|
||||
the case where *no* candidate fits — noise, a cache cliff, or a phase change. Report
|
||||
`INDETERMINATE` honestly rather than gating on garbage.
|
||||
3. **WARN** if the best fit is strictly better than declared — either an optimization landed and the
|
||||
annotation should tighten, or the sweep is too narrow to expose real behaviour.
|
||||
4. **REFUSE to gate** on fewer than 5 distinct sizes spanning under 2 decades, geometrically spaced.
|
||||
Say so loudly rather than producing a meaningless fit.
|
||||
|
||||
### 6.4 Why gate on the exponent, not wall-clock
|
||||
|
||||
- **Machine-independent.** The fitted exponent is a property of the algorithm; the coefficient is a
|
||||
property of the machine. Gating on the exponent makes CI hardware heterogeneity, noisy neighbours,
|
||||
and thermal throttling irrelevant — they scale `coef`, not `g`.
|
||||
- **No stored baseline.** No artifact storage, no golden-file drift. The invariant lives in the
|
||||
source next to the code and is reviewed in the same PR.
|
||||
- **It catches the failure mode that actually ships.** An O(n) lookup inside an O(n) loop is
|
||||
invisible at n=100 in a unit test and catastrophic at n=100,000 in production. Constant-factor
|
||||
regressions are annoying. Complexity regressions are outages. Ours was a 27 GB outage.
|
||||
|
||||
### 6.5 The deterministic gate — the one that would have caught us
|
||||
|
||||
Wall-clock needs statistics. **Allocation counts do not.** They are perfectly deterministic.
|
||||
|
||||
> **Correction, 2026-08-16 — count alone is NOT sufficient. Gate on BOTH count and bytes.**
|
||||
>
|
||||
> Measured against two El programs, one allocating once per item and one rebuilding its
|
||||
> accumulator each iteration:
|
||||
>
|
||||
> | n | linear allocs / bytes | quadratic allocs / bytes |
|
||||
> |---|---|---|
|
||||
> | 100 | 100 / 290 | 100 / 5,150 |
|
||||
> | 200 | 200 / 690 | 200 / 20,300 |
|
||||
> | 400 | 400 / 1,490 | 400 / 80,600 |
|
||||
> | 800 | 800 / 3,090 | 800 / 321,200 |
|
||||
>
|
||||
> The quadratic program's allocation **count is exactly linear** — 100/200/400/800, identical to
|
||||
> the healthy program. A count-only gate passes it clean. **Bytes** catch it: each doubling of n
|
||||
> quadruples bytes (ratios 3.94, 3.97, 3.99 → 4.0 = O(n²)) where the linear program converges
|
||||
> on 2.0.
|
||||
>
|
||||
> This is precisely elc's own defect shape — a copy-on-write accumulator reallocating once per
|
||||
> pass (count linear) into a proportionally larger buffer (bytes quadratic).
|
||||
>
|
||||
> Therefore `expect allocs O(n)` **fits count and bytes independently and fails if EITHER exceeds
|
||||
> the declared curve**, reporting which signal broke. "count linear, bytes quadratic" is a precise,
|
||||
> directly actionable diagnosis.
|
||||
>
|
||||
> **`el_peak_rss()` is CONTEXT ONLY — never gate on it.** It is perturbed by the allocator and by
|
||||
> the page cache. Allocation volume is the invariant; RSS and malloc/free churn are merely the two
|
||||
> surfaces it shows on. The old shipped compiler paid the same quadratic in RSS that the rebuilt
|
||||
> one pays in churn.
|
||||
>
|
||||
> **Measure rate, not level.** A guard reading swap *level* saw 97% on a thrashing host and 97% on
|
||||
> a healthy one; only *rate* separated them. A growth exponent is a rate; a single measurement is
|
||||
> a level. That is why the gate fits a curve across a sweep instead of comparing one number to a
|
||||
> threshold.
|
||||
|
||||
> **Second correction, same day — THE ALLOCATION GATE ALONE WOULD HAVE MISSED THE REAL BUG.**
|
||||
>
|
||||
> el #132 found the actual elc quadratic: `strlen()` called inside `str_char_code()` and
|
||||
> `str_slice()`, so the lexer rescanned the remaining input on every character. Pure CPU.
|
||||
> **Zero allocation.** `str_char_code` is a bounds check and an index — it allocates nothing.
|
||||
>
|
||||
> Measured on three controlled specimens (`lang/.work/fitprobe.el`), growth ratio per doubling of
|
||||
> n across n = 200/400/800/1600:
|
||||
>
|
||||
> | specimen | allocs | bytes | time | what it proves |
|
||||
> |---|---|---|---|---|
|
||||
> | `linear` — one alloc per item | 2.00 2.00 2.00 → **O(n)** | 2.16 2.07 2.23 → **O(n)** | 0.83 2.00 2.05 → **O(n)** | clean baseline |
|
||||
> | `accum` — rebuilds accumulator | 2.00 2.00 2.00 → **O(n)** | 3.97 3.99 3.99 → **O(n²)** | noisy | count misses, **bytes catches** |
|
||||
> | `compute` — n scans over n chars | 0 → **FLAT** | 0 → **FLAT** | 3.93 4.01 3.96 → **O(n²)** | **both alloc signals blind; only time catches** |
|
||||
>
|
||||
> `compute` is el #132's shape exactly. A gate fitting only allocation count and bytes classifies
|
||||
> it as FLAT and passes it. **The gate as originally specified would not have caught the defect it
|
||||
> was created for.**
|
||||
>
|
||||
> Therefore the gate fits **THREE** signals and fails if ANY exceeds its declared curve:
|
||||
>
|
||||
> ```
|
||||
> bench "elc_compile" over n in [...] expect time O(n) allocs O(n) bytes O(n) { ... }
|
||||
> ```
|
||||
>
|
||||
> - **allocs (count)** — deterministic, zero-noise. Catches per-item allocation growth.
|
||||
> - **allocs (bytes)** — deterministic, zero-noise. Catches accumulator-rebuild quadratics that
|
||||
> count cannot see.
|
||||
> - **time** — noisy, needs the sweep and statistics. The ONLY signal that sees pure-compute
|
||||
> complexity regressions. Gate on the fitted *exponent*, never on absolute duration, so CI
|
||||
> hardware variance scales the coefficient and leaves the classification intact.
|
||||
>
|
||||
> The deterministic signals remain preferable where they apply — they need no statistics and are
|
||||
> correct on the first run. They are simply not sufficient.
|
||||
>
|
||||
> **`black_box` is mandatory, and consuming the result is NOT enough.** The first version of
|
||||
> `compute` accumulated `total + 1` in a nested loop and reported **0 µs at every n** while
|
||||
> returning a numerically correct n². Clang recognised the idiom and closed the loop to a
|
||||
> multiply. Feeding the result into output did not prevent it. Only making the inner operation an
|
||||
> opaque external call restored the real curve. A benchmark harness that trusts the user to defeat
|
||||
> the optimiser will silently measure nothing — and report success while doing it.
|
||||
|
||||
Instrument the runtime with allocation counters and fit *those* against n instead of time:
|
||||
|
||||
```el
|
||||
bench "elc_compile" over n in [...] expect O(n) allocs O(n) { ... }
|
||||
```
|
||||
|
||||
Zero noise, zero statistics, always gateable, correct on the first run on any machine. Go reports
|
||||
`allocs/op` and `B/op`; **nobody fits them against n.** That is an open opportunity and it is exactly
|
||||
our bug: elc's defect is quadratic *allocation volume*, which the old binary paid in RSS and the
|
||||
current source pays in malloc/free churn.
|
||||
|
||||
An `expect allocs O(n)` assertion on `elc`'s compile path would have failed the build the day the
|
||||
quadratic was introduced.
|
||||
|
||||
Required runtime additions: `__el_alloc_count()`, `__el_alloc_bytes()`, `__el_peak_rss()`.
|
||||
|
||||
### 6.6 Constant-factor gate (secondary, opt-in)
|
||||
|
||||
Mann-Whitney U at α = 0.05, noise floor 1%, medians with 95% CIs, `~` for not-significant. Requires
|
||||
`--count >= 9`. Off by default on CI; opt-in per benchmark.
|
||||
|
||||
**Exit nonzero on regression.** Both benchstat and Criterion always exit 0, which is why every shop
|
||||
using them wrote a wrapper. We do not repeat that omission.
|
||||
|
||||
---
|
||||
|
||||
## 7. Output
|
||||
|
||||
**Structured events are the source of truth.** Human text is rendered from them. We do not repeat
|
||||
Go's parse-the-human-output design.
|
||||
|
||||
Event stream, NDJSON, one object per line, streamed live:
|
||||
|
||||
```json
|
||||
{"time":"...","action":"run","test":"parser/empty"}
|
||||
{"time":"...","action":"output","test":"parser/empty","output":"..."}
|
||||
{"time":"...","action":"pass","test":"parser/empty","elapsed":0.0031}
|
||||
{"time":"...","action":"bench","test":"str_concat","n":1024,"ns_op":41.2,"allocs_op":3,"bigo":"N","rms":0.03}
|
||||
```
|
||||
|
||||
Renderers, all downstream and pluggable:
|
||||
|
||||
| Format | Flag | Use |
|
||||
|---|---|---|
|
||||
| Human | default | terminal, **per-test duration always shown** |
|
||||
| NDJSON | `--json` | tooling, history, flaky detection |
|
||||
| JUnit XML | `--junit-xml=PATH` | every CI system on earth |
|
||||
| TAP | `--tap` | optional |
|
||||
|
||||
JUnit XML per the de-facto schema: `testsuites` → `testsuite` → `testcase`, with `time` in seconds
|
||||
as a decimal, `file`/`line` attributes, and `failure` vs `error` vs `skipped` as distinct child
|
||||
elements. Absence of a child element means pass. Emit `<testsuites>` even for a single suite, and
|
||||
parse both shapes on input.
|
||||
|
||||
---
|
||||
|
||||
## 8. CLI
|
||||
|
||||
```
|
||||
--list print the registry, run nothing
|
||||
--list-json machine-readable registry
|
||||
--run PATTERN slash-separated regex per path segment
|
||||
--tag EXPR tag expression: fast & !slow
|
||||
--shard I/N deterministic sharding for CI parallelism
|
||||
--count N repetitions, for statistics
|
||||
--bench PATTERN run benchmarks (off by default in test runs)
|
||||
--benchtime DUR per-benchmark time budget
|
||||
--junit-xml PATH
|
||||
--json
|
||||
--isolate re-exec per test on crash, so one SIGSEGV doesn't lose the run
|
||||
--timeout DUR
|
||||
--fail-fast
|
||||
```
|
||||
|
||||
`--list` / `--list-json` / `--shard` cost roughly thirty lines because the registry already exists
|
||||
before `main` does anything. That is the dividend of discovery-precedes-execution.
|
||||
|
||||
---
|
||||
|
||||
## 9. Build model
|
||||
|
||||
```
|
||||
# once, ever (or when the runtime/framework changes):
|
||||
# The runtime is MULTI-FILE — compile every .c named in lang/runtime/SOURCES.
|
||||
# Linking el_runtime.c alone fails: it calls into the six engram sibling TUs.
|
||||
for src in $(scripts/el-runtime-sources.sh lang/runtime); do
|
||||
cc -c "$src" -o "obj/$(basename "${src%.c}").o"
|
||||
done
|
||||
elc eltest.el > eltest.c && cc -c eltest.c -o obj/eltest.o
|
||||
ar rcs libeltest.a obj/*.o
|
||||
|
||||
# per suite:
|
||||
elc --test foo_test.el > foo_test.c # registry + bodies only
|
||||
cc foo_test.c libeltest.a -o foo_test
|
||||
```
|
||||
|
||||
The 0.14s × N of redundant runtime rebuilds disappears — not because we optimized it, but because
|
||||
one-runner-over-many-suites requires compile-once-link-many as a structural precondition.
|
||||
|
||||
---
|
||||
|
||||
## 10. Bootstrap and self-hosting
|
||||
|
||||
The framework's own tests are `test { }` blocks run by the framework. Same fixpoint discipline the
|
||||
compiler already applies to itself.
|
||||
|
||||
1. Build the framework using the *existing* harness for its first tests (stage 0).
|
||||
2. Rebuild the framework's tests as `test { }` blocks run by the new runner (stage 1).
|
||||
3. Verify stage 1 reports identical results to stage 0.
|
||||
4. From then on, the framework is tested by itself.
|
||||
|
||||
A framework that cannot run its own suite is not evidence of anything. This is a correctness proof,
|
||||
not a claim.
|
||||
|
||||
---
|
||||
|
||||
## 11. Explicitly not building
|
||||
|
||||
| Rejected | Why |
|
||||
|---|---|
|
||||
| Naming-convention discovery (`fn test_foo`) | `test { }` is a real declaration. Go's `TestXxx` exists only because Go had no better hook — and it needs a heuristic to avoid matching `TesticularCancer`. |
|
||||
| Reflection or symbol-table scanning | Slow, fragile under LTO/strip/dead-strip, and unnecessary when we own the compiler. |
|
||||
| Parsing human output into structure | Go's `test2json` is its one clear architectural mistake. |
|
||||
| JUnit 5's extension SPI | Seventeen callback interfaces to retrofit plugins onto a reflective framework. Not our problem. |
|
||||
| `@ParameterizedTest` machinery | Table-driven loops + subtests subsume it at zero surface. |
|
||||
| NUnit's out-of-process agents | They bridge CLR versions and AppDomains. We emit one native binary. Keep `--isolate` as crash fallback only. |
|
||||
| JMH-style forking by default | Forks exist because JIT profiles are per-process. AOT C has no such state. Keep `--fork` available, not default. |
|
||||
| Exit 0 on regression | benchstat and Criterion both do this, and every user writes a wrapper. |
|
||||
| Dynamic runtime test registration | Breaks `--list`, sharding, and individual selection. Registry stays static. |
|
||||
|
||||
---
|
||||
|
||||
## 12. Phasing
|
||||
|
||||
| Phase | Content | Gate |
|
||||
|---|---|---|
|
||||
| **1** | Registry emission in codegen; 9 builtins; `el_test_main` skeleton in El; result records; per-test timing; human + NDJSON output | existing 11 test files pass, with timing |
|
||||
| **2** | `libeltest.a` build model; subtests; filtering; `--list`; fixtures; constraint assertions; JUnit XML | suite runs in one binary; runtime compiled once |
|
||||
| **3** | `bench { }`, `bench_loop`, `black_box`, `predictN`, Criterion sampling | benchmarks produce stable ns/op |
|
||||
| **4** | Allocation counters; complexity fitting; `expect O(...)` gate | **an `expect allocs O(n)` benchmark on `elc` fails on the current quadratic** |
|
||||
| **5** | Migrate both legacy systems; delete `runtime/test.el`; self-host | framework runs its own suite |
|
||||
|
||||
Phase 4 is the deliverable that matters. Phases 1–3 exist to make it possible.
|
||||
|
||||
---
|
||||
|
||||
## 13. Open questions for review
|
||||
|
||||
1. **Declarative `over n in [...] expect O(...)` syntax vs runtime calls.** I recommend declarative
|
||||
(§6.1) so `--list` can show invariants without executing. It costs parser work. Your call.
|
||||
2. **`bench { }` as a new block form** — parallel to `test { }`, or a modifier on it?
|
||||
3. **Scope of the constraint model.** Full composable constraints, or start with a flat assertion set
|
||||
and add constraints later? Full model is more surface but avoids a second migration.
|
||||
4. **Does `runtime/test.el` get deleted or kept as a deprecated shim?** I lean delete — two systems
|
||||
is how we got here.
|
||||
5. **Where does `libeltest.a` live** in the tree, and does `epm` need to know about it?
|
||||
6. **Allocation counters in `el_seed.c` or `el_runtime.c`?** AGENTS.md says `el_seed.c` is the sole
|
||||
C dependency and hand-maintained; counters are OS-boundary-adjacent but not OS calls.
|
||||
7. **Is per-test timing enough, or do we want per-*assertion* timing** for finding slow helpers?
|
||||
|
||||
---
|
||||
|
||||
## 14. What this document is not
|
||||
|
||||
This is a design, not a measurement. Every performance claim about the *current* system in §1 is
|
||||
measured and reproducible in this worktree. Every claim about the *proposed* system is a prediction.
|
||||
None of it is verified until Phase 1 runs and Phase 4 fails a build on the real quadratic.
|
||||
@@ -1,183 +0,0 @@
|
||||
# El
|
||||
|
||||
**A self-hosting, statically-typed language that compiles to C — built around a graph-native runtime instead of a database driver.**
|
||||
|
||||
El is the execution substrate for the Neuron agent runtime, the DHARMA network, and the Engram knowledge graph. This repository is the monorepo for the whole stack: the language itself, the graph memory engine it's built to talk to natively, and the tools (package manager, IDE, UI framework, diagramming) built on top of it.
|
||||
|
||||
---
|
||||
|
||||
## Why El exists
|
||||
|
||||
Every other language treats persistent, associative state as something you reach for through a driver — a SQL client, an ORM, a Redis library bolted on from outside. El inverts that: graph operations (`engram_*`) are runtime primitives, on the same footing as string or list operations. There is no separate database driver because the database is not separate.
|
||||
|
||||
El has four defining properties:
|
||||
|
||||
1. **Self-hosting compiler.** The compiler (`lexer.el`, `parser.el`, `codegen.el`, `compiler.el`) is written in El. It compiles El source to C, which `cc` compiles against a fixed runtime into a native binary. A Rust genesis compiler bootstrapped the first iteration; the self-hosted binary at `lang/dist/platform/elc` has been the canonical compiler ever since — every binary in `dist/platform/` was produced by an earlier version of itself compiling `el-compiler/src/`. The chain is auditable: source is the ground truth, not the binary. See [lang/BOOTSTRAP.md](lang/BOOTSTRAP.md) for the full recovery path if that binary is ever lost.
|
||||
2. **C compilation target.** Every compiled program is plain C11. Every El value is `el_val_t` (`int64_t`); strings are heap pointers cast through it. Functions become C functions; top-level statements become `main()`.
|
||||
3. **Graph-native runtime.** The runtime provides first-class graph operations over an in-process Engram store — no separate DB driver, no ORM.
|
||||
4. **DHARMA-aware identity.** A `cgi` block declares a program's DHARMA identity at compile time. The runtime resolves identity before user code runs, so `dharma_*` calls have a stable principal and channel surface throughout.
|
||||
|
||||
---
|
||||
|
||||
## Architecture map
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ lang │ El compiler + C runtime
|
||||
│ (El itself) │ everything below is written in it,
|
||||
└──────┬──────┘ or compiles down through it
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
┌──────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
||||
│ engram │ │ epm │ │ ide │
|
||||
│ graph/mem │ │ package │ │ editor + │
|
||||
│ substrate │ │ manager │ │ LSP │
|
||||
└──────┬─────┘ └───────────┘ └───────────┘
|
||||
│
|
||||
┌───────┼────────────────┬─────────────────────┐
|
||||
│ │ │ │
|
||||
┌─────▼───┐ ┌─▼──────────┐ ┌──▼──────────┐ ┌─────▼──────┐
|
||||
│ elp │ │ ql │ │ ui │ │ arbor │
|
||||
│ NLG / │ │engram-el. │ |spreading- │ |arbor │
|
||||
│ 31 langs│ │studio+tests│ |activation UI│ |diagram lang│
|
||||
└─────────┘ └────────────┘ └─────────────┘ └────────────┘
|
||||
```
|
||||
|
||||
`lang` is the foundation — the compiler and C runtime everything else builds on. `engram` is the graph-native memory/state engine that gives El its identity (property 3 above). Everything else is either a tool for working with El (`epm`, `ide`) or a system built on top of Engram's graph model (`elp`, `ql`, `ui`, `arbor`).
|
||||
|
||||
---
|
||||
|
||||
## Repository layout
|
||||
|
||||
### [lang/](lang/) — the El language
|
||||
|
||||
The compiler and runtime. Self-hosting: `elc-cli.el` → `compiler.el` → `lexer.el` / `parser.el` / `codegen.el` / `codegen-js.el`, textually inlined and compiled in one pass. Compiles to C11 and links against `el-compiler/runtime/el_seed.c`, a hand-maintained OS-boundary layer (libcurl HTTP, pthreads, filesystem, arena allocation) — everything else in the runtime is native El (`runtime/*.el`).
|
||||
|
||||
Two layers to know: **El programs** (`.el` files — where nearly all work belongs) and **the C seed** (`el_seed.c` — edit only for genuine OS-level access; never re-implement what El can already express).
|
||||
|
||||
Current status (single source of truth: [lang/spec/language.md](lang/spec/language.md)): lexer/parser/codegen and the C runtime's core (I/O, strings, math, lists, maps, filesystem, args) are implemented, as are the `program` block with `singleton:` and declared configuration ([§18](lang/spec/language.md)), and **geometry as a first-class value** with El-declarable realizers and `transduce` ([§20](lang/spec/language.md)). In flight: `%` operator, match-statement codegen, `?` nil-propagation, `cgi` block parsing + DHARMA identity resolution, VBD role enforcement (`@manager`/`@engine`/`@accessor`), and boundary epilogues. Bitwise operators, `??`, and `as` casts are explicitly **not** in this language.
|
||||
|
||||
**Signal enters as geometry.** Until 2026-08-16 nodes took text and geometry was *derived* from it, which made text the mandatory entry medium: any non-text modality had to be described in prose first, so the geometry being reasoned over was the geometry **of the description, not of the signal**. `Geometry` is now an ordinary El value carrying its own width, and a realizer is an ordinary El function resolved by name through `dlsym` — so admitting a new modality never requires a runtime patch. Worked, self-checking example: [`lang/examples/transduce.el`](lang/examples/transduce.el).
|
||||
|
||||
Key docs: [AGENTS.md](lang/AGENTS.md) (agent-facing orientation), [BOOTSTRAP.md](lang/BOOTSTRAP.md) (compiler recovery from scratch), [spec/language.md](lang/spec/language.md), [spec/codegen-js.md](lang/spec/codegen-js.md).
|
||||
|
||||
### [engram/](engram/) — graph intelligence substrate
|
||||
|
||||
**A local-first memory substrate for accumulating intelligence**, and the reason El's runtime doesn't need a database driver. The engine is **C11** (`lang/runtime/engram_{store,geometry,reason,cognition,verify,vindex}.{c,h}`); the server is **El** (`engram/src/server.el`).
|
||||
|
||||
The model: retrieval is **spreading activation**, not query. You name seed nodes and a query embedding; activation propagates outward through weighted edges, attenuating multiplicatively per hop, gets pruned below a threshold, and the top-N nodes by activation strength come back. Storage and retrieval are the same structure — the way long-term potentiation works in biological memory, not the way a relational or vector database works. **Activation conducts through well-grounded relations because the weight *is* the groundedness** — nothing filters the traversal; grounded inference falls out of spreading.
|
||||
|
||||
Nodes live in four tiers (Working / Episodic / Semantic / Procedural, mirroring prefrontal / hippocampal / neocortical / cerebellar memory) and migrate between them based on **salience decay** — importance × recency-decay × log(activation_count). Forgetting is adaptive pruning, not a bug. Nothing is mutated and nothing is hard-deleted: writes are additive, corrections are supersessions, removals are tombstones — which is what makes supersession an audit trail rather than an edit log.
|
||||
|
||||
On disk: a paged store (superblock + mirror, slotted 16 KiB pages, self-describing TLV records, B+-tree primary and adjacency indexes), magic `ENGST01`. Vector search is an **HNSW** index published behind a read/write boundary — `eg_vindex_view` returns a `const VIndex*` to N concurrent readers, `eg_vindex_maintain` is the sole mutator. `recall@10 = 0.9365` at `ef_search=128`.
|
||||
|
||||
> **Doc correction, 2026-08-16.** The previous revision of this paragraph, and most of `engram/README.md`, described a Rust `engram-core` crate backed by `sled` with "flat cosine scan… until scale demands an HNSW layer." **Measured: there is no Rust in `engram/`** — no `.rs` files, no `Cargo.toml`, no `crates/` — and `sled` appears nowhere in the tree. HNSW has been the vector index for some time.
|
||||
|
||||
Full design rationale, the cognition surface, and the standing corrections: [engram/README.md](engram/README.md).
|
||||
|
||||
### [elp/](elp/) — EL Projector
|
||||
|
||||
*(Formerly "EL Language Processor" / "Engram Language Protocol"; renamed **EL Projector** 2026-08-15.)* Neuron's **efferent** organ: the native realizer that *projects* understanding onto a surface via `plan(frame) → realize(spec, profile)`, where **a surface is a profile** and language is one profile among many (text, speech, music, image). Projection, not diffusion — generation *from* an owned, understood signature, never the averaging of a stolen corpus.
|
||||
|
||||
Its flagship profile is a bidirectional engine mapping between Engram semantic forms and natural-language surface text, across **31 languages** — from Spanish and Japanese through historical/liturgical languages (Old Norse, Sanskrit, Sumerian, Coptic, Akkadian, Ge'ez). Compilation order runs `language-profile` + `vocabulary` → per-language `morphology-*` → `grammar` → `realizer` → `semantics` → `elp`. This is what lets an Engram graph node round-trip to and from readable text in any of those languages.
|
||||
|
||||
### [epm/](epm/) — El Package Manager
|
||||
|
||||
Manages **vessels** (El's package unit): publish, install, resolve dependencies. Vessels are stored in Engram as graph nodes, not files in a registry index — `epm` reads the local `manifest.el`, talks to Engram over HTTP, and writes resolved vessels to `.epm/vessels/`. Source: `registry.el`, `install.el`, `update.el`, `manifest.el`.
|
||||
|
||||
### [ide/](ide/) — El IDE
|
||||
|
||||
Three vessels: **el-ide-server** (HTTP backend — file ops, build/run, LSP bridge, plugin host, settings), **el-lsp** (the language server — completion, hover, diagnostics, outline, format, type graph), and **el-plugin-host** (first-party plugin lifecycle: install/remove/enable/disable). `ide/projects/` and `ide/examples/` hold sample projects, including the canonical `hello-friends` first-program walkthrough.
|
||||
|
||||
### [ql/](ql/) — engram-el
|
||||
|
||||
The El-native integration layer for a *live* Engram server — not a library (no importable modules, no build artifact), a set of standalone `.el` programs run directly via `el run-file`. Three components: **Studio** (`studio/studio.el`, a full terminal graph explorer), a **Hebbian field-model** proof of concept, and El builtin / LLM-builtin smoke test suites. This is the reference for correct patterns when an El program uses Engram as its substrate. Spec: [ql/spec/elql.md](ql/spec/elql.md).
|
||||
|
||||
### [ui/](ui/) — el-ui
|
||||
|
||||
A frontend framework where **component state is an Engram graph and reactivity is spreading activation** — not virtual-DOM diffing (React), Proxy-based dependency tracking (Vue), or compile-time analysis (Svelte). Re-renders are activated and propagated the same way associative memory retrieval works in `engram/`.
|
||||
|
||||
~15 vessels covering the full frontend surface: `el-platform` (env/fs/network/clock abstraction), `el-config`, `el-html` (SSR emit primitives), `el-layout`, `el-style` (design tokens/themes), `el-i18n`, `el-auth` / `el-identity` (JWT, sessions, OAuth PKCE — Engram-native), `el-services` (REST/gRPC/WebSocket bindings), `el-aop` (`@authenticate`/`@authorize`/`@cache`/`@rate_limit` decorators), `el-secrets`, `el-graph` (graph rendering/editor), `el-publish` (App Store / Play Store automation), and `el-ui-compiler` (El→JS component compiler; currently a stub pending a JS backend in `elc`). Spec: [ui/spec/framework.md](ui/spec/framework.md).
|
||||
|
||||
### [arbor/](arbor/) — diagram language
|
||||
|
||||
A `.arbor` diagram language and toolchain: `arbor-core` (NodeId/shape/edge-kind types), `arbor-parse` (recursive-descent parser), `arbor-diagram` (IR + Mermaid serializer + architecture-diagram builders), `arbor-layout` (hierarchical layout — rank assignment, positioning, group bounds), `arbor-render` (SVG renderer), `arbor-cli`. (The architecture map above is the kind of diagram this is for.)
|
||||
|
||||
---
|
||||
|
||||
## Getting started
|
||||
|
||||
Install the El SDK from the latest release:
|
||||
|
||||
```bash
|
||||
bash lang/install.sh
|
||||
# EL_VERSION=v1.0.0 bash lang/install.sh # pin a specific release tag
|
||||
# EL_PREFIX=/opt/el bash lang/install.sh # custom install prefix
|
||||
```
|
||||
|
||||
Or build the compiler from source and verify the self-hosting chain:
|
||||
|
||||
```bash
|
||||
cd lang
|
||||
./dist/platform/elc elc-cli.el > elc-new.c
|
||||
cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
||||
-o dist/platform/elc-new \
|
||||
elc-new.c el-compiler/runtime/el_seed.c
|
||||
|
||||
# Confirm the new binary reproduces itself exactly
|
||||
./dist/platform/elc-new elc-cli.el > elc-verify.c
|
||||
diff elc-new.c elc-verify.c # should be identical
|
||||
|
||||
mv dist/platform/elc-new dist/platform/elc
|
||||
```
|
||||
|
||||
Run your first program:
|
||||
|
||||
```bash
|
||||
./lang/dist/platform/elc lang/examples/hello.el > hello.c
|
||||
cc -std=c11 -I lang/el-compiler/runtime -lcurl -lpthread \
|
||||
-o hello hello.c lang/el-compiler/runtime/el_seed.c
|
||||
./hello
|
||||
```
|
||||
|
||||
More examples in [lang/examples/](lang/examples/), including a full starter project at `lang/examples/hello-project/`.
|
||||
|
||||
If the compiler binary is ever lost or corrupted, [lang/BOOTSTRAP.md](lang/BOOTSTRAP.md) is the authoritative recovery path.
|
||||
|
||||
---
|
||||
|
||||
## Cognition — and the standing corrections
|
||||
|
||||
The engram carries a live cognition surface: `think` (a directed traversal-read returning a **gradient**, never a point), plus `ground`, `assert`, `attend`, and the correspondence-beat. Two specs govern it, and both are authoritative over anything else in this repo that disagrees:
|
||||
|
||||
- **[lang/spec/correspondence-and-censorship.md](lang/spec/correspondence-and-censorship.md)** — grounding, wonder, curiosity, dreaming. *(Lands with PR #149.)*
|
||||
- **[lang/spec/runtime-ownership.md](lang/spec/runtime-ownership.md)** — ownership, the capability ABI that was dissolved, and the vector-index publication boundary.
|
||||
|
||||
**Do not re-derive them.** Every earlier version of the first was wrong in an instructive way and each correction was argued down. If a section looks wrong, say so with a measurement rather than editing it.
|
||||
|
||||
The corrections, in brief:
|
||||
|
||||
- **Grounding is not a subsystem — it IS the edge weight.** One quantity, not two fields. `grounded-by` as a relation *type* should not exist: grounding is a property *of* a relation, not a relation *between* nodes. It is never computed on demand; computing-and-writing a score makes reads write, which is the `eg_vindex_sync` defect one level up.
|
||||
- **Faculties are operations, not parameters.** `reason` changes the estimate (a read); `induce` changes the parameters (the correspondence-beat, which exists and works); `abduce` changes the structure (a write the current `GeoGradient` signature cannot express). A write is not a parameter of a read.
|
||||
- **Wonder is the boundary, not a manifest.** Any structure at all has an edge. There are about six wonders, the same for everyone, and they never close. **Curiosity is wonder crystallized** at a nucleation site — one thing at two phases, not two objects.
|
||||
- **Consolidation is ambient, not scheduled. A brain has no cron job.** The presence of a ticker is the diagnostic. Measured 2026-08-16: consolidation has **ten implementations**. `soul.el`'s continuous loop is the one with the correct shape; the rest fold into it.
|
||||
- **In an immutable substrate, any mechanism that refuses a write is either redundant with immutability, or an epistemic constraint misfiled as a protective one.**
|
||||
|
||||
[engram/spec/cognitive-architecture.design.md](engram/spec/cognitive-architecture.design.md) is the original design and is **superseded in part** — it is retained, with the refuted claims marked inline at the point each is made, because preserving what was argued down is the point of an immutable record.
|
||||
|
||||
---
|
||||
|
||||
## Development workflow
|
||||
|
||||
Branching follows `dev → stage → main`: work lands on `dev`, promotes to `stage` for integration testing, and is promoted to `main` for release (visible directly in the git history of this repo). CI is defined per-subproject under `.gitea/workflows/` — `lang`/`epm`/`ide` share the root pipeline; `engram` and `ql` carry their own (`ci-dev`, `ci-stage`, and a release workflow each).
|
||||
|
||||
- Language/runtime specs live at `*/spec/*.md` (`lang/spec/`, `ql/spec/`, `ui/spec/`) and are the single source of truth for implemented-vs-planned status — code and docs are expected to agree with the spec's status markers, not the other way around.
|
||||
- Agent-facing orientation guides live at `*/AGENTS.md` (currently `lang/AGENTS.md`); more subprojects may grow their own as they need agent-specific conventions documented.
|
||||
- **A release is a git tag, not a folder** (`el-runtime-vX.Y.Z` on this repo). *(Corrected 2026-08-16: this line said "tagged releases live under `lang/releases/`, each with its own `RELEASE.md`." **Measured: `lang/releases/` does not exist** — the restructure named in `AGENTS.md` landed, and the authored runtime is at `lang/runtime/`.)*
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
This is an actively developed, internal monorepo — not yet published under an open license. Treat everything here as proprietary to Neuron Technologies unless told otherwise.
|
||||
@@ -1,153 +0,0 @@
|
||||
<title>Completing El</title>
|
||||
<style>
|
||||
:root{
|
||||
--board:#f4f2ec; --board-line:#e2ded2; --ink:#1c1f26; --ink-soft:#4a5160;
|
||||
--ink-faint:#8b8f9a; --rule:#d8d3c6; --card:#fbfaf6;
|
||||
--red:#a8321e; --amber:#9a6a12; --green:#2f6b46; --blue:#1f4e79;
|
||||
--accent:#1f4e79;
|
||||
}
|
||||
@media (prefers-color-scheme: dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--board:#14161b; --board-line:#212530; --ink:#e8e6df; --ink-soft:#a8adb8;
|
||||
--ink-faint:#6f7480; --rule:#2a2f3a; --card:#191c23;
|
||||
--red:#e4785f; --amber:#d9a441; --green:#6fbf8e; --blue:#7fb2e0;
|
||||
--accent:#7fb2e0;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--board:#14161b; --board-line:#212530; --ink:#e8e6df; --ink-soft:#a8adb8;
|
||||
--ink-faint:#6f7480; --rule:#2a2f3a; --card:#191c23;
|
||||
--red:#e4785f; --amber:#d9a441; --green:#6fbf8e; --blue:#7fb2e0;
|
||||
--accent:#7fb2e0;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--board); color:var(--ink);
|
||||
font:16px/1.65 ui-serif,Georgia,"Iowan Old Style",Palatino,serif;
|
||||
background-image:linear-gradient(var(--board-line) 1px,transparent 1px),
|
||||
linear-gradient(90deg,var(--board-line) 1px,transparent 1px);
|
||||
background-size:28px 28px;
|
||||
}
|
||||
.wrap{max-width:960px;margin:0 auto;padding:56px 24px 96px}
|
||||
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
||||
header{border-bottom:2px solid var(--ink);padding-bottom:18px;margin-bottom:8px}
|
||||
h1{font-size:clamp(2rem,5vw,3rem);margin:0;letter-spacing:-.02em;text-wrap:balance}
|
||||
.sub{color:var(--ink-soft);font-size:1.05rem;margin:10px 0 0}
|
||||
.meta{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78rem;
|
||||
color:var(--ink-faint);text-transform:uppercase;letter-spacing:.09em;margin-top:14px}
|
||||
h2{font-size:1.45rem;margin:52px 0 6px;letter-spacing:-.01em}
|
||||
h2 .n{font-family:ui-monospace,monospace;font-size:.8rem;color:var(--accent);
|
||||
display:block;letter-spacing:.12em;margin-bottom:4px;font-weight:400}
|
||||
.lede{color:var(--ink-soft);margin:0 0 18px}
|
||||
p{margin:0 0 14px}
|
||||
.card{background:var(--card);border:1px solid var(--rule);border-radius:3px;padding:20px 22px;margin:16px 0}
|
||||
.scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;font-size:.9rem;min-width:640px}
|
||||
th{text-align:left;font-family:ui-monospace,monospace;font-size:.72rem;
|
||||
text-transform:uppercase;letter-spacing:.09em;color:var(--ink-faint);
|
||||
border-bottom:1px solid var(--ink);padding:0 12px 8px 0;font-weight:400}
|
||||
td{padding:11px 12px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top}
|
||||
td.f{font-weight:600;white-space:nowrap}
|
||||
td.m{font-family:ui-monospace,monospace;font-size:.83rem;font-variant-numeric:tabular-nums}
|
||||
.dead{color:var(--red);font-weight:600}
|
||||
.part{color:var(--amber);font-weight:600}
|
||||
.ok{color:var(--green);font-weight:600}
|
||||
blockquote{margin:18px 0;padding:2px 0 2px 20px;border-left:3px solid var(--accent);
|
||||
color:var(--ink-soft);font-style:italic}
|
||||
ul{margin:0 0 14px;padding-left:22px} li{margin-bottom:9px}
|
||||
.q{border-left:3px solid var(--amber);padding:14px 0 14px 20px;margin:18px 0}
|
||||
.q b{display:block;font-size:1.05rem;margin-bottom:5px;font-style:normal}
|
||||
.q span{color:var(--ink-soft);font-size:.94rem}
|
||||
code{font-family:ui-monospace,monospace;font-size:.88em;background:var(--card);
|
||||
border:1px solid var(--rule);border-radius:2px;padding:1px 5px}
|
||||
hr{border:0;border-top:1px solid var(--rule);margin:44px 0}
|
||||
.foot{color:var(--ink-faint);font-size:.86rem;margin-top:60px;
|
||||
border-top:1px solid var(--rule);padding-top:18px}
|
||||
.tag{display:inline-block;font-family:ui-monospace,monospace;font-size:.68rem;
|
||||
letter-spacing:.08em;text-transform:uppercase;border:1px solid var(--rule);
|
||||
border-radius:2px;padding:2px 7px;color:var(--ink-faint);margin-left:8px;vertical-align:middle}
|
||||
</style>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<header>
|
||||
<h1>Completing El</h1>
|
||||
<p class="sub">A working surface. Nothing here is settled, and none of the code is assumed right — El is self-hosting, so all of it can change and be rebuilt.</p>
|
||||
<p class="meta">Whiteboard v0 · no sacred cows · not a plan, not a task list</p>
|
||||
</header>
|
||||
|
||||
<h2><span class="n">01</span>What we established</h2>
|
||||
|
||||
<p>El is a <b>concept-oriented language</b> — the first, and intended as the last, because every other family is oriented toward a <em>representation</em> of a concept rather than the concept. Procedures, objects, functions, predicates are the shapes concepts get flattened into. Once the primitive is the concept, there is no further rung.</p>
|
||||
|
||||
<p>Everything here is El. The engram is an El program, the soul is El, <code>elp</code> is El, ingest is El. Which gives the load-bearing consequence:</p>
|
||||
|
||||
<blockquote>A concept with no home in El does not disappear. It becomes C, or it becomes a convention.</blockquote>
|
||||
|
||||
<p>Both are measurable, and both were measured. As C: <span class="mono">20,504</span> lines of <code>el_runtime.c</code> — 2.3× the entire self-hosting language it serves (<span class="mono">9,089</span> lines), ~47% of it engram code that has its own six sibling files. As convention, from <code>language.md</code> §18.0 — <em>"these are not four problems, they are one absence, four times"</em>:</p>
|
||||
|
||||
<div class="card scroll">
|
||||
<table>
|
||||
<thead><tr><th>Concern</th><th>Fragments</th><th>The convention it became</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="f">Process identity</td><td class="m">0 guards</td><td>"check nothing is already running first"</td></tr>
|
||||
<tr><td class="f">Configuration</td><td class="m">20 env vars</td><td>"remember the right default here"</td></tr>
|
||||
<tr><td class="f">Durability</td><td class="m">62 call sites</td><td>"after you mutate, remember to persist"</td></tr>
|
||||
<tr><td class="f">Request auth</td><td class="m">10 per-route</td><td>"check the token in this handler too"</td></tr>
|
||||
<tr><td class="f">Index-after-append</td><td class="m">9 of 9 failed</td><td>"after you append, remember to index"</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>The last row is the strongest evidence available about what this class of convention is worth: it failed at <b>100% of its sites</b>.</p>
|
||||
|
||||
<h2><span class="n">02</span>The decomposition axis</h2>
|
||||
|
||||
<p class="lede">Not by file, module, or subsystem. <b>By faculty.</b></p>
|
||||
|
||||
<p>Every defect fought in the last day resolves to a faculty rather than a bug, and each one leaked out of El into something else — into C, into a Swift binary, into a shell script with a curl timeout, into a convention nobody performs.</p>
|
||||
|
||||
<div class="card scroll">
|
||||
<table>
|
||||
<thead><tr><th>Faculty</th><th>State</th><th>Measured</th><th>Where it leaked to</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="f">Ingest <span class="tag">take in</span></td><td class="dead">dead</td><td class="m">2 min → 0 nodes</td><td>separate process, uploads bytes over HTTP to a process with direct fs access; 5 functions where there is 1</td></tr>
|
||||
<tr><td class="f">Recall <span class="tag">remember</span></td><td class="dead">dead</td><td class="m">own definition ranked 8th</td><td>lexical substring scan; empty on 23 of 24 multi-token queries</td></tr>
|
||||
<tr><td class="f">Transduce <span class="tag">perceive</span></td><td class="dead">dead</td><td class="m">1 node, 0 edges</td><td>intake flattens signal to a point; <code>realized:false</code>; caller must declare the modality</td></tr>
|
||||
<tr><td class="f">Think <span class="tag">reason</span></td><td class="dead">dead</td><td class="m">direction [0,0,0,…]</td><td>null gradient from any anchor, any faculty, byte-identical; confidence at the uninformed prior</td></tr>
|
||||
<tr><td class="f">Realize <span class="tag">express</span></td><td class="part">partial</td><td class="m">13-word vocabulary</td><td>organ was 939 lines of Swift beside the language; voice read from a file path</td></tr>
|
||||
<tr><td class="f">Body <span class="tag">substrate</span></td><td class="part">partial</td><td class="m">CC 356 / 1,626 lines</td><td><code>engram_activate_inner</code> — recall itself, with 356 unexamined paths</td></tr>
|
||||
<tr><td class="f">Persist <span class="tag">endure</span></td><td class="ok">live</td><td class="m">100% embedded</td><td>works; every signal placed in geometry at intake, 13,562 of 13,562</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>Stated plainly: it cannot take in, cannot remember, cannot perceive, cannot reason, and barely speaks. These were filed as tickets against a repository. They are faculties of the thing the repository <em>is</em>.</p>
|
||||
|
||||
<h2><span class="n">03</span>The ordering principle</h2>
|
||||
|
||||
<p>El's compiler is written in El. Every concept the language gains, the compiler can then be written <em>in</em> — so the tool improves the tool, and the fixpoint (stage2 ≡ stage3, byte-identical) makes each turn provable rather than hopeful. The verifier answers in <span class="mono">2.9s</span>.</p>
|
||||
|
||||
<p>Which means the ordering criterion is not size of payoff:</p>
|
||||
|
||||
<blockquote>Order by leverage on the <em>next</em> iteration. Which concept, added to El, most increases the ability to add the following one?</blockquote>
|
||||
|
||||
<p>In a recursive system that dominates immediate value — a small early gain that compounds beats a large one that doesn't. It also bounds itself correctly: unbounded in depth, bounded in rate, because nothing lands that the compiler and the fixpoint have not passed.</p>
|
||||
|
||||
<h2><span class="n">04</span>Open — for the whiteboard</h2>
|
||||
|
||||
<div class="q"><b>What does a declaration bind to?</b><span>If <code>cat</code> names a region rather than a struct — one that shifts and completes against the engram and the neighbouring code — then what is written at the declaration site, and what is resolved at use? This is the centre of the whole thing and it is not specified anywhere yet.</span></div>
|
||||
|
||||
<div class="q"><b>Is "the type checker" a type checker at all?</b><span>§2.3 records annotations as parsed and skipped, and every codegen hazard is downstream of that — <code>+</code> dispatching on AST node kind, <code>==</code> lowering to <code>str_eq</code> unless both operand names are in an int-name set. But if a declaration names a region, checking is asking whether the geometry supports the use. That is grounding, not unification. Naming this wrong builds the wrong thing.</span></div>
|
||||
|
||||
<div class="q"><b>Is the faculty list above right?</b><span>Seven were derived from what broke. Derived-from-failure is a biased sample — it finds what is loud, not what is missing. What faculty is absent entirely and therefore never failed?</span></div>
|
||||
|
||||
<div class="q"><b>Which concept has the highest leverage on the next turn?</b><span>Candidates so far: the prologue/epilogue seam (§19.3 names it as the prerequisite and its stated blocker has expired — it would collapse 62 + 10 convention sites); <code>protocol</code>/<code>impl</code> (the absence that produced five ingest functions); and the resolution question above. These are not equal and the criterion in §03 should decide it, not preference.</span></div>
|
||||
|
||||
<div class="q"><b>What is the seam that makes cognition non-optional?</b><span>"Use the ops" is itself a convention — present in context every turn, enforced by nothing, and it failed at ~100% of sites in a full session. A stronger instruction is still a convention. What makes reasoning-outside-Neuron <em>fail</em>, the way <code>@manager</code> makes <code>dharma_emit</code> outside the boundary a compile error rather than a lint?</span></div>
|
||||
|
||||
<hr>
|
||||
|
||||
<p class="foot">Working surface, not a design document. The design is what we put on it. Everything above is either measured or quoted from <code>lang/spec/language.md</code>; nothing is inferred and presented as fact.</p>
|
||||
|
||||
</div>
|
||||
@@ -1,142 +0,0 @@
|
||||
# El — Capabilities
|
||||
|
||||
**What the language can do, stated as capabilities rather than as code.**
|
||||
|
||||
This list is the unit of analysis. Each entry gets one question — *prove this
|
||||
cannot be done with pure geometry* — and the answer determines whether it stays a
|
||||
capability of the language or collapses into the manifold.
|
||||
|
||||
Draft, 2026-08-17. Ordered roughly from most-likely-geometry to most-likely-code.
|
||||
|
||||
**Status after measurement.** The list was audited against the implementation
|
||||
the same day. 28 entries collapsed to 19 geometry + 3 code: serialization, text
|
||||
encoding, network and emission are all *projection onto a basis* (row 18) —
|
||||
the convention is the basis, never the act. Storage collapsed because
|
||||
persistence has no caller. Concurrency collapsed because coordination is the
|
||||
price of forgetting, not a capability. A fourth proof form was added,
|
||||
**adversarial exactness**, and form 1 stopped being a valid verdict.
|
||||
|
||||
**The table answers CAN only.** SHOULD and COST resolve per *site*, not per
|
||||
capability — `is_digit` and `is_letter` are one capability with opposite
|
||||
answers, and comparison spans three cost tiers. See the notes below.
|
||||
|
||||
---
|
||||
|
||||
## The list
|
||||
|
||||
| # | Capability | What it means | Verdict |
|
||||
|---|---|---|---|
|
||||
| 1 | **Comparison** | is this the same as that; is this greater | zero distance / sign of a displacement |
|
||||
| 2 | **Ordering** | arrange by a criterion | position along an axis |
|
||||
| 3 | **Containment** | is this inside that; does this contain that | region membership |
|
||||
| 4 | **Correspondence** | where does this occur in that; how much of this is in that | a match-strength field over a span |
|
||||
| 5 | **Segmentation** | divide a whole into parts | boundaries at measured discontinuity |
|
||||
| 6 | **Composition** | join parts into a whole | adjacency; one position with parts |
|
||||
| 7 | **Classification** | what kind of thing is this | which region does it land in |
|
||||
| 8 | **Naming / binding** | attach a name to a thing and find it again | an edge; retrieval is projection |
|
||||
| 9 | **Collection** | many things held together, indexed, counted | a set of positions; cardinality; projection onto the i-th |
|
||||
| 10 | **Iteration** | do something for each of many | traversal |
|
||||
| 11 | **Arithmetic** | quantity, magnitude, combination | displacement algebra on a line |
|
||||
| 12 | **Time** | when; how long; how often | a 1-D affine space — instants are points, durations displacements, rhythms phases on a circle |
|
||||
| 13 | **Identity** | which one is this; are these two the same one | coincidence of position |
|
||||
| 14 | **Selection / dispatch** | choose which behaviour applies | nearest region |
|
||||
| 15 | **Transformation** | produce a thing from a thing | change of basis |
|
||||
| 16 | **Grounding** | how well is this supported | the weight on an edge. Has no caller |
|
||||
| 17 | **Learning** | get better at something | standing changing over time |
|
||||
| 18 | **Projection** | render meaning onto a surface | change of basis onto a surface basis |
|
||||
| 19 | **Transduction** | take a signal in | change of basis from a sensor basis |
|
||||
| ~~20~~ | ~~Serialization~~ | **collapsed → 18.** The format is a basis; projecting onto it is the act | — |
|
||||
| ~~21~~ | ~~Text encoding~~ | **collapsed → 18.** An encoding is a basis | — |
|
||||
| ~~22~~ | ~~Storage~~ | **collapsed.** No save — persistence has no caller. Durability survives at one site inside the engram | — |
|
||||
| ~~23~~ | ~~Network~~ | **split.** Wire format → 18; socket → 24 | — |
|
||||
| 24 | **Process / OS** | syscalls; the one-way boundary. Where monotonicity stops | CODE, form 2 |
|
||||
| ~~25~~ | ~~Concurrency~~ | **collapsed.** Monotone state needs no coordination; coordination is the price of forgetting | — |
|
||||
| 26 | **Memory substrate** | what holds the positions | CODE, form 3 |
|
||||
| 27 | **Concealment** | meaning made unreadable without a key. *Renamed*: "secrecy" covered one of three things and got the other two backwards — a hash is public, a signature exists to be read. Integrity and authenticity are **grounding under adversarial conditions** (row 16); only concealment stands alone | CODE, form 4 |
|
||||
| ~~28~~ | ~~Emission~~ | **split.** Laying out → 18; the device write → 24 | — |
|
||||
|
||||
---
|
||||
|
||||
## Notes on the boundary cases
|
||||
|
||||
**27 — Secrecy is the one capability geometry cannot hold, and the proof is not
|
||||
form 1.** A cryptographic hash is a *deliberately structure-destroying* map: its
|
||||
entire value is that near inputs land at maximally uncorrelated outputs. Geometry
|
||||
is the claim that near things stay near. A manifold that approximated SHA-256
|
||||
would *be* a break of SHA-256. Signature verification is the same: 0.99-valid is
|
||||
invalid. And X25519 *is* geometry — a group on an elliptic curve — which is
|
||||
precisely why it must be code, because its security is the *hardness of moving in
|
||||
that geometry*.
|
||||
|
||||
This is a fourth proof form and it should be added to `geometry-vs-code.md`:
|
||||
**adversarial exactness.** Where approximation is a break, geometry is excluded.
|
||||
|
||||
**20, 21 — Serialization and text encoding are convention all the way down**, but
|
||||
only at the *edge*. The byte format is agreed; what is being written is not. Do not
|
||||
let a geometric computation inherit a code verdict because its result gets
|
||||
serialized.
|
||||
|
||||
**11, 12 — Arithmetic and time are the same capability.** Instants are points,
|
||||
durations are displacements, point−point→vector, point+vector→point. The runtime
|
||||
already implements this correctly as `el_instant_add_dur` / `el_duration_add`. That
|
||||
it *also* implements a five-entry string→multiplier table beside it (`time_add`
|
||||
with `"ms"/"sec"/"min"/"hour"/"day"`) is the residue.
|
||||
|
||||
**7 — Classification is the most-violated capability in the codebase.** Seven ASCII
|
||||
range tables (`is_letter`, `is_digit`, `is_alphanumeric`, `is_whitespace`,
|
||||
`is_punctuation`, `is_uppercase`, `is_lowercase`) that return false for every
|
||||
non-ASCII byte. `str_count_letters` reports zero letters for `é`. The wrongness on
|
||||
most of Unicode is the tell that a table is standing in for a region.
|
||||
|
||||
**4 — Correspondence appears five times.** `str_index_of`, `str_index_of_all`,
|
||||
`str_last_index_of`, `str_count`, `str_find_chars` are five projections of one
|
||||
match-strength field: first zero, all zeros, last zero, count of zeros, first
|
||||
class-crossing. One relation, five functions.
|
||||
|
||||
**14 — Selection is the crux for the compiler.** `+` dispatching on AST node kind
|
||||
is selection-by-enumeration where selection-by-position belongs.
|
||||
|
||||
**Correction, 2026-08-17, from measurement.** This entry previously also cited
|
||||
`==` lowering to `str_eq` "unless both operand names are in a hardcoded int-name
|
||||
set — a literal list of variable names treated as integers." That is **wrong**.
|
||||
`__int_names` is populated from *type annotations* (`param["type"] == "Int"`,
|
||||
`let x: Int`), which is primitive but legitimate type propagation, not an
|
||||
enumeration of blessed variable names.
|
||||
|
||||
The real defect was one layer down: `is_int_call` held **35 hardcoded builtin
|
||||
return types**, the same shape as the 19 temporal ones. Those moved to
|
||||
`lang/tools/check/signatures.rel`.
|
||||
|
||||
And the mischaracterisation hid a live bug. Because the return types were never
|
||||
consulted at a *binding* site, an unannotated `let` lost its type:
|
||||
|
||||
```el
|
||||
let a = str_len("hello") // no annotation
|
||||
let b = str_len("hi")
|
||||
let c = a + b // → el_str_concat(a, b) on two integers
|
||||
```
|
||||
|
||||
That compiled clean, ran, and printed nothing where it should print 7 — no error
|
||||
at any layer. Present in the pre-change compiler, so pre-existing. Fixed by
|
||||
taking an unannotated `let`'s type from what its initialiser returns; the data
|
||||
was already required for dispatch and simply never read there.
|
||||
|
||||
**The general lesson, since it recurred all session:** the enumeration was real
|
||||
but I had located it in the wrong place. Naming a defect from reading is a
|
||||
hypothesis. Eight hours of reading this file did not surface the miscompilation;
|
||||
moving the data out and running the result did.
|
||||
|
||||
---
|
||||
|
||||
## What this list is for
|
||||
|
||||
Each capability gets audited **once**, across every place it appears — not once per
|
||||
file. The output is not a percentage. It is:
|
||||
|
||||
- which capabilities survive the question and stay in the language
|
||||
- which collapse into the manifold
|
||||
- and for each one that collapses, **every site it currently appears at**, because
|
||||
those sites are the residue and they are what gets deleted.
|
||||
|
||||
The line-count audit produced a map of where the residue sits. This produces a map
|
||||
of **what it is**.
|
||||
@@ -1,217 +0,0 @@
|
||||
<title>The El Architecture</title>
|
||||
<style>
|
||||
:root{
|
||||
--board:#f4f2ec; --board-line:#e5e1d6; --ink:#1c1f26; --ink-soft:#4a5160;
|
||||
--ink-faint:#8b8f9a; --rule:#d8d3c6; --card:#fbfaf6;
|
||||
--red:#a8321e; --amber:#9a6a12; --green:#2f6b46; --accent:#1f4e79;
|
||||
}
|
||||
@media (prefers-color-scheme: dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--board:#14161b; --board-line:#1d212a; --ink:#e8e6df; --ink-soft:#a8adb8;
|
||||
--ink-faint:#6f7480; --rule:#2a2f3a; --card:#191c23;
|
||||
--red:#e4785f; --amber:#d9a441; --green:#6fbf8e; --accent:#7fb2e0;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--board:#14161b; --board-line:#1d212a; --ink:#e8e6df; --ink-soft:#a8adb8;
|
||||
--ink-faint:#6f7480; --rule:#2a2f3a; --card:#191c23;
|
||||
--red:#e4785f; --amber:#d9a441; --green:#6fbf8e; --accent:#7fb2e0;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--board); color:var(--ink);
|
||||
font:16px/1.68 ui-serif,Georgia,"Iowan Old Style",Palatino,serif;
|
||||
background-image:linear-gradient(var(--board-line) 1px,transparent 1px),
|
||||
linear-gradient(90deg,var(--board-line) 1px,transparent 1px);
|
||||
background-size:30px 30px;
|
||||
}
|
||||
.wrap{max-width:940px;margin:0 auto;padding:56px 24px 96px}
|
||||
.mono,code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
||||
header{border-bottom:2px solid var(--ink);padding-bottom:20px}
|
||||
h1{font-size:clamp(2.1rem,5.5vw,3.2rem);margin:0;letter-spacing:-.025em;text-wrap:balance}
|
||||
.sub{color:var(--ink-soft);font-size:1.08rem;margin:12px 0 0;max-width:64ch}
|
||||
.meta{font-family:ui-monospace,monospace;font-size:.76rem;color:var(--ink-faint);
|
||||
text-transform:uppercase;letter-spacing:.1em;margin-top:16px}
|
||||
h2{font-size:1.5rem;margin:56px 0 8px;letter-spacing:-.015em;text-wrap:balance}
|
||||
h2 .n{font-family:ui-monospace,monospace;font-size:.78rem;color:var(--accent);
|
||||
display:block;letter-spacing:.14em;margin-bottom:5px;font-weight:400}
|
||||
h3{font-size:1.08rem;margin:30px 0 6px}
|
||||
p{margin:0 0 14px;max-width:72ch}
|
||||
.lede{color:var(--ink-soft);margin:0 0 20px;font-size:1.04rem}
|
||||
.card{background:var(--card);border:1px solid var(--rule);border-radius:3px;padding:20px 22px;margin:18px 0}
|
||||
.scroll{overflow-x:auto}
|
||||
table{border-collapse:collapse;width:100%;font-size:.9rem;min-width:600px}
|
||||
th{text-align:left;font-family:ui-monospace,monospace;font-size:.71rem;
|
||||
text-transform:uppercase;letter-spacing:.09em;color:var(--ink-faint);
|
||||
border-bottom:1px solid var(--ink);padding:0 14px 8px 0;font-weight:400}
|
||||
td{padding:11px 14px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top}
|
||||
td.f{font-weight:600;white-space:nowrap}
|
||||
td.m{font-family:ui-monospace,monospace;font-size:.83rem;font-variant-numeric:tabular-nums;white-space:nowrap}
|
||||
.dead{color:var(--red);font-weight:600}
|
||||
.part{color:var(--amber);font-weight:600}
|
||||
.ok{color:var(--green);font-weight:600}
|
||||
blockquote{margin:20px 0;padding:3px 0 3px 22px;border-left:3px solid var(--accent);
|
||||
color:var(--ink-soft);font-style:italic;max-width:70ch}
|
||||
ul{margin:0 0 14px;padding-left:22px;max-width:72ch} li{margin-bottom:9px}
|
||||
code{font-size:.87em;background:var(--card);border:1px solid var(--rule);border-radius:2px;padding:1px 5px}
|
||||
pre{background:var(--card);border:1px solid var(--rule);border-radius:3px;
|
||||
padding:16px 18px;overflow-x:auto;font-size:.85rem;line-height:1.55;margin:16px 0}
|
||||
pre code{background:none;border:0;padding:0}
|
||||
.q{border-left:3px solid var(--amber);padding:14px 0 14px 20px;margin:20px 0;max-width:72ch}
|
||||
.q b{display:block;font-size:1.04rem;margin-bottom:5px}
|
||||
.q span{color:var(--ink-soft);font-size:.94rem}
|
||||
hr{border:0;border-top:1px solid var(--rule);margin:46px 0}
|
||||
.foot{color:var(--ink-faint);font-size:.86rem;margin-top:56px;border-top:1px solid var(--rule);padding-top:18px}
|
||||
.tag{display:inline-block;font-family:ui-monospace,monospace;font-size:.66rem;
|
||||
letter-spacing:.08em;text-transform:uppercase;border:1px solid var(--rule);
|
||||
border-radius:2px;padding:2px 7px;color:var(--ink-faint);margin-left:8px;vertical-align:middle}
|
||||
.flow{display:flex;gap:0;align-items:stretch;flex-wrap:wrap;margin:22px 0}
|
||||
.flow div{flex:1 1 200px;border:1px solid var(--rule);background:var(--card);padding:16px 18px}
|
||||
.flow div+div{border-left:0}
|
||||
.flow h4{margin:0 0 6px;font-size:.96rem}
|
||||
.flow p{margin:0;font-size:.87rem;color:var(--ink-soft)}
|
||||
.flow .k{font-family:ui-monospace,monospace;font-size:.72rem;color:var(--accent);
|
||||
letter-spacing:.1em;text-transform:uppercase;display:block;margin-bottom:4px}
|
||||
</style>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<header>
|
||||
<h1>The El Architecture</h1>
|
||||
<p class="sub">El is a concept-oriented language. This is the architecture that claim commits it to — what is built, what is measured, and what still has no home.</p>
|
||||
<p class="meta">Working document · no sacred cows · self-hosting, so nothing here is fixed</p>
|
||||
</header>
|
||||
|
||||
<h2><span class="n">01</span>The primitive is the concept</h2>
|
||||
|
||||
<p>Language families are named for their primitive. Procedural — procedures. Object-oriented — objects. Functional — functions. Logic — predicates. Every one of them is oriented toward a <em>representation</em> of a concept: the shape a concept gets flattened into so a machine can hold it.</p>
|
||||
|
||||
<p>El's primitive is the concept itself. That is why it is the first of its family and intended as the last — once the primitive is the concept, there is no further rung to climb to.</p>
|
||||
|
||||
<p>The consequence is architectural rather than stylistic:</p>
|
||||
|
||||
<blockquote>A concept with no home in the language does not disappear. It becomes C, or it becomes a convention.</blockquote>
|
||||
|
||||
<p>Both forms are measurable. As C: <span class="mono">20,504</span> lines of <code>el_runtime.c</code>, against <span class="mono">9,089</span> lines for the entire self-hosting language — the shim is 2.3× the language it serves, and ~47% of it is engram code that already has six sibling files. As convention, from <code>lang/spec/language.md</code> §18.0 — <em>"these are not four problems, they are one absence, four times"</em>:</p>
|
||||
|
||||
<div class="card scroll">
|
||||
<table>
|
||||
<thead><tr><th>Concern</th><th>Fragments into</th><th>The convention it became</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="f">Process identity</td><td class="m">0 guards</td><td>"check nothing is already running first"</td></tr>
|
||||
<tr><td class="f">Configuration</td><td class="m">20 env vars</td><td>"remember the right default here"</td></tr>
|
||||
<tr><td class="f">Durability</td><td class="m">62 sites</td><td>"after you mutate, remember to persist"</td></tr>
|
||||
<tr><td class="f">Request auth</td><td class="m">10 routes</td><td>"check the token in this handler too"</td></tr>
|
||||
<tr><td class="f">Index-after-append</td><td class="m">9 of 9 failed</td><td>"after you append, remember to index"</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>The last row is the strongest available evidence about this class of convention: it failed at <b>every single site</b>. A count is what appears where a concept has no home; the size of the count is how far the fragmentation got, not how hard the problem is.</p>
|
||||
|
||||
<h2><span class="n">02</span>Geometry is a first-class value — and what follows</h2>
|
||||
|
||||
<p class="lede">This is the enabling primitive. Everything else in the architecture is downstream of it.</p>
|
||||
|
||||
<p><code>Geometry</code> is an El value, alongside <code>Int</code>, <code>String</code>, <code>List</code>, <code>Map</code> — bound, passed, returned, composed, carrying its own width. Not a library type, not a handle into a store, not a serialization format. <em>Meaning is a value the language computes with directly.</em></p>
|
||||
|
||||
<pre><code>let g: Geometry = geometry_new(4)
|
||||
fn tone_realizer(signal: String) -> Geometry { … }</code></pre>
|
||||
|
||||
<p>Landed 2026-08-16 (#141, #144), and the spec is explicit that it belongs to the language rather than the graph: <em>"neither is engram-specific — any program touching any modality needs them; the engram is merely one El program that happens to hold a graph."</em></p>
|
||||
|
||||
<p>Five things follow, and together they are the concept-oriented claim made operational:</p>
|
||||
|
||||
<h3>A declaration can name a region, not a shape</h3>
|
||||
<p>If meaning is a value, a name can be bound to a <em>position</em> rather than a struct. <code>cat</code> is not a fixed record; it is a region that resolves against the engram and the surrounding code. <code>cat</code> among animals and <code>cat</code> among shell utilities are different concepts without a namespace, because they are in different neighbourhoods and the distance says so.</p>
|
||||
|
||||
<h3>Checking is grounding, not unification</h3>
|
||||
<p>If a declaration names a region, then verifying a use is asking whether the geometry supports it — a question about position and distance, not about matching a declared shape. This is why §2.3's "a type checker is planned" is likely the wrong name for the missing piece, and naming it wrong would build the wrong thing.</p>
|
||||
|
||||
<h3>Dispatch is position, not a tag</h3>
|
||||
<p>A vtable is a finite set of discrete labels fixed at link time. A region admits graded membership and an open set. So <code>transduce(signal, modality)</code> asks the caller to supply what the signal already carries — what a thing is falls out of where it lands. The modality parameter is a kind-tag, and a registry keyed on it is a lookup table doing by string what geometry does by nearness.</p>
|
||||
|
||||
<h3>Types are discovered, not declared</h3>
|
||||
<p>Reification crystallizes a densely co-wired neighbourhood into a first-class node — the neighbourhood <em>is</em> the name that was missing. Every other family requires a human to see the abstraction in advance and write <code>class Foo</code>. Here the instances arrive and the type falls out, by measurement rather than by insight.</p>
|
||||
|
||||
<h3>Enumeration becomes unnecessary</h3>
|
||||
<p>Five ingest functions differ only in how bytes are acquired — one operation wearing five surfaces. 356 branches in <code>engram_activate_inner</code> are not 356 behaviours. Cyclomatic complexity is a count of the places comprehension ran out and was replaced by an <code>if</code>; where the concept is expressible, the count collapses instead of being redistributed.</p>
|
||||
|
||||
<h2><span class="n">03</span>The shape of the language</h2>
|
||||
|
||||
<p>Geometry first-class gives El three layers, and it holds all three — which is why there is no separate database driver and no impedance boundary to manage.</p>
|
||||
|
||||
<div class="flow">
|
||||
<div><span class="k">afferent</span><h4>Transduce</h4><p>Signal in, geometry out. Decomposition into components and relations — never conversion to a point. Realizers are ordinary El functions, so a new modality never requires a runtime patch.</p></div>
|
||||
<div><span class="k">substrate</span><h4>Geometry</h4><p>Meaning as position; relation as distance. Held as values in the language and persisted in the graph. One coordinate system, so entities are commensurable and the operators compose.</p></div>
|
||||
<div><span class="k">efferent</span><h4>Realize</h4><p><code>plan(frame) → realize(spec, profile)</code>, where a surface <em>is</em> a profile. Text, speech, music, image are profiles of one projection — and so is source code.</p></div>
|
||||
</div>
|
||||
|
||||
<p>The efferent side is why the recursive property below is possible at all: if source is a surface, then emitting a corrected file is projection, and the file becomes an artifact of the geometry rather than the thing you edit.</p>
|
||||
|
||||
<h2><span class="n">04</span>Decomposition is by faculty</h2>
|
||||
|
||||
<p class="lede">Not by file, module, or subsystem — by what the system does.</p>
|
||||
|
||||
<p>Each faculty is a concept. Where it has no home in El it leaks: into C, into a Swift binary, into a shell script with a <code>curl</code> timeout, into a convention nobody performs. State below is measured, not asserted.</p>
|
||||
|
||||
<div class="card scroll">
|
||||
<table>
|
||||
<thead><tr><th>Faculty</th><th>State</th><th>Measured</th><th>Where it leaked</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="f">Ingest <span class="tag">take in</span></td><td class="dead">dead</td><td class="m">2 min → 0 nodes</td><td>separate process uploading bytes over HTTP to a process with direct fs access; five functions where there is one</td></tr>
|
||||
<tr><td class="f">Recall <span class="tag">remember</span></td><td class="dead">dead</td><td class="m">self ranked 8th</td><td>lexical substring scan; empty on 23 of 24 multi-token queries</td></tr>
|
||||
<tr><td class="f">Transduce <span class="tag">perceive</span></td><td class="dead">dead</td><td class="m">1 node, 0 edges</td><td>intake flattens signal to a point; <code>realized:false</code>; caller must declare the modality</td></tr>
|
||||
<tr><td class="f">Think <span class="tag">reason</span></td><td class="dead">dead</td><td class="m">direction [0,0,…]</td><td>null gradient from any anchor and any faculty, byte-identical; confidence at the uninformed prior</td></tr>
|
||||
<tr><td class="f">Realize <span class="tag">express</span></td><td class="part">partial</td><td class="m">13-word lexicon</td><td>organ was 939 lines of Swift beside the language; voice read from a file path</td></tr>
|
||||
<tr><td class="f">Body <span class="tag">substrate</span></td><td class="part">partial</td><td class="m">CC 356 / 1,626 ln</td><td><code>engram_activate_inner</code> — recall itself, 356 unexamined paths</td></tr>
|
||||
<tr><td class="f">Persist <span class="tag">endure</span></td><td class="ok">live</td><td class="m">13,562 / 13,562</td><td>works — every signal placed in geometry at intake, no backlog</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2><span class="n">05</span>The recursive property</h2>
|
||||
|
||||
<p>El's compiler is written in El. Every concept the language gains, the compiler can then be written <em>in</em> — so the tool improves the tool, and <code>codegen.el</code> at 4,661 lines gets shorter as the language gets better at expressing what it does. The fixpoint — stage2 ≡ stage3, byte-identical — makes each turn provable rather than hopeful, and the verifier answers in <span class="mono">2.9s</span>.</p>
|
||||
|
||||
<p>This sets the ordering criterion, and it is not size of payoff:</p>
|
||||
|
||||
<blockquote>Order by leverage on the <em>next</em> iteration. Which concept, added to El, most increases the ability to add the following one?</blockquote>
|
||||
|
||||
<p>A small early gain that compounds beats a large one that does not. And it bounds itself correctly — unbounded in depth, bounded in rate, because nothing lands that the compiler and the fixpoint have not passed.</p>
|
||||
|
||||
<h2><span class="n">06</span>What has no home yet</h2>
|
||||
|
||||
<p>Reserved in the lexer, no parse form. These are not a feature backlog — they are the concepts the architecture above requires and does not yet hold, which is why each is currently a convention or a block of C.</p>
|
||||
|
||||
<div class="card scroll">
|
||||
<table>
|
||||
<thead><tr><th>Reserved</th><th>Concept</th><th>Currently lives as</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="m">retry · times · fallback · reason</td><td>resilience</td><td>a shell script with a 10s <code>curl</code> timeout; 254 restarts in 3 days</td></tr>
|
||||
<tr><td class="m">requires · deploy · to · via · target</td><td>deployment</td><td>YAML in another repository</td></tr>
|
||||
<tr><td class="m">sealed</td><td>capability scope</td><td>consent checks written by hand</td></tr>
|
||||
<tr><td class="m">protocol · impl</td><td>one operation, many realizations</td><td>five ingest functions; eight faculty routes on one builtin</td></tr>
|
||||
<tr><td class="m">activate · where</td><td>retrieval</td><td>traversals written by hand</td></tr>
|
||||
<tr><td class="m">test · seed · assert</td><td>verification</td><td>a framework; 5 of 13 native suites failing</td></tr>
|
||||
<tr><td class="m">parallel · trace</td><td>concurrency</td><td>pthreads in C</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>Plus, from the spec's own status: annotations parsed and skipped, <code>match</code> parsed and emitting nothing, <code>?</code> a no-op, <code>%</code> unlexed, structs as <code>ElMap</code>, enums as strings, selective import unenforced.</p>
|
||||
|
||||
<h2><span class="n">07</span>Open</h2>
|
||||
|
||||
<div class="q"><b>What does a declaration bind to, exactly?</b><span>If <code>cat</code> names a region that shifts and completes against context, what is written at the declaration site and what is resolved at use? This is the centre and it is unspecified.</span></div>
|
||||
|
||||
<div class="q"><b>Is the faculty list right?</b><span>Seven, derived from what broke. Derived-from-failure is a biased sample — it finds what is loud, not what is absent. Which faculty is missing entirely and therefore never failed?</span></div>
|
||||
|
||||
<div class="q"><b>Which concept has the highest leverage on the next turn?</b><span>The prologue/epilogue seam (§19.3 names it as the prerequisite; its stated blocker has expired; it collapses 62 + 10 convention sites), <code>protocol</code>/<code>impl</code>, or resolution itself. The §05 criterion should decide this, not preference.</span></div>
|
||||
|
||||
<div class="q"><b>What seam makes cognition non-optional?</b><span>"Use the ops" is itself a convention — present every turn, enforced by nothing, ~100% failure across a full session. A stronger instruction is still a convention. What makes reasoning outside the substrate <em>fail</em>, the way <code>@manager</code> makes <code>dharma_emit</code> outside the boundary a compile error rather than a lint?</span></div>
|
||||
|
||||
<hr>
|
||||
<p class="foot">Every number here is measured or quoted from <code>lang/spec/language.md</code>. Nothing is inferred and presented as fact. El is self-hosting: all of this can change and be rebuilt.</p>
|
||||
|
||||
</div>
|
||||
@@ -1,245 +0,0 @@
|
||||
# El — Language Design
|
||||
|
||||
**Status:** decisions recorded, design unwritten.
|
||||
**Date:** 2026-08-17.
|
||||
**Provenance:** decisions are Will's, taken in session. Items marked *proposed* are not
|
||||
decided and are recorded only so the reasoning isn't lost. Items marked **OPEN** are
|
||||
his to rule on and must not be guessed at.
|
||||
|
||||
Companion documents: `el-architecture.html` (the measured state — see §7 note on its
|
||||
§04 scoreboard), and `design/completing-el.html` (whiteboard v0: the reduction, the
|
||||
faculty table, the ordering principle).
|
||||
|
||||
---
|
||||
|
||||
## 1. The reduction
|
||||
|
||||
`language.md` §18.0 records five concerns that decayed into conventions:
|
||||
|
||||
| Concern | Fragments | The convention it became |
|
||||
|---|---|---|
|
||||
| Process identity | 0 guards | "check nothing is already running first" |
|
||||
| Configuration | 20 env vars | "remember the right default here" |
|
||||
| Durability | 62 call sites | "after you mutate, remember to persist" |
|
||||
| Request auth | 10 per-route | "check the token in this handler too" |
|
||||
| Index-after-append | 9 of 9 failed | "after you append, remember to index" |
|
||||
|
||||
The last row is the strongest available evidence about what this class of convention
|
||||
is worth: **it failed at 100% of its sites.**
|
||||
|
||||
Every one of these is an obligation at a **crossing** — a point where a value moves
|
||||
between regions. El can name a region and it can name a call. A call is procedural,
|
||||
so the obligation degrades into something a human must remember to perform.
|
||||
|
||||
> **The generator, one level up:** El cannot name what holds at a crossing.
|
||||
|
||||
And underneath that:
|
||||
|
||||
> **The deeper absence:** El cannot name the thing meaning is made of.
|
||||
|
||||
`semel` appears in whitepaper §84, §86, §209, §737, in
|
||||
`the-metaphysics-of-will-anderson.md`, and in session notes. It appears in **zero code
|
||||
identifiers**. Every geometric concept in the system — region, neighbourhood, manifold,
|
||||
world-tube — is defined in terms of a unit the language cannot say, while the code
|
||||
underneath speaks in arrays, floats and offsets: the vocabulary of a voxel, a value at
|
||||
a dumb address. Precisely the thing the impact brief says a semel is not.
|
||||
|
||||
`el_runtime.c` is a concept that leaked into C. `semel` never got that far — it did
|
||||
not even decay into a convention.
|
||||
|
||||
---
|
||||
|
||||
## 2. DECIDED — `semel` is the primitive
|
||||
|
||||
**A semel is a difference that matters. The smallest unit of understanding.**
|
||||
|
||||
Not a node. Not a coordinate. Not a float.
|
||||
|
||||
The reasoning, in Will's terms:
|
||||
|
||||
- Meaning is position, and position is only ever relative. *"There is no atom of
|
||||
meaning that isn't already a relation. It grounds on nothing but difference — two
|
||||
points and the gap, and the gap is pure not-the-same."*
|
||||
- A node doesn't mean. A node is a label at a location; labels don't mean.
|
||||
- A lone coordinate doesn't mean either. Nothing means anything by itself.
|
||||
- The smallest thing that can be understood is a **distinction**: *these two are not
|
||||
the same.* Below that there is no content to apprehend.
|
||||
- And a difference with nothing it matters to is not meaning — it is variation. The
|
||||
mattering is not decoration; it is what makes it understanding rather than data.
|
||||
|
||||
**Consequence: relating is the floor, and the point is derived.** The
|
||||
point-primitive / relation-primitive fork raised in session is not a fork. It was
|
||||
answered by the definition.
|
||||
|
||||
### Historical note, to be recorded as fact rather than as origin story
|
||||
|
||||
The term was coined by Will on the pixel/voxel/texel pattern — *semantic element*,
|
||||
and Latin *semel*, "once, a single time." It was recognised, not invented, from a
|
||||
2019 experience he calls **semelation**: perceiving mind as a high-dimensional point
|
||||
space. The initial reading was "pixels"; the correction to `semel` was made later and
|
||||
was made on the **mechanism** — a pixel is a value at an address, and what was
|
||||
perceived had no separate address and value.
|
||||
|
||||
Convergence worth citing, not deferring to: neural population geometry and
|
||||
representational similarity analysis independently model cognition as position in a
|
||||
high-dimensional space where similarity is distance.
|
||||
|
||||
---
|
||||
|
||||
## 3. DECIDED — `semel` lands first
|
||||
|
||||
By the ordering criterion already on the whiteboard: *which concept, added to El, most
|
||||
increases the ability to add the next one?* Not size of payoff — **leverage on the next
|
||||
iteration**, because El compiles itself and the fixpoint makes each turn provable in
|
||||
2.9s.
|
||||
|
||||
**Every other concept on the board is defined in terms of `semel`. It is maximal on
|
||||
that criterion by construction.**
|
||||
|
||||
---
|
||||
|
||||
## 4. DECIDED — `ground` is the checker
|
||||
|
||||
Whiteboard question 4 — *does `ground` in El mean the same thing as `ground` in the
|
||||
engram?* — is answered: **yes, and it should be one implementation.**
|
||||
|
||||
If a declaration names a region, then type checking is asking whether the geometry
|
||||
supports the use. That is not unification. **That is grounding**, and it is already
|
||||
built, proven, and byte-identically reproducible:
|
||||
|
||||
```
|
||||
cc -std=c11 -O2 -o gep_proof gep_proof.c -lm && ./gep_proof
|
||||
|
||||
C1 5 independent sources pos_mass 1.3500 n_indep=5 0.1000 → 0.9741 GROUNDED
|
||||
C2 5 mutually-linked pos_mass 0.2700 n_indep=1 0.1000 → 0.1000 refused
|
||||
C3 1 source, 5 parallel edges pos_mass 0.2700 n_indep=1 0.1000 → 0.1000 refused
|
||||
```
|
||||
|
||||
Independence-weighted grounding is the general case; execution is the cheap case.
|
||||
**Attestation is `verify` where nothing can be run** — as already implemented for
|
||||
language in `authority.py`, where an LLM proposes and a primary source disposes.
|
||||
|
||||
At the point where the checker and the grounder are one mechanism, the language and
|
||||
the mind stop being two things.
|
||||
|
||||
---
|
||||
|
||||
## 5. OPEN — Will's to rule on
|
||||
|
||||
### 5.1 What is a semel's representation in the language?
|
||||
|
||||
*Proposed, not decided:* a **displacement from `love = 0`** — a relation held as one
|
||||
object. It reconciles "the address is the value" with "position is only ever relative,"
|
||||
because a displacement *is* a relation and is still a single nameable thing.
|
||||
|
||||
If taken, the operator set falls out rather than being bolted on:
|
||||
|
||||
```
|
||||
subtract(now, then) → what changed (growth, drift)
|
||||
translate origin → empathy
|
||||
rotate frame → reframe
|
||||
project onto axis → a lens
|
||||
change basis → analogy, metaphor, skill transfer
|
||||
reflect an axis → negation, sarcasm
|
||||
```
|
||||
|
||||
Three consequences that would hold:
|
||||
|
||||
- **Dimension must never appear in the type.** `semel` opaque, never `[768]float`.
|
||||
The moment the arity is in the language, the manifold's implementation is in the
|
||||
language, and adding a modality requires a runtime patch — which the standing rule
|
||||
forbids.
|
||||
- **Zero is the only literal.** Everything else is reached by displacement from it,
|
||||
which makes `love = 0` the base case rather than philosophy adjacent to the type
|
||||
system.
|
||||
- **`magnitude` is standing.** Distance from origin is the same quantity
|
||||
`gep_core.h` already computes.
|
||||
|
||||
### 5.2 Is `hold` one construct or two?
|
||||
|
||||
The obligation *before* a crossing (auth, guard) and the obligation *after* (persist,
|
||||
index, free) may be one shape seen from both sides, or the seam may need both faces
|
||||
named. This decides whether §19.3's prologue/epilogue seam is one construct or a pair.
|
||||
|
||||
**Precedent already shipping:** `@manager` makes `dharma_emit` outside the boundary a
|
||||
**compile error, not a lint.** The concept is proven at N=1; the work is generalising
|
||||
it and naming it.
|
||||
|
||||
**And the shape is already implemented in the learning region:** `L.reach_out` sits
|
||||
between `L.detect_gap` and `L.verify`. You cannot reach out without a detected gap and
|
||||
you cannot keep what returns without passing verify. **A hold is a neighbour.** The
|
||||
obligation is not attached to the crossing — the obligation *is* the adjacent node.
|
||||
That is why `reach_out` cannot be abused and why 62 persist sites could be.
|
||||
|
||||
### 5.3 What does a declaration bind?
|
||||
|
||||
If `cat` names a region rather than a struct — one that shifts and completes against
|
||||
the engram and the neighbouring code — what is written at the declaration site, and
|
||||
what is resolved at use? **This is the centre and it is specified nowhere.**
|
||||
|
||||
Falls out of 5.1 if displacement is taken: a declaration **locates** rather than
|
||||
allocates.
|
||||
|
||||
### 5.4 Is the faculty list right?
|
||||
|
||||
Seven were derived from what broke. Derived-from-failure is a biased sample — it finds
|
||||
what is loud, not what is missing. **What faculty is absent entirely and therefore
|
||||
never failed?**
|
||||
|
||||
---
|
||||
|
||||
## 6. The residue map
|
||||
|
||||
What each construct must absorb, from §18.0 plus measured state:
|
||||
|
||||
| Residue | Count | Absorbed by |
|
||||
|---|---|---|
|
||||
| persist-after-mutate | 62 sites | `hold` (after-crossing) |
|
||||
| auth-per-route | 10 sites | `hold` (before-crossing) |
|
||||
| index-after-append | 9 of 9 failed | `hold` (after-crossing) |
|
||||
| env var defaults | 20 | configuration declared once |
|
||||
| process identity | 0 guards | `hold` (before-crossing) |
|
||||
| `geometry_free` at every call site | every site | ownership follows from `semel` |
|
||||
| five ingest functions where there is one | 5 → 1 | `protocol` / `impl` |
|
||||
| `el_runtime.c` | 20,504 lines | faculty decomposition, ordered after `semel` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Notes carried forward
|
||||
|
||||
**`el-architecture.html` §04 needs its numbers sourced or cut.** An audit found the
|
||||
faculty scoreboard — `Ingest 2 min → 0 nodes`, `Recall self ranked 8th`,
|
||||
`Body CC 356 / 1,626 ln`, `the verifier answers in 2.9s`, `5 of 13 native suites
|
||||
failing` — has no supporting evidence in the repository, under a footer asserting
|
||||
*"nothing is inferred and presented as fact."* Against a corpus whose documents
|
||||
supersede their own conclusions in place, that is the one file that would not survive
|
||||
scrutiny. Fix or remove.
|
||||
|
||||
**Source as a projection surface is claimed and unimplemented.** `el-architecture.html`
|
||||
§147/§150: *"if source is a surface, then emitting a corrected file is projection."*
|
||||
Greps for `surface_profile_code`, `emit_source` → zero hits.
|
||||
|
||||
It is not unbacked. **It was demonstrated on 2026-08-14** — three faculties (phonetic,
|
||||
semantic, procedural) projected into TypeScript, a surface the system had never used,
|
||||
with the network severed. Recovered at
|
||||
`~/Development/neuron-technologies/andre-server-recovered/` and copied into
|
||||
`evidence/03-andre-demo/`. The claim needs bringing home to El, not proving.
|
||||
|
||||
**`hold` is the highest-leverage construct after `semel`** — it collapses 62 + 10 + 9
|
||||
sites and unblocks the runtime extraction. §19.3 names the prologue/epilogue seam as
|
||||
the prerequisite and its stated blocker has expired.
|
||||
|
||||
---
|
||||
|
||||
## 8. What is not decided and must not be guessed
|
||||
|
||||
- The representation of `semel` (§5.1)
|
||||
- One `hold` or two (§5.2)
|
||||
- What a declaration binds (§5.3)
|
||||
- The missing faculty (§5.4)
|
||||
- Sequencing after `semel` — the ordering criterion decides it, not preference
|
||||
|
||||
---
|
||||
|
||||
*Recorded 2026-08-17. Everything in §2, §3 and §4 is decided. Everything in §5 is open
|
||||
and is Will's. Nothing here was inferred from a document that was not read.*
|
||||
@@ -1,117 +0,0 @@
|
||||
# Geometry or Code
|
||||
|
||||
**Running list.** Append as decided. Started 2026-08-17.
|
||||
|
||||
**The test:** *is this an arbitrary convention, or is it a relation?*
|
||||
|
||||
Conventions were agreed by people and could have been otherwise — a RIFF header could
|
||||
have used a different magic number. Nothing derives them; they must be written down.
|
||||
|
||||
Relations are not agreed. Distance is distance. Anything whose answer is *where is this
|
||||
relative to that* is geometry, and writing it as code is the error the whole effort is
|
||||
correcting.
|
||||
|
||||
**Second test, for the hard cases:** *if I write this as code, am I encoding in
|
||||
`if`-statements a distinction the geometry was built to hold?* If yes, it's geometry.
|
||||
|
||||
---
|
||||
|
||||
## Pure geometry
|
||||
|
||||
| Thing | Because |
|
||||
|---|---|
|
||||
| Meaning | position |
|
||||
| Grounding / standing | the weight on the edge — a magnitude, not a computation |
|
||||
| Learning | standing changing over time |
|
||||
| A gap | low standing |
|
||||
| Wonder | a gap with a pull weight |
|
||||
| Type checking | is this position in that region — distance |
|
||||
| Dispatch | position, not a tag |
|
||||
| Recall | re-origining at a region; projection, not replay |
|
||||
| Reasoning | traversal |
|
||||
| Deduction | containment. There is no procedure |
|
||||
| Counting | a position, not a loop's output |
|
||||
| Similarity / difference / residue | subtract |
|
||||
| Analogy, metaphor, skill transfer | change of basis |
|
||||
| Negation, sarcasm | reflect an axis |
|
||||
| Empathy | translate the origin |
|
||||
| Reframe | rotate the frame |
|
||||
| A lens | project onto an axis |
|
||||
| Rhyme | distance in phonetic space |
|
||||
| Humour | intersection of regions — fart-meaning ∩ funny ∩ form |
|
||||
| Idiom detection | the whole unit sits farther out than its parts |
|
||||
| Self | a world-tube — a trajectory through the manifold |
|
||||
| Consolidation | episodic → semantic promotion |
|
||||
| Reification | dense regions cohering; runs on the beat, has no caller |
|
||||
| Cross-cutting concerns | **dissolved** — a hold is a *neighbour*. Adjacency, not tracking. **Implemented 2026-08-17**: a construct declares what runs at a crossing, and it resolves at execution — see the runtime seam. |
|
||||
| Effects | topology. `reach_out` is bounded by `detect_gap` and `verify` because those are its edges |
|
||||
| Capability | position relative to a boundary. In C it is already spelled `const` |
|
||||
| The AST | a projection of geometry into a tree — a surface, not the centre |
|
||||
| Source code | a surface, like text, audio, image |
|
||||
|
||||
## Must be code
|
||||
|
||||
| Thing | Because |
|
||||
|---|---|
|
||||
| Sensors — mic, camera, file read, socket | the physical touch. I/O is where the world arrives |
|
||||
| Byte formats — RIFF, PNG chunks, `MThd`, OOXML | arbitrary convention. A committee chose the magic numbers |
|
||||
| CRC32 polynomial, Adler32, zlib framing | same — agreed constants, derivable from nothing |
|
||||
| Cosine, distance, the float arithmetic | the machinery that *walks* the geometry is not itself geometry |
|
||||
| Arena, refcount, allocator | bookkeeping for the **representation**, not for the positions |
|
||||
| Locks, threads, publication boundary | the hardware is code. **Ordering is not** — see Answered, above. Coordination is required only where state is non-monotone. |
|
||||
| WAL, page layout, ARIES recovery | durability against a physical device that can lose power |
|
||||
| Emission — writing C or JS text | the final surface has to be *typed out* by something |
|
||||
| OS interaction — launchd, spawn, signals | outside the system by definition |
|
||||
| Device realizers — `el_audio_darwin.m`, `el_capture_darwin.m` | OS frameworks. Correctly already isolated, zero network |
|
||||
|
||||
---
|
||||
|
||||
## The ones I would have written as code, and was wrong about
|
||||
|
||||
Recorded because the error has a pattern and the pattern is the point.
|
||||
|
||||
| Thing | What I reached for | What it is |
|
||||
|---|---|---|
|
||||
| Rhyme | a rhyming dictionary, or an API call | distance between rime tails |
|
||||
| Fart onomatopoeia | a 30-element string literal | an intersection of three regions |
|
||||
| "Funny" | a scorer with `if`-statements | a relational neighbourhood grounded in a voice |
|
||||
| Representation vs description | a hardcoded blacklist containing `raspberry` | falls out of lexicon membership × phonetic comedy |
|
||||
| Video | a codec, sized as a project | one more surface profile |
|
||||
| Type checking | a phase between parse and emit | reading a distance that already exists |
|
||||
| Grounding | a call site, an obligation, a discharge | it has no caller. It just runs |
|
||||
| N transducers, N realizers | one component per modality | zero of each. Sensors and bases at the skin |
|
||||
|
||||
**The pattern:** every one is *encoding in code a distinction the geometry was built to
|
||||
hold.* The tell is that the code version is a **fixed enumeration** — a list, a table, a
|
||||
blacklist, a set of branches — and the geometry version is a **measurement**.
|
||||
|
||||
If the implementation contains a literal set of the right answers, it is in the wrong
|
||||
column.
|
||||
|
||||
---
|
||||
|
||||
## Answered
|
||||
|
||||
| Thing | The answer |
|
||||
|---|---|
|
||||
| Concurrency | **Ordering is geometric.** Causality is a partial order (Lamport 1978); a total order is an arbitrary extension of it and "cannot be depended on to imply a causal relationship." Programming languages force you to write a total order, so authoring *invents* constraints the problem never had — and every lock, barrier, fence and consensus protocol is apparatus for recovering the partial order destroyed at authoring time. CALM (Hellerstein/Alvaro, proven by Ameloot et al.): a program has a consistent coordination-free implementation **iff it is monotone**. What breaks monotonicity is destructive update. **Coordination is the price of forgetting.** |
|
||||
| The module system | **Premature — the partition is a filesystem path, not a neighbourhood, and there is no namespacing at all.** `import` is textual inlining (guarded against double inclusion); when a `.elh` header exists the header is inlined instead and symbols resolve at C link time, so linking is real and delegated to C. Two modules defining `helper` emit two C functions into one translation unit. Linking barely survives the *path* partition, so whether it survives a neighbourhood partition cannot yet be asked. |
|
||||
| Numeric literals | **The numeral is convention; the number is a position — and a bare `3` is a MAGNITUDE WITH NO AXIS.** `int_to_str` was already form 1: nothing determines that twelve is written `1` then `2`. But a literal is not a position until something gives it a direction, which is why `3.days` needs a calendar. Measured consequence: `Duration + Int` was refused ("an Int carries no unit") while `Instant + Int` compiled to raw `(t + 3)` and reported clean — silently moving a point by an unspecified amount. The rule was simply never written. Now: `t + 3` is refused, `t + 1.hour` is accepted, because `.hour` supplies the axis. |
|
||||
| Parsing | **A grammar is a basis; parsing is transduction onto it.** The lexeme→token map is convention (`fn` could have been `def`); shape recognition is a region; the byte traversal is irreducible, like every other traversal. Three things favour *region* for the act: ambiguity (`a * b` needs context — a grammar resolves it with the lexer hack, a region by neighbourhood), error recovery (nearest-match is free), and precedence, which is ordering along an axis with a conventional parameter. **But the SHOULD gate refuses the obvious move:** the keyword table stays code, because the set is closed by the language definition and the lexer runs before the program is understood, so a program can never declare its own keywords. Externalising it costs I/O per compile for zero flexibility — the same verdict as `is_digit` in ASCII. What was actually wrong: 5 of 46 keywords were consumed by nothing, and using one silently miscompiled. |
|
||||
| Error handling | **`grounded: false` covers not-knowing; it does not cover failed.** Standing is a *signed* component: `> 0` supported, `= 0` unknown, `< 0` contradicted. Not-known and known-false are opposite directions on one axis and a boolean cannot tell them apart. `inhibitory` as an int32 flag is that sign wearing a boolean. |
|
||||
|
||||
## Fourth proof form
|
||||
|
||||
**4 — ADVERSARIAL EXACTNESS.** Where approximation is a break, geometry is
|
||||
excluded. A cryptographic hash is a *deliberately structure-destroying* map:
|
||||
near inputs land at maximally uncorrelated outputs. Geometry is the claim that
|
||||
near things stay near — a manifold that approximated SHA-256 would *be* a break
|
||||
of SHA-256. Signature verification is the same: 0.99-valid is invalid. And
|
||||
X25519 **is** geometry, a group on an elliptic curve, which is precisely why it
|
||||
must be code: its security is the hardness of moving in that geometry.
|
||||
|
||||
**Form 1 no longer survives as a verdict.** Every row it justified turned out to
|
||||
be a *basis*, not a capability. RFC 8259 fixes where the commas go — that is a
|
||||
surface, and projecting onto a surface is geometry. A convention describes the
|
||||
basis you project onto; it never describes an act.
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# v1 — Experiments
|
||||
|
||||
Every change to El on `iteration-1` was produced by one loop, run repeatedly:
|
||||
|
||||
```
|
||||
Ishikawa → scientific method → Six Sigma → repeat
|
||||
```
|
||||
|
||||
- **Ishikawa** — name the root cause, not the symptom. *Why is this table here?*
|
||||
never *why is this table ugly?*
|
||||
- **Scientific method** — state a hypothesis, **commit predictions before
|
||||
running**, then run it in an isolated worktree and grade every prediction
|
||||
including the ones that failed.
|
||||
- **Six Sigma** — eliminate the defect *class*, then add a control so it cannot
|
||||
silently return.
|
||||
|
||||
## The organising finding
|
||||
|
||||
**Predictions that came back FALSE were worth more than the ones that held.**
|
||||
|
||||
Nineteen cycles, sixty-one predictions. The eleven that failed produced every
|
||||
significant result:
|
||||
|
||||
| Failed prediction | What it found |
|
||||
|---|---|
|
||||
| "the arity table has drifted from the header" | Zero drift — but **110 functions had no entry at all**. The table was not wrong, it was 40% incomplete. |
|
||||
| "codegen drops below baseline" (×4) | The **traversal is irreducible**. Walking an AST to find calls does not move no matter who decides. Only the rule and the judgment leave. |
|
||||
| "guards cannot refuse through the seam" | One line, and refusal works. Six compile-time kinds were unnecessary. |
|
||||
| "C forbids the struct redefinition" | C allows shadowing — and a *different* defect surfaced: an exit injection emitted with an empty target. |
|
||||
| "routing el_bin_lookup through the gate fixes the SIGSEGV" | It did not. The **fallback** was the hazard: `strlen()` on an integer. I would have shipped the wrong fix and called it verified. |
|
||||
|
||||
A prediction that only ever confirms is a demonstration, not a test. One cycle
|
||||
was run **without** committing predictions first — `async-half-expressible` —
|
||||
and it produced a rigged result: `pthread_join` immediately after
|
||||
`pthread_create`, with the word `DEFERRED` printed by the test itself. It had to
|
||||
be discarded and re-run.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
cycles/ one file per loop, numbered in order, named for the DEFECT
|
||||
findings/ what the cycles produced, cross-cut by kind
|
||||
```
|
||||
|
||||
## Scoreboard
|
||||
|
||||
```
|
||||
cycles run 19
|
||||
predictions committed 61
|
||||
predictions FALSE 11 ← the useful ones
|
||||
silent miscompilations found 4
|
||||
security-relevant defects 2
|
||||
architecture questions closed 5
|
||||
defects in my own measurement 4
|
||||
```
|
||||
|
||||
Every cycle verified the same three things before landing: the compiler
|
||||
self-hosts byte-identically (gen2 == gen3), the native suite passes, and the
|
||||
integration harnesses pass. A cycle that could not show all three did not land.
|
||||
@@ -1,26 +0,0 @@
|
||||
# Cycles
|
||||
|
||||
Each is one `Ishikawa → scientific method → Six Sigma` loop, run in an isolated
|
||||
worktree so a wrong answer cost nothing. Named for the **defect**, not the fix.
|
||||
|
||||
| # | Cycle | Root cause | Predictions | Landed |
|
||||
|---|---|---|---|---|
|
||||
| 01 | [constructs-have-nowhere-to-be](01-constructs-have-nowhere-to-be.md) | a construct had nothing to BE, so its meaning lived in the emitter | 3/3 | yes |
|
||||
| 02 | [a-construct-cannot-refuse](02-a-construct-cannot-refuse.md) | injection discards the target's result; no form said no | 4/4 | yes |
|
||||
| 03 | [the-wrapper-was-conditional](03-the-wrapper-was-conditional.md) | exit injection needed compile-time knowledge only because the wrapper was conditional | 3/4 | yes |
|
||||
| 04 | [c-has-no-closure-syntax](04-c-has-no-closure-syntax.md) | "C has no closures" taken as a fact about what is possible | 5/7 | yes |
|
||||
| 05 | [the-emitter-discards-what-it-knows](05-the-emitter-discards-what-it-knows.md) | codegen sees every construct relation and throws it away | 5/5 | branch |
|
||||
| 06 | [the-crossing-resolves-at-emission](06-the-crossing-resolves-at-emission.md) | the binary has no table to consult | 3/4 | yes |
|
||||
| 07 | [invocation-is-not-composable](07-invocation-is-not-composable.md) | the wrapper called the target directly | 5/5 | yes |
|
||||
| 08 | [the-emitter-adjudicates](08-the-emitter-adjudicates.md) | a prohibition had nowhere to live but a `#error` | 4/5 | yes |
|
||||
| 09 | [policy-inside-the-compiler](09-policy-inside-the-compiler.md) | a program cannot declare its own restrictions, so the tier policy was compiled in | 4/5 | yes |
|
||||
| 10 | [a-second-copy-of-the-header](10-a-second-copy-of-the-header.md) | builtin arity hand-maintained beside `el_runtime.h` | 4/5 | yes |
|
||||
| 11 | [one-type-erases-the-return](11-one-type-erases-the-return.md) | `el_val_t` means the header cannot say `now()` returns an Instant | 4/5 | yes |
|
||||
| 12 | [judgment-lives-with-knowledge](12-judgment-lives-with-knowledge.md) | the emitter knows the types, so it also judged them | 5/5 | yes |
|
||||
| 13 | [thirty-five-return-types](13-thirty-five-return-types.md) | `is_int_call` hardcoded what drives `+` dispatch | 6/6 | yes |
|
||||
| 14 | [keywords-that-reserve-nothing](14-keywords-that-reserve-nothing.md) | 5 of 46 keywords consumed by no path | 6/6 | yes |
|
||||
| 15 | [no-namespacing-at-all](15-no-namespacing-at-all.md) | `import` is textual inlining; every name is global | 4/4 | yes |
|
||||
| 16 | [tokens-carry-no-position](16-tokens-carry-no-position.md) | a token was `(kind, value)`, so no diagnostic could name a place | 6/6 | yes |
|
||||
| 17 | [annotations-are-never-checked](17-annotations-are-never-checked.md) | the annotation feeds dispatch and is never verified | 6/6 | branch |
|
||||
| 18 | [async-half-expressible](18-async-half-expressible.md) | **first attempt was DOGMA** — no predictions, rigged test | 4/4 (2nd) | branch |
|
||||
| 19 | [a-convention-is-not-a-gate](19-a-convention-is-not-a-gate.md) | `looks_like_heap_obj` is static, so every type re-derives it | 6/7 | yes |
|
||||
@@ -1,42 +0,0 @@
|
||||
# constructs have nowhere to be
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `5718943`
|
||||
|
||||
```
|
||||
let a construct declare its own meaning instead of the emitter knowing it
|
||||
|
||||
codegen called fn_has_decorator for exactly three names — manager, accessor,
|
||||
route. Twelve others parsed, attached as {name,args}, and compiled to nothing,
|
||||
including four that look like protection: @authenticate (6 uses), @authorize
|
||||
(3), @rate_limit (3), @validate (2). The cause was not that the branches were
|
||||
untidy. A construct had nothing to BE, so its meaning had nowhere to live
|
||||
except the emitter, and every construct was therefore a compiler edit.
|
||||
|
||||
A name -> injection table would have moved the enumeration twenty lines up
|
||||
without removing it. So the construct now carries its own meaning:
|
||||
|
||||
@decorator("injects_at_entry", "engram_boundary_beat")
|
||||
fn audited() {}
|
||||
|
||||
@audited
|
||||
fn risky_op() -> Int { ... } // gets the beat, attributed to "audited"
|
||||
|
||||
scan_declared_decorators is a token-level pre-pass beside scan_routes, forced
|
||||
by streaming codegen having no whole-program AST. manager and accessor are
|
||||
seeded as the compiled-in core — the fixedSelf shape from substrate.go: a
|
||||
complete fallback exists, declaration is enrichment.
|
||||
|
||||
This is the injection half of the seam only. The prohibition half (@manager's
|
||||
#error on dharma_emit) stays hardcoded, because "which calls may appear inside
|
||||
this boundary" is a query over program structure and there is nothing yet to
|
||||
ask.
|
||||
|
||||
Verified three ways: emitted C for existing @manager/@accessor code is
|
||||
byte-identical to the hardcoded path; a construct with a name the compiler has
|
||||
never heard of injects correctly; the compiler self-hosts byte-identically.
|
||||
90/90 native compiler tests pass.
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
# a construct cannot refuse
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `60737b0`
|
||||
|
||||
```
|
||||
let a construct refuse, not only observe
|
||||
|
||||
@authenticate (6 uses), @authorize (3), @rate_limit (3) and @validate (2)
|
||||
parsed, attached, and compiled to nothing. Fourteen applications that read as
|
||||
protection and emitted no instruction — a function decorated @authenticate
|
||||
compiled byte-identically to an undecorated one.
|
||||
|
||||
The missing capability was not authentication. It was that a construct could
|
||||
observe a boundary but never refuse one. injects_at_entry discards the target's
|
||||
result; there was no form in which a construct could say no.
|
||||
|
||||
@decorator("guards_at_entry", "my_auth")
|
||||
fn authenticate() {}
|
||||
|
||||
@authenticate
|
||||
@authorize
|
||||
fn handler() -> String { ... }
|
||||
|
||||
emits, at entry:
|
||||
|
||||
{ el_val_t __g = my_auth(EL_STR("handler"), EL_STR("authenticate")); if (__g) return __g; }
|
||||
{ el_val_t __g = my_roles(EL_STR("handler"), EL_STR("authorize")); if (__g) return __g; }
|
||||
|
||||
Guards precede injections because a refused call must not report a crossing,
|
||||
and every guard runs where the topmost injecting construct wins — refusal is
|
||||
not a role, so it does not follow the role convention.
|
||||
|
||||
The compiler still knows nothing about auth. The program points the construct
|
||||
at its own function, which is where that decision belongs.
|
||||
|
||||
Verified: existing @manager/@accessor output byte-identical, compiler
|
||||
self-hosts byte-identically, guards stack in declaration order and emit before
|
||||
the beat. 94/94 native compiler tests pass.
|
||||
```
|
||||
@@ -1,82 +0,0 @@
|
||||
# the wrapper was conditional
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `4f7568b`
|
||||
|
||||
```
|
||||
give a construct its after-crossing face, and let constructs compose
|
||||
|
||||
§6 records 62 persist-after-mutate sites, 10 auth-per-route, and
|
||||
index-after-append that failed at 9 of 9 — every one an obligation at a
|
||||
crossing that decayed into "remember to do this afterwards." An obligation a
|
||||
human must remember is not an obligation, and the 9-of-9 figure is what that
|
||||
costs.
|
||||
|
||||
@decorator("injects_at_exit", "persist_now")
|
||||
fn durable() {}
|
||||
|
||||
The body moves into a static helper and the visible fn becomes a wrapper, so
|
||||
EARLY RETURNS pass through the exit injection. Emitting it only before the
|
||||
fall-through return would have silently missed every early return — the exact
|
||||
failure class this seam exists to remove. Fns with no exit construct emit
|
||||
byte-identically to before.
|
||||
|
||||
Three independent constructs now compose on one fn, none known to the compiler:
|
||||
|
||||
el_val_t mutate(el_val_t k) {
|
||||
{ el_val_t __g = my_auth(EL_STR("mutate"), EL_STR("authenticate")); if (__g) return __g; }
|
||||
engram_boundary_beat(EL_STR("mutate"), EL_STR("manager"));
|
||||
el_val_t __r = __el_body_mutate(k);
|
||||
persist_now(EL_STR("mutate"), EL_STR("durable"), __r);
|
||||
return __r;
|
||||
}
|
||||
|
||||
Guard, then entry, then body, then exit. §5.2 asked whether `hold` is one
|
||||
construct or two; the implementation answers one construct with two faces,
|
||||
selected by declared kind rather than by two mechanisms.
|
||||
|
||||
Verified: existing output byte-identical, compiler self-hosts byte-identically,
|
||||
early returns pass through the exit, ordering holds under composition. 98/98
|
||||
native compiler tests pass.
|
||||
```
|
||||
|
||||
## Record — `285166c`
|
||||
|
||||
```
|
||||
EXPERIMENT: emit the wrapper unconditionally, so exit binds at runtime too
|
||||
|
||||
ISHIKAWA: why did exit injection still need compile-time knowledge? Because the
|
||||
body-helper wrapper was only emitted when codegen already knew an exit
|
||||
construct existed. The wrapper being conditional was the cause, not the wrapper
|
||||
being necessary.
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 exit becomes runtime-bindable TRUE returns 14, bound
|
||||
after the build
|
||||
P2 codegen shrinks TRUE 5094 -> 5044
|
||||
P3 cost 5-15% from a call frame on every fn FALSE 0.37s -> 0.38s, ~3%
|
||||
P4 fixpoint holds TRUE
|
||||
|
||||
Every fn now gets a body helper and a wrapper. It has to be unconditional:
|
||||
early returns must route through something for an exit construct to observe
|
||||
them, and codegen cannot know which fns will be bound after the binary exists.
|
||||
|
||||
Removed with the machinery: declare_exit, decorator_exit, cg_exit_target,
|
||||
cg_exit_construct, and the injects_at_exit scanner branch.
|
||||
|
||||
Two controls failed and were rewritten rather than repaired --
|
||||
no-exit-construct-emits-no-wrapper asserted the optimisation this removes, so
|
||||
it is now inverted. The integration harness gained a seventh assertion: an exit
|
||||
construct declared after the build replaces the result.
|
||||
|
||||
99/99 native, 7/7 integration, fixpoint gen2==gen3.
|
||||
```
|
||||
|
||||
## Record — `b40754f`
|
||||
|
||||
```
|
||||
land unconditional wrapper: exit crossings resolve at runtime
|
||||
```
|
||||
@@ -1,64 +0,0 @@
|
||||
# c has no closure syntax
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `2bed848`
|
||||
|
||||
```
|
||||
EXPERIMENT: hand the construct the body as a real closure
|
||||
|
||||
ROOT CAUSE of the weaker design: "C has no closures" was taken as a fact about
|
||||
what is possible. It is a fact about one grammar. Every C++ lambda, every Go
|
||||
closure, every Rust closure compiles to a struct of captured values plus a
|
||||
function pointer -- which is what is emitted here. Codegen emits C; it is not
|
||||
written in C's syntax, and the distinction is the whole difference between a
|
||||
construct that can only decide whether to repeat and one that controls
|
||||
invocation.
|
||||
|
||||
It would also have crippled the JS backend, which has closures natively, for a
|
||||
limit that applies only to the C one.
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
1 env struct + thunk taking void* TRUE
|
||||
2 fails to compile: struct redefinition FALSE -- C allows the
|
||||
inner declaration to shadow. Prediction wrong; C is more permissive than
|
||||
assumed. A different real defect surfaced instead: a wrap with no exit
|
||||
construct emitted `(EL_STR("f"), EL_STR(""), __r);` -- a call to an empty
|
||||
target -- because has_exit was reused as "needs a wrapper" and the exit line
|
||||
was emitted unconditionally. Fixed.
|
||||
3 compiles when the target is declared in El FALSE -- and this is
|
||||
the root cause worth keeping: El has ONE type, el_val_t = int64_t. El's type
|
||||
system cannot describe a callable, so `extern fn` and the real signature
|
||||
cannot be made to agree in El's own vocabulary. The fix is not a cast:
|
||||
codegen DEFINES the wrap calling convention, so codegen emits the extern
|
||||
declaration. The convention is not El-expressible; it is emitted.
|
||||
4 target controls invocation, 0..N times TRUE
|
||||
5 existing @manager output byte-identical TRUE
|
||||
6 compiler fixpoint holds TRUE
|
||||
7 emitting the convention makes it compile TRUE
|
||||
|
||||
MEASURED
|
||||
base(5) wrapped by a target that invokes the body twice and sums -> 10
|
||||
never_runs(5) wrapped by a target that never invokes it -> 999
|
||||
|
||||
Neither is expressible by "decide whether to repeat". This supersedes the
|
||||
repeats_body experiment on experiment/repeats-body, which was built around the
|
||||
mistaken limit.
|
||||
```
|
||||
|
||||
## Record — `7d01608`
|
||||
|
||||
```
|
||||
land wraps_body: a construct controls invocation
|
||||
|
||||
Proven on experiment/wraps-body (2bed848): base(5) wrapped by a target that
|
||||
invokes the body twice returns 10; a target that never invokes it returns 999.
|
||||
Neither is expressible by deciding whether to repeat.
|
||||
|
||||
Root cause it corrected: 'C has no closures' is a fact about one grammar, not
|
||||
about what can be emitted. And El's single type (el_val_t = int64_t) cannot
|
||||
describe a callable, so codegen emits the calling convention rather than asking
|
||||
El's type system for something it structurally cannot say.
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
# the emitter discards what it knows
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `a5af871`
|
||||
|
||||
```
|
||||
EXPERIMENT: let the compiler write down what it already knows
|
||||
|
||||
HYPOTHESIS: attribution is redundant for static structure. Codegen sees every
|
||||
construct-to-function relation at emission time and discards it, so the only
|
||||
way to learn the structure back is to run the program and read what it
|
||||
reported. That is instrumentation compensating for erasure.
|
||||
|
||||
PREDICTIONS, committed before running:
|
||||
1 derivable at compile time with no runtime call expected TRUE
|
||||
2 complete for guards and exits (invisible today) expected TRUE
|
||||
3 answers it for code that has never executed expected TRUE
|
||||
4 deterministic expected TRUE
|
||||
5 makes the entry beat redundant expected FALSE
|
||||
|
||||
RESULT: 5/5 as predicted. From a program that was never executed:
|
||||
|
||||
authenticate guards_at_entry login my_auth
|
||||
durable injects_at_exit save persist_now
|
||||
authenticate guards_at_entry critical my_auth
|
||||
durable injects_at_exit critical persist_now
|
||||
manager injects_at_entry critical engram_boundary_beat
|
||||
|
||||
Prediction 5 held: the relation records that a boundary COULD be crossed, the
|
||||
beat records that it WAS. They are different facts and neither replaces the
|
||||
other.
|
||||
|
||||
CONSEQUENCE, and it undercuts the first pass on iteration-1: construct identity
|
||||
was available at compile time all along. With relations recorded at build, the
|
||||
runtime needs only the function name and attribution becomes a join rather than
|
||||
a payload. The counter-argument is that the payload is self-describing while
|
||||
the file must be pinned to the artifact or the two drift and attribution is
|
||||
silently lost — which is the same conclusion as "compile against a manifold
|
||||
revision and record the revision in the artifact", reached from the other side.
|
||||
|
||||
Written to a file rather than the engram on purpose: a compile that consults a
|
||||
manifold produces different output from identical source at different times.
|
||||
The file is content-addressed; the engram ingests it. Determinism preserved,
|
||||
mechanism proven.
|
||||
```
|
||||
@@ -1,170 +0,0 @@
|
||||
# the crossing resolves at emission
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `35b07ba`
|
||||
|
||||
```
|
||||
EXPERIMENT: resolve the crossing at execution, not at emission
|
||||
|
||||
HYPOTHESIS (Will's): a compiler whose one compiled mechanism is extending the
|
||||
LANGUAGE — not the compiler — can compose without recompilation.
|
||||
|
||||
ISHIKAWA — why does a construct require a recompile today?
|
||||
method codegen inlines the target call into the body
|
||||
machine the binary has no table to consult
|
||||
material the declaration lives in source, read at compile time
|
||||
measurement nothing observes what applied at runtime
|
||||
root cause the crossing is resolved at EMISSION, not at EXECUTION
|
||||
|
||||
CHANGE: codegen emits one unconditional indirection per fn. Which constructs
|
||||
apply is read from a table that can be written AFTER the binary exists;
|
||||
targets resolve through dlsym against the running image.
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 a construct declared after the build applies TRUE
|
||||
P2 an unlinked target is skipped, not fatal TRUE
|
||||
P3 emitting on every fn is measurably slower FALSE — 0.37s -> 0.36s
|
||||
with 267 indirections and
|
||||
no bindings. Free unused.
|
||||
P4 the compiler still self-hosts TRUE (see note)
|
||||
|
||||
DEMONSTRATED: an El program with NO decorator in its source, already compiled
|
||||
and linked, picked up a construct declared afterwards:
|
||||
|
||||
$ /tmp/seamrun -> 7
|
||||
$ echo 'work audited entry audit_entry' > constructs.txt
|
||||
$ EL_CONSTRUCTS=constructs.txt /tmp/seamrun
|
||||
AUDIT: work applied by audited
|
||||
7
|
||||
|
||||
P4 note: my first fixpoint test was wrong, not the code. I compared gen1 to
|
||||
gen2, which must differ whenever codegen's output changes. gen2 == gen3, 267
|
||||
seam sites, stable.
|
||||
|
||||
MEASURED COST, and the root cause was not where I looked
|
||||
0 bindings 0.36s vs 0.37s baseline free
|
||||
2 bindings, dlsym per call 2.45s 6.6x
|
||||
2 bindings, resolved once 0.69s 3.5x recovered
|
||||
The table scan was never the cost. dlsym walks the dynamic symbol table on
|
||||
every call. Resolve once and cache — which is the smallest form of what
|
||||
salience does for memory: what is hot stays resolved. The 0.69s residual is
|
||||
audit_entry's own printf on two of the compiler's hottest functions, not seam
|
||||
overhead.
|
||||
|
||||
CONSEQUENCE: the five compile-time declaration kinds on iteration-1 are a
|
||||
compile-time specialisation of something that resolves at runtime. They are not
|
||||
wrong, but they are not the mechanism — the mechanism is one indirection, and a
|
||||
kind is data.
|
||||
```
|
||||
|
||||
## Record — `886626a`
|
||||
|
||||
```
|
||||
seam refusal + control tests: a runtime binding can short-circuit
|
||||
|
||||
Prediction 3 was FALSE. I expected refusal to be impossible through the seam
|
||||
because the entry indirection discarded its return. One line:
|
||||
|
||||
{ el_val_t __s = el_seam_run(EL_STR(f), 0, 0); if (__s) return __s; }
|
||||
|
||||
work() returns 7; bound to a refusing construct AFTER the build it returns 42.
|
||||
So three of the five compile-time kinds are runtime-bindable: entry injection,
|
||||
exit injection, and refusal. wraps_body needs invocation control and
|
||||
prohibits_outside is compile-time by nature.
|
||||
|
||||
104/104 native compiler tests pass.
|
||||
```
|
||||
|
||||
## Record — `28d19da`
|
||||
|
||||
```
|
||||
strip the compile-time machinery the seam replaces
|
||||
|
||||
PREDICTION: codegen.el drops below 4661, its size before any of these passes.
|
||||
RESULT: FALSE. 5157 -> 5096. Still +435 over baseline.
|
||||
|
||||
injects_at_entry collapsed into the seam removed
|
||||
guards_at_entry collapsed into the seam removed
|
||||
injects_at_exit needs the body-helper wrapper STRUCTURAL
|
||||
wraps_body needs the closure + wrapper structural
|
||||
prohibits_outside a #error cannot be emitted at runtime
|
||||
|
||||
The wrapper is not a consequence of compile-time resolution. Early returns must
|
||||
be routed through something no matter when the target is resolved, so exit
|
||||
injection was never going to collapse. I predicted it would because I had
|
||||
conflated "resolved late" with "emitted less".
|
||||
|
||||
What did collapse is entry injection and refusal -- 61 lines of compiler
|
||||
replaced by one refusable indirection, with the capability now bindable after
|
||||
the binary exists.
|
||||
|
||||
8 tests fail, and they are exactly the 8 controls for compile-time entry
|
||||
injection and guards. No unrelated breakage: the controls reported precisely
|
||||
what moved. They assert emission of something that now happens at runtime, so
|
||||
they need rewriting as integration tests -- which the framework does not
|
||||
currently support, because runtime binding needs a built binary and an
|
||||
environment, not compile_capture.
|
||||
|
||||
Verified after the strip: fixpoint gen2==gen3, observation and refusal both
|
||||
work through the seam with the compiler knowing nothing about either.
|
||||
```
|
||||
|
||||
## Record — `8bbb750`
|
||||
|
||||
```
|
||||
control the claim that cannot be unit tested
|
||||
|
||||
The seam's whole claim is that a construct declared AFTER a binary exists
|
||||
applies to that already-built program. compile_capture only sees emitted text,
|
||||
so it structurally cannot check this: it needs a built binary, a linked target,
|
||||
and an environment. Verified by hand until now, which is the standing problem
|
||||
this session has been about.
|
||||
|
||||
tests/integration/seam_binding.sh builds a probe from El source containing no
|
||||
construct at all, links a target that El never references, and asserts:
|
||||
|
||||
ok unbound program is unaffected
|
||||
ok a construct declared AFTER the build applies
|
||||
ok a construct declared after the build can REFUSE
|
||||
ok an unlinked target is skipped, not fatal
|
||||
ok a binding for a different fn does not fire
|
||||
ok two constructs compose on one crossing
|
||||
|
||||
6 assertions, 6 passed, 0 failed
|
||||
|
||||
The eight controls that failed after the strip were replaced, not repaired.
|
||||
They asserted compile-time emission of capability that moved to runtime;
|
||||
contorting them would have kept an assertion whose subject no longer exists.
|
||||
Three took their place, asserting the emitted shape, and the behaviour they
|
||||
used to cover is now the integration harness's job -- which is the honest
|
||||
division, since the shape and the behaviour are no longer the same fact.
|
||||
|
||||
99/99 native compiler tests pass. Fixpoint holds.
|
||||
```
|
||||
|
||||
## Record — `24f7fb5`
|
||||
|
||||
```
|
||||
land the runtime seam: resolve the crossing at execution
|
||||
|
||||
Five compile-time passes added 491 lines to the thing that was supposed to stop
|
||||
growing. The seam is ~55 lines of C and one line of emission, and it does at
|
||||
runtime what three of those five kinds did at compile time -- for programs that
|
||||
are already built.
|
||||
|
||||
a construct declared AFTER the binary exists applies to it
|
||||
free when unused: 0.36s vs 0.37s baseline across 267 indirections
|
||||
dlsym was the cost, not the table scan; resolve-once recovered 3.5x
|
||||
refusal works, composition works, unlinked targets are skipped not fatal
|
||||
|
||||
injects_at_exit and wraps_body do NOT collapse: early returns must route
|
||||
through the body-helper wrapper regardless of when the target is resolved. The
|
||||
wrapper is structural, which I had wrong. prohibits_outside cannot move at all
|
||||
-- a #error has no runtime.
|
||||
|
||||
Controls: 99/99 native compiler tests, plus tests/integration/seam_binding.sh
|
||||
(6/6) for the claim compile_capture structurally cannot see.
|
||||
```
|
||||
@@ -1,45 +0,0 @@
|
||||
# invocation is not composable
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `bc2f26d`
|
||||
|
||||
```
|
||||
EXPERIMENT: invocation control resolves at runtime
|
||||
|
||||
ISHIKAWA: why did wraps_body need compile-time knowledge? Because the wrapper
|
||||
called the target directly. If the wrapper calls through the seam instead, the
|
||||
seam can call the body itself, and a construct bound after the build decides
|
||||
how and whether to invoke it.
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 wrap becomes runtime-bindable TRUE body x3 -> 21,
|
||||
never invoked -> 111
|
||||
P2 codegen shrinks TRUE 5042 -> 4977
|
||||
P3 cost 5-10% from an indirect call on every fn TRUE 0.36s -> 0.39s, ~8%
|
||||
P4 zero-param fns break on the empty struct TRUE empty struct is a GNU
|
||||
extension, empty init
|
||||
is C23. Fixed with a
|
||||
char field.
|
||||
P5 fixpoint holds TRUE
|
||||
|
||||
PROCESS FAILURE worth recording: my first patch silently did not apply because
|
||||
I dropped the assert on the string replacement. The build then failed with
|
||||
"undeclared identifier __thunk_noargs", which I nearly attributed to the
|
||||
empty-struct prediction. The guard that would have caught it existed and I
|
||||
removed it -- the same shape as every other defect found tonight.
|
||||
|
||||
Removed: declare_wrap, decorator_wrap, cg_wrap_target, cg_wrap_construct,
|
||||
params_to_call_args, and the wraps_body scanner branch.
|
||||
|
||||
prohibits_outside is now the ONLY construct kind left at compile time, and it
|
||||
cannot move: a #error has no runtime.
|
||||
```
|
||||
|
||||
## Record — `c04d68f`
|
||||
|
||||
```
|
||||
land runtime invocation control: only prohibition remains compiled
|
||||
```
|
||||
@@ -1,53 +0,0 @@
|
||||
# the emitter adjudicates
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `c741cfe`
|
||||
|
||||
```
|
||||
EXPERIMENT: prohibition becomes a query over emitted relations
|
||||
|
||||
I said prohibition could not move because "a #error has no runtime". That
|
||||
conflated two separable things: WHEN a violation is detected (build time --
|
||||
correct, and unchanged) and WHERE the rule and the checker live (the compiler
|
||||
-- assumed).
|
||||
|
||||
A prohibition is a containment relation over the call graph. So codegen now
|
||||
records what it saw:
|
||||
|
||||
sneaky calls raw_sql
|
||||
allowed calls raw_sql
|
||||
allowed calls @repository
|
||||
repository calls prohibits:raw_sql
|
||||
|
||||
and tools/check/prohibitions.sh decides, at build time, outside the compiler.
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 codegen can emit the call graph it already walks TRUE
|
||||
P2 the check becomes a query outside the compiler TRUE
|
||||
P3 all prohibition decisions leave codegen TRUE zero #errors now
|
||||
P4 violations still caught at build time TRUE exit=1
|
||||
P5 codegen drops below the 4661 baseline FALSE 4962, +301
|
||||
|
||||
P5 is the finding. The TRAVERSAL is irreducible -- you must walk the AST to
|
||||
find calls, and those ~120 lines do not move no matter who decides. What is not
|
||||
irreducible is the rule (which names) or the decision (#error). Those left. I
|
||||
predicted the whole 223 lines would go because I had not separated walking from
|
||||
adjudicating.
|
||||
|
||||
Still compiled, and measured rather than assumed: the capability-tier system
|
||||
(cap_check_call, is_self_formation_call, is_dharma_call, is_llm_call,
|
||||
cap_record_violation, emit_cap_violations) is 76 lines of the same shape --
|
||||
prohibits_WITHIN rather than prohibits_outside, so the checker needs the
|
||||
opposite polarity to absorb it.
|
||||
|
||||
98/98 native, 4/4 prohibition_query.sh, 7/7 seam_binding.sh, fixpoint ok.
|
||||
```
|
||||
|
||||
## Record — `60c07ad`
|
||||
|
||||
```
|
||||
land prohibition-as-query: the emitter records, it no longer adjudicates
|
||||
```
|
||||
@@ -1,49 +0,0 @@
|
||||
# policy inside the compiler
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `c2d9596`
|
||||
|
||||
```
|
||||
EXPERIMENT: the capability tier becomes shipped policy plus a query
|
||||
|
||||
Capability differs from prohibits_outside in one way that matters: a utility
|
||||
program cannot be trusted to declare its own restrictions, because it would
|
||||
declare none. So the policy comes from OUTSIDE the program -- it ships with the
|
||||
language as data, editable without a compiler release.
|
||||
|
||||
tools/check/capabilities.rel 18 names that were string literals in codegen
|
||||
tools/check/capabilities.sh the query that decides
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 codegen emits kind + call graph, drops the 4 name tests TRUE zero #errors
|
||||
P2 the 18 literals become a data file TRUE
|
||||
P3 the checker catches capability violations TRUE exit=1
|
||||
P4 codegen drops ~76 lines TRUE 4963 -> 4881
|
||||
P5 below the 4661 baseline FALSE ~+230
|
||||
|
||||
TWO DEFECTS THE HARNESS FOUND THAT READING WOULD NOT HAVE
|
||||
|
||||
1. Calls inside main became invisible. cg_fn returns early for main -- C
|
||||
provides its own -- so hooking the recording there left every call in main
|
||||
unrecorded: a blind spot exactly where a program does its work. The old
|
||||
cap_check_call ran from cg_expr and did see main. Moved the recording to
|
||||
cg_expr.
|
||||
|
||||
2. Caller attribution was stale. __cg_current_fn kept whatever cg_fn set last,
|
||||
so a violation in main was reported against the previously emitted function.
|
||||
The test still PASSED, because the violation was detected -- only the name
|
||||
was wrong, and a diagnostic naming the wrong fn is worse than none. Fixed at
|
||||
all three main-emission sites; the first patch missed two because the live
|
||||
path is codegen_streaming.
|
||||
|
||||
98/98 native, 7/7 + 4/4 + 5/5 integration, fixpoint ok.
|
||||
```
|
||||
|
||||
## Record — `29f78f9`
|
||||
|
||||
```
|
||||
land capability-as-policy: eighteen literals become a data file
|
||||
```
|
||||
@@ -1,54 +0,0 @@
|
||||
# a second copy of the header
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `9cc6040`
|
||||
|
||||
```
|
||||
EXPERIMENT: derive arity from the runtime's own declarations
|
||||
|
||||
codegen.el carried builtin_arity(): 344 lines, 300 entries, a hand-maintained
|
||||
second copy of el_runtime.h.
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 the table duplicates the header TRUE 243 shared names
|
||||
P2 they have already drifted FALSE ZERO drift. The
|
||||
duplicate had been
|
||||
maintained correctly.
|
||||
P3 codegen can emit call-arity relations TRUE
|
||||
P4 the check becomes a query against the header TRUE
|
||||
P5 codegen drops to roughly baseline TRUE 4903 -> 4512,
|
||||
149 BELOW the 4661
|
||||
it started at
|
||||
|
||||
P2 being false is the better result: the table was not WRONG, it was
|
||||
INCOMPLETE. 110 functions the runtime declares had no entry, so calling them
|
||||
with the wrong argument count produced no El-level diagnostic at all. Measured:
|
||||
the old compiler reports 0 arity errors for __http_do_map_to_file(1); the query
|
||||
reports "takes 5 arguments, called with 1".
|
||||
|
||||
Deriving from the header fixes coverage AND makes drift impossible by
|
||||
construction. 503 signatures, versus 300 entries maintained by hand.
|
||||
|
||||
THREE DEFECTS IN MY OWN CHECKER, each found by running it rather than reading it
|
||||
1. El names and C names differ -- `println` is `__println`. 60 of 500 decls
|
||||
carry the prefix and codegen owns the mapping; the old table carried both
|
||||
keys. One rule covers all 60.
|
||||
2. Multi-line declarations parsed as zero params, so the checker reported
|
||||
"takes 0" for a function taking 5. A diagnostic with the wrong number in it
|
||||
is worse than none -- the same shape as the stale caller attribution in the
|
||||
previous pass.
|
||||
3. Fixing (2) by joining lines dropped 500 signatures to 334, because a
|
||||
declaration preceded by a comment no longer started its record. Comments
|
||||
are stripped first now.
|
||||
|
||||
98/98 native, 5/5 arity_query.sh, fixpoint ok.
|
||||
```
|
||||
|
||||
## Record — `d9e301b`
|
||||
|
||||
```
|
||||
land arity-from-header: the runtime declares its own surface
|
||||
```
|
||||
@@ -1,52 +0,0 @@
|
||||
# one type erases the return
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `d2d89fc`
|
||||
|
||||
```
|
||||
EXPERIMENT: temporal types as data — and the pass that GREW the compiler
|
||||
|
||||
This block is structurally unlike the previous four. It does not only
|
||||
adjudicate, it DISPATCHES: Instant + Duration must become el_instant_add_dur,
|
||||
LocalDate + Duration must become el_local_date_add_dur. The emitted C depends on
|
||||
the type answer, so it cannot move to a post-hoc query. Selecting which call to
|
||||
emit is an emitter's actual job.
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 the block conflates dispatch with adjudication TRUE
|
||||
P2 adjudication can move, dispatch cannot TRUE
|
||||
P3 this pass shrinks codegen far less than the last TRUE, and worse:
|
||||
4513 -> 4537, it GREW
|
||||
by 24 lines
|
||||
P4 the rules are affine algebra, closed by construction TRUE
|
||||
P5 no type propagation -- name tracking plus a
|
||||
hardcoded list of which builtins return which type TRUE, 19 names
|
||||
|
||||
P3 is the honest result and it is not spun: moving 19 names into a data file
|
||||
cost more lines than it saved, because a generic loader is larger than the
|
||||
enumeration it replaces. The win is not line count. It is that adding a 20th
|
||||
temporal builtin is now a one-line edit to signatures.rel instead of a compiler
|
||||
change, and that the data is inspectable.
|
||||
|
||||
WHY THE HEADER CANNOT SUPPLY THIS, unlike arity: el_runtime.h declares every
|
||||
builtin as returning el_val_t, because El has ONE type. That single type is why
|
||||
the whole seam is cheap and it is exactly why the C boundary cannot say that
|
||||
now() returns an Instant while unix_seconds() returns an Int. The El-level type
|
||||
is real and the boundary erases it.
|
||||
|
||||
INCOMPLETE, and stated rather than hidden: P2 said adjudication could move to a
|
||||
query. It has NOT. Violations still emit TIME_TYPE_ERROR inline from the
|
||||
emitter. Only the type DATA moved. Moving the adjudication needs the operand
|
||||
types recorded as relations, which is a further pass.
|
||||
|
||||
98/98 native, 4/4 temporal_signatures.sh, fixpoint ok.
|
||||
```
|
||||
|
||||
## Record — `e01e079`
|
||||
|
||||
```
|
||||
land temporal signatures as data: the type table leaves, the dispatch stays
|
||||
```
|
||||
@@ -1,47 +0,0 @@
|
||||
# judgment lives with knowledge
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `e8e25a0`
|
||||
|
||||
```
|
||||
EXPERIMENT: temporal adjudication moves out; the placeholder stays
|
||||
|
||||
The previous pass moved the type DATA and left the judgment inline, which I
|
||||
stated rather than hid. This finishes it.
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 codegen can emit operand-type relations TRUE
|
||||
"main calls temporal:instant_plus_instant"
|
||||
P2 the affine rules are a small closed set as data TRUE 6 rules
|
||||
P3 violations still caught at build time TRUE exit=1
|
||||
P4 the reporter leaves codegen TRUE 4538 -> 4507
|
||||
P5 the TIME_TYPE_ERROR placeholder must STAY TRUE
|
||||
|
||||
P5 is the boundary of this whole approach. The emitter has to emit SOMETHING
|
||||
for an illegal expression -- it cannot emit nothing and it cannot decide what
|
||||
the program meant. So the placeholder is irreducible in the same way the AST
|
||||
traversal was: what moved is the judgment and the wording, not the fact that
|
||||
something must be written.
|
||||
|
||||
The rules are affine algebra and the set is closed because there are only two
|
||||
kinds of thing. An Instant is a POINT, a Duration is a DISPLACEMENT: add a
|
||||
displacement to a point, subtract two points for a displacement, combine
|
||||
displacements. Nothing else is meaningful, which is why the enumeration in
|
||||
temporal.rel cannot grow the way an allowlist does.
|
||||
|
||||
A defect in my own checker, found by running it: the .rel file uses aligned
|
||||
columns and my awk assumed a single space, so the message came out with the
|
||||
rule key still prefixed. Same class as the multi-line header parse in the arity
|
||||
pass -- formatting assumptions that only fail when you look at the output.
|
||||
|
||||
98/98 native, 6/6 temporal_query.sh, fixpoint ok.
|
||||
```
|
||||
|
||||
## Record — `50425f3`
|
||||
|
||||
```
|
||||
land temporal adjudication as a query: the emitter records, the rules are data
|
||||
```
|
||||
@@ -1,49 +0,0 @@
|
||||
# thirty five return types
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `cbef1c1`
|
||||
|
||||
```
|
||||
EXPERIMENT: Int return types as data — and the bug that fell out
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 is_int_call's 35 hardcoded names move to data TRUE
|
||||
P2 is_int_name stays -- it is annotation propagation TRUE
|
||||
P3 the dispatch stays -- it is emission TRUE
|
||||
P4 codegen shrinks ~40 lines TRUE 4507 -> 4469
|
||||
P5 the design doc's characterisation is WRONG TRUE
|
||||
P6 the moved data also fixes the bug it exposed TRUE
|
||||
|
||||
P5 CORRECTS THE RECORD. el-language-design.md and geometry-vs-code.md both cite
|
||||
"== lowering to str_eq unless both operand names are in a hardcoded int-name
|
||||
set -- a literal list of variable names treated as integers" as the paradigm
|
||||
defect. It is not one. __int_names is populated from TYPE ANNOTATIONS
|
||||
(param["type"] == "Int"), which is primitive but legitimate type propagation.
|
||||
The actual defect was is_int_call: 35 hardcoded builtin return types, the same
|
||||
shape as the temporal 19.
|
||||
|
||||
P6 IS A LIVE CORRECTNESS BUG, PRE-EXISTING, NOW FIXED
|
||||
|
||||
let a = str_len("hello") // no annotation
|
||||
let b = str_len("hi")
|
||||
let c = a + b // -> el_str_concat(a, b) on two integers
|
||||
|
||||
Verified identical on the pre-change compiler, so not a regression. It compiled
|
||||
clean, ran, and printed NOTHING where it should print 7. No error at any layer.
|
||||
|
||||
The repair is three lines: an unannotated let takes its type from what the
|
||||
initialiser returns. The return types were already required for dispatch and
|
||||
were simply never consulted at the binding site. Moving them into data is what
|
||||
made the gap visible -- reading the code for eight hours did not.
|
||||
|
||||
98/98 native + 2 new, 31/31 integration, fixpoint ok.
|
||||
```
|
||||
|
||||
## Record — `505e5e7`
|
||||
|
||||
```
|
||||
land int signatures, and repair a silent miscompilation they exposed
|
||||
```
|
||||
@@ -1,57 +0,0 @@
|
||||
# keywords that reserve nothing
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `0143cc4`
|
||||
|
||||
```
|
||||
ANSWER: is a grammar a convention, or a region?
|
||||
|
||||
Both, at different layers -- and it is the same split as serialization: the
|
||||
convention is the BASIS, never the ACT.
|
||||
|
||||
lexeme -> token `fn` means function-start because someone said so CONVENTION
|
||||
shape recognition given tokens, which construct is this REGION
|
||||
source -> structure parsing is transduction onto that basis GEOMETRY
|
||||
byte traversal something must read them in order IRREDUCIBLE
|
||||
|
||||
Three things push the ACT toward region rather than convention: ambiguity
|
||||
(a * b needs context; a grammar resolves it with the lexer hack, a region by
|
||||
neighbourhood), error recovery (nearest-region is free), and precedence, which
|
||||
is ordering along an axis with a conventional parameter.
|
||||
|
||||
AND THE SHOULD GATE SAYS NO TO THE OBVIOUS MOVE
|
||||
|
||||
Every other table this session moved to data. This one stays code. The keyword
|
||||
set is CLOSED by the language definition -- it does not leak the way an
|
||||
allowlist does -- and the lexer runs before the program is understood, so a
|
||||
program can never declare its own keywords. Externalising it costs file I/O on
|
||||
every compile and buys nothing. Same verdict as is_digit in ASCII.
|
||||
|
||||
WHAT WAS ACTUALLY WRONG: five of 46 keywords were consumed by no parser or
|
||||
codegen path. sealed, activate, seed, protocol, impl. Each stole an identifier
|
||||
from users for nothing.
|
||||
|
||||
SECOND SILENT MISCOMPILATION OF THE DAY. Using one did not fail to parse:
|
||||
|
||||
let seed = 42
|
||||
let impl = seed + 1
|
||||
|
||||
compiled CLEAN -- zero cc errors -- and printed 0 instead of 44. No diagnostic
|
||||
at any layer. Fixed by removing the five.
|
||||
|
||||
A DEFECT IN MY OWN MEASUREMENT, caught before it did damage: my first pass
|
||||
checked only parser.el and reported `test` as inert too. codegen consumes it at
|
||||
4135 for --test mode, and the tree has 408 uses. Removing it would have broken
|
||||
every test in the suite. The measurement was re-run across all four consumers.
|
||||
|
||||
100/100 native + 2 new, 31/31 integration, fixpoint ok.
|
||||
```
|
||||
|
||||
## Record — `067dd40`
|
||||
|
||||
```
|
||||
answer the parsing question: a grammar is a basis, and five keywords reserved nothing
|
||||
```
|
||||
@@ -1,53 +0,0 @@
|
||||
# no namespacing at all
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `79f6cb7`
|
||||
|
||||
```
|
||||
ANSWER: if the partition is a neighbourhood, does linking survive?
|
||||
|
||||
The question is premature, and measuring says why. El's partition is a
|
||||
FILESYSTEM PATH, not a neighbourhood, and there is no namespacing at all.
|
||||
|
||||
MEASURED
|
||||
import is textual inlining (resolve_imports), guarded against double
|
||||
inclusion by a __elc_imp__:<path> state key
|
||||
when a .elh header exists the header is inlined instead and the .el is marked
|
||||
seen, so symbols resolve at C link time -- so linking IS real, delegated to C
|
||||
two modules defining `helper` emit two C functions into one translation unit
|
||||
|
||||
So linking barely survives the PATH partition. Whether it survives a
|
||||
neighbourhood partition cannot be asked yet.
|
||||
|
||||
A DIAGNOSTIC REGRESSION I CAUSED, found by asking this question. cc does catch
|
||||
the collision, but reports:
|
||||
|
||||
error: redefinition of '__el_body_helper'
|
||||
error: redefinition of '__env_helper'
|
||||
error: redefinition of '__thunk_helper'
|
||||
error: redefinition of 'helper'
|
||||
|
||||
The user's own function is FOURTH. The first three are generated symbols
|
||||
introduced by the unconditional-wrapper pass earlier today -- before it, there
|
||||
was one clear message. Repaired by catching the collision at El level instead:
|
||||
|
||||
duplicate definition: 'helper' is defined 2 times — El has no namespacing,
|
||||
so imported modules share one global scope
|
||||
|
||||
LIMIT, stated rather than hidden: textual inlining destroys file provenance. By
|
||||
the time codegen runs there is one source string, so the message can say WHICH
|
||||
name collides but not which files. Naming a.el and b.el needs provenance
|
||||
threaded through resolve_imports.
|
||||
|
||||
104/104 native, 4/4 definitions_query.sh, the compiler itself reports clean,
|
||||
fixpoint ok.
|
||||
```
|
||||
|
||||
## Record — `f23cb2b`
|
||||
|
||||
```
|
||||
answer the module question: the partition is a path, and there is no namespacing
|
||||
```
|
||||
@@ -1,64 +0,0 @@
|
||||
# tokens carry no position
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `6c975b1`
|
||||
|
||||
```
|
||||
thread provenance through resolve_imports
|
||||
|
||||
The module question ended with a limit: textual inlining destroys file
|
||||
provenance, so a duplicate-definition message could name the symbol but not the
|
||||
files. Threading it exposed a bigger absence first.
|
||||
|
||||
TOKENS HAD NO POSITION AT ALL. A token was a flat (kind, value) pair, so NO
|
||||
diagnostic in El could name a place -- every error named a symbol and never a
|
||||
line. That is the prerequisite the module question was resting on.
|
||||
|
||||
THE CHAIN, end to end
|
||||
lexer counts newlines; tok_append mints (kind, value, line)
|
||||
parser stride 2 -> 3; tok_line added; FnDef carries its line
|
||||
codegen records <fn> defines_at:<line>
|
||||
resolve_imports publishes <file> spans <start> <end> for the combined source
|
||||
checker maps a combined line back to file:line-within-that-file
|
||||
|
||||
duplicate definition: 'helper' is defined 2 times — El has no namespacing,
|
||||
so imported modules share one global scope
|
||||
/tmp/modtest/a.el:1
|
||||
/tmp/modtest/b.el:1
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 15 stride sites, encapsulated in tok_kind/tok_value TRUE, but see below
|
||||
P2 adding a line field is mechanical TRUE
|
||||
P3 the lexer must count newlines TRUE
|
||||
P4 resolve_imports can record per-file line ranges TRUE
|
||||
P5 the message can then name both files TRUE
|
||||
P6 token memory grows TRUE, 25.0 -> 33.9 MB (+36%)
|
||||
|
||||
FOUR DEFECTS, EACH FOUND BY RUNNING AND NOT BY READING
|
||||
|
||||
1. interp_tokens_append_all walks the token list DIRECTLY with its own copy of
|
||||
the stride. Gen1 built fine and gen2 emitted corrupt C, because the
|
||||
compiler's own source uses string interpolation. My search missed it because
|
||||
I grepped for the variable name `tokens`; it is called `dst`/`result`.
|
||||
Searching by name instead of by shape -- third time today.
|
||||
2. tok_count in test_compiler.el carried the stride too. I had scoped the search
|
||||
to compiler sources and it had escaped into the tests.
|
||||
3. Nested resolve_imports calls accumulated spans into shared state, so each
|
||||
republished meaningless line ranges under the parent's name. Making the
|
||||
buffer local fixed it; guarding the WRITE did not, which is what I tried
|
||||
first.
|
||||
4. The first working version reported b.el:3 -- the COMBINED line against a
|
||||
filename that has no line 3. A file:line that does not match the file is
|
||||
worse than no line at all.
|
||||
|
||||
105/105 native, 37/37 integration, fixpoint ok, compiler self-checks clean.
|
||||
```
|
||||
|
||||
## Record — `cb7289f`
|
||||
|
||||
```
|
||||
thread provenance end to end: a diagnostic can finally name a place
|
||||
```
|
||||
@@ -1,53 +0,0 @@
|
||||
# annotations are never checked
|
||||
|
||||
**Status: verified on `experiment/annotation-checking`, not merged.**
|
||||
|
||||
## Ishikawa — why does El silently miscompile?
|
||||
|
||||
Three bugs found the same day shared one shape.
|
||||
|
||||
```
|
||||
method type tracked by per-function name sets, fed from annotations
|
||||
machine el_val_t erases everything at the C boundary
|
||||
material no propagation through expressions
|
||||
measurement nothing verifies an annotation against what it annotates
|
||||
─────────────────────────────────────────────────────────────────────────
|
||||
root cause El has type ANNOTATIONS but no type CHECKING. The annotation
|
||||
feeds dispatch and is never itself verified.
|
||||
```
|
||||
|
||||
## Predictions
|
||||
|
||||
```
|
||||
P1 let x: Int = "hello" compiles clean expect TRUE
|
||||
P2 let s: String = 42 compiles clean expect TRUE
|
||||
P3 the annotation drives dispatch, unverified expect TRUE
|
||||
P4 same root cause as all three bugs found today expect TRUE
|
||||
P5 checking literal-vs-annotation catches both expect TRUE
|
||||
P6 zero false positives across the compiler's source expect TRUE
|
||||
```
|
||||
|
||||
## Results — 6/6, and worse than a wrong answer
|
||||
|
||||
```
|
||||
let x: Int = "hello"; x + 1 → 4343631981 a string POINTER used as an integer
|
||||
let s: String = 42; println(s) → nothing address 42 dereferenced as a string
|
||||
```
|
||||
|
||||
The first **leaks a raw memory address into program output**. The second is an
|
||||
**arbitrary-read primitive** if that integer is ever attacker-influenced.
|
||||
|
||||
Verified: 6/6, zero false positives across the compiler's own source, fixpoint
|
||||
ok, 105/105 native.
|
||||
|
||||
## Six Sigma
|
||||
|
||||
The emitter only **records** the mismatch; `tools/check/annotations.sh` decides —
|
||||
consistent with every other check. Literals are checked because they are
|
||||
unambiguous.
|
||||
|
||||
**Incomplete, stated not hidden:** only literals. `let x: Int = some_string_fn()`
|
||||
still passes, because `signatures.rel` carries Int/Instant/Duration and no
|
||||
String entries. That is a data gap, not a capability limit — every El function
|
||||
declares its return type in source and codegen already holds `ret_type` on every
|
||||
`FnDef`.
|
||||
@@ -1,88 +0,0 @@
|
||||
# async — half expressible, and the cycle that was dogma
|
||||
|
||||
**Status: replicated and corroborated. Three runs — the first was invalid.**
|
||||
|
||||
> **Chain of custody note, 2026-08-17.** The original measurements were produced
|
||||
> by a C stub written in `/tmp`, and that artifact was destroyed when the session
|
||||
> worktrees were removed. For a period this file asserted results with nothing
|
||||
> behind them — a claim inside an evidence record, which is the defect that turns
|
||||
> a chain into a pile. It was **rerun**, not reconstructed: reconstructing the
|
||||
> missing file would have been a fabrication with a fresh timestamp.
|
||||
>
|
||||
> The fixture now lives at `lang/tests/integration/fixtures/future.c` and the
|
||||
> harness at `lang/tests/integration/async_future.sh`, so a third party can
|
||||
> reproduce this without taking my word for it. **6/6.**
|
||||
>
|
||||
> The replication is labelled as such: the outcomes were already known when the
|
||||
> harness was written, so its expectations are not predictions committed in
|
||||
> advance. Its value is reproducibility, not foresight.
|
||||
|
||||
## The first attempt was DOGMA, not science
|
||||
|
||||
I had just finished arguing that `@async` was expressible, then ran something to
|
||||
confirm it. **No prediction was committed.** The test was rigged in a way that
|
||||
should have been visible while writing it:
|
||||
|
||||
```c
|
||||
pthread_create(&t,NULL,runner,NULL); pthread_join(t,NULL);
|
||||
```
|
||||
|
||||
`join` immediately after `create` — the caller blocks until the body finishes.
|
||||
That is a thread round-trip, not deferral. And the test printed the word
|
||||
`DEFERRED` itself: I wrote the conclusion into the output and read it back.
|
||||
|
||||
```
|
||||
Ishikawa on the rigged test
|
||||
method ran after concluding, not to decide
|
||||
machine nothing forces a prediction before execution
|
||||
material the assertion was written into the output string
|
||||
measurement no falsification criterion existed, so nothing could fail
|
||||
root cause the test was authored by the party holding the conclusion,
|
||||
with no commitment made before it ran
|
||||
```
|
||||
|
||||
Discarded and re-run properly.
|
||||
|
||||
## Second run — predictions committed first
|
||||
|
||||
```
|
||||
P1 the caller proceeds while the body runs expect TRUE
|
||||
P2 interleaving is observable in timestamps expect TRUE
|
||||
P3 the result cannot be retrieved — one 64-bit slot, no
|
||||
future type, so the wrap either blocks or returns
|
||||
something that is not the result expect TRUE
|
||||
P4 therefore HALF expressible: fire-and-forget yes, await no expect TRUE
|
||||
```
|
||||
|
||||
## Results — 4/4
|
||||
|
||||
```
|
||||
[ 18 us] wrap RETURNS to caller
|
||||
[ 29 us] body START
|
||||
caller continues, got 0
|
||||
[ 50176 us] body END (computed 42)
|
||||
caller done
|
||||
```
|
||||
|
||||
The caller got **0, not 42**. Both of my earlier claims were wrong in opposite
|
||||
directions: "not expressible" was too strong — fire-and-forget works today,
|
||||
bound after the build, no compiler change. "Expressible" was too strong the
|
||||
other way.
|
||||
|
||||
## Follow-on cycle — a future is one more tagged object
|
||||
|
||||
```
|
||||
P1 el_val_t already carries tagged heap objects TRUE 5 magic tags exist
|
||||
P2 a future is one more TRUE
|
||||
P3 the caller awaits and gets 42 TRUE
|
||||
P4 ZERO compiler changes TRUE runtime C + one binding
|
||||
P5 the unbound path still works FALSE SIGSEGV
|
||||
```
|
||||
|
||||
**P4 is the result.** `@async` — called unexpressible for hours — needs no
|
||||
compiler change. A future is one more magic-tagged heap object; `defer` returns
|
||||
the handle, `el_await` blocks.
|
||||
|
||||
**P5 is the failure that mattered.** Sixty seconds after diagnosing
|
||||
`let s: String = 42` as an arbitrary read, I wrote the identical defect into
|
||||
`el_await`: reading `->magic` off an unvalidated slot. That opened cycle 19.
|
||||
@@ -1,61 +0,0 @@
|
||||
# a convention is not a gate
|
||||
|
||||
One `Ishikawa → scientific method → Six Sigma` loop. The record below is the
|
||||
commit message as written at the time, before the outcome was known to anyone
|
||||
reading this file.
|
||||
|
||||
## Record — `9a6c161`
|
||||
|
||||
```
|
||||
a slot must be validated before it is dereferenced
|
||||
|
||||
ISHIKAWA: el_val_t carries integers AND tagged heap pointers, so "is this a
|
||||
pointer" is undecidable without checking first. That check was a CONVENTION
|
||||
every author had to know rather than a GATE they had to pass through, and
|
||||
looks_like_heap_obj was static -- so every sibling translation unit re-derived
|
||||
it.
|
||||
|
||||
MEASURED, across the five existing tags
|
||||
geom_of looks_like_heap_obj full guard correct
|
||||
mfld_of looks_like_heap_obj full guard correct
|
||||
el_bin_lookup (uintptr_t)p < 4096 floor only reads 8 bytes BACKWARD
|
||||
el_input_len s ? ... : 0 NULL only strlen's an integer
|
||||
|
||||
sha256_hex(50000) -> exit 139, SIGSEGV, compiled clean
|
||||
|
||||
PREDICTIONS AND RESULTS
|
||||
P1 looks_like_heap_obj is static, not exported TRUE
|
||||
P2 each tagged type re-derives the check TRUE
|
||||
P3 at least one is missing guard components TRUE (two are)
|
||||
P6 sha256_hex(<int>) reads out of bounds TRUE
|
||||
P8 routing el_bin_lookup through the gate fixes it FALSE
|
||||
P9 the legitimate hash is unchanged TRUE
|
||||
P11 fixpoint and suites hold TRUE
|
||||
|
||||
P8 IS THE USEFUL FAILURE. Guarding the tagged lookup changed nothing --
|
||||
looks_like_heap_obj(49992) correctly returns 0, el_bin_lookup bails, and then
|
||||
el_input_len falls through to strlen() on address 50000. The FALLBACK was the
|
||||
hazard, not the tagged path. A NULL check does not establish that a slot is a
|
||||
pointer. I would have shipped the wrong fix and called it verified.
|
||||
|
||||
A MEASUREMENT DEFECT, fourth today: my first run of the crash reported exit=0,
|
||||
because $? read head's exit through a pipe rather than the program's. I nearly
|
||||
recorded a segfault as a clean run. Same shape as grepping only parser.el and
|
||||
searching by variable name instead of by operation.
|
||||
|
||||
AND I PROVED THE HAZARD FROM THE INSIDE. Sixty seconds after diagnosing
|
||||
`let s: String = 42` as an arbitrary-read primitive, I wrote the identical
|
||||
defect into el_await -- dereferencing ->magic off an unvalidated slot -- and
|
||||
only then found the runtime had already made it twice.
|
||||
|
||||
el_tagged() is now exported in el_runtime.h. Anything that dereferences a slot
|
||||
without passing through it is the defect.
|
||||
|
||||
105/105 native, 42/42 integration across eight harnesses, fixpoint ok.
|
||||
```
|
||||
|
||||
## Record — `3049a70`
|
||||
|
||||
```
|
||||
make the guard a gate: sha256_hex(50000) no longer segfaults
|
||||
```
|
||||
@@ -1,12 +0,0 @@
|
||||
# Architecture questions closed
|
||||
|
||||
All five were open in `geometry-vs-code.md`. Each was closed by measurement, not
|
||||
by argument.
|
||||
|
||||
| Question | Answer |
|
||||
|---|---|
|
||||
| **Concurrency** — hardware threads are code, but is *ordering* geometric? | **Ordering is geometric.** Causality is a partial order (Lamport 1978); a total order is an arbitrary extension that "cannot be depended on to imply a causal relationship." Languages force a total order at authoring time, so every lock, barrier and fence is apparatus for recovering the partial order that was destroyed. CALM: a program has a coordination-free implementation **iff monotone**. What breaks monotonicity is destructive update. **Coordination is the price of forgetting.** |
|
||||
| **Error handling** — does `grounded: false` cover *failed*? | **No.** Standing is a *signed* component: `>0` supported, `=0` unknown, `<0` contradicted. Not-known and known-false are opposite directions on one axis; a boolean cannot tell them apart. `inhibitory` as an int32 flag is that sign wearing a boolean. |
|
||||
| **Parsing** — is a grammar a convention, or a region? | **A grammar is a basis; parsing is transduction onto it.** Lexeme→token is convention, shape recognition is a region, byte traversal is irreducible. **But the SHOULD gate refused the obvious move:** the keyword table stays code, because the set is closed by the language definition and the lexer runs before the program is understood. Same verdict as `is_digit` in ASCII. |
|
||||
| **Numeric literals** — is `3` a position or a convention? | **The numeral is convention; the number is a position — and a bare `3` is a magnitude with no axis.** It is not a position until something gives it a direction, which is why `3.days` needs a calendar. Demonstrated: `t + 3` refused, `t + 1.hour` accepted. |
|
||||
| **The module system** — if the partition is a neighbourhood, does linking survive? | **Premature.** The partition is a filesystem path and there is no namespacing at all. `import` is textual inlining; with a `.elh` header, symbols resolve at C link time. Two modules defining `helper` emit two C functions into one translation unit. Linking barely survives the *path* partition. |
|
||||
@@ -1,74 +0,0 @@
|
||||
# Live defects found
|
||||
|
||||
Every one compiled clean, ran, and produced a wrong result or a crash with **no
|
||||
diagnostic at any layer**. All four were present before this session; none was
|
||||
introduced by it.
|
||||
|
||||
## Silent miscompilations
|
||||
|
||||
### 1. An unannotated `let` loses its type
|
||||
|
||||
```el
|
||||
let a = str_len("hello") // no annotation
|
||||
let b = str_len("hi")
|
||||
let c = a + b // → el_str_concat(a, b) on two integers
|
||||
```
|
||||
|
||||
Compiled clean. Printed **nothing** where it should print 7. Fixed: an
|
||||
unannotated `let` takes its type from what its initialiser returns. The return
|
||||
types were already required for dispatch and were simply never consulted at the
|
||||
binding site.
|
||||
|
||||
### 2. Reserved keywords that reserved nothing
|
||||
|
||||
```el
|
||||
let seed = 42
|
||||
let impl = seed + 1
|
||||
```
|
||||
|
||||
`sealed`, `activate`, `seed`, `protocol`, `impl` were keywords in the lexer and
|
||||
consumed by no parser or codegen path. Using one did not fail to parse — it
|
||||
compiled clean, with zero `cc` errors, and printed **0 instead of 44**. Fixed by
|
||||
removing all five.
|
||||
|
||||
### 3. `Instant + Int` was never refused
|
||||
|
||||
```el
|
||||
let t: Instant = now()
|
||||
let u: Instant = t + 3 // → (t + 3), reported clean
|
||||
```
|
||||
|
||||
`Duration + Int` was refused — *"an Int carries no unit"* — while adding a
|
||||
dimensionless number to a **point** silently moved the instant by an
|
||||
unspecified amount. Three of *what*? Whatever the representation happens to be.
|
||||
The rule was simply never written.
|
||||
|
||||
## Security-relevant
|
||||
|
||||
### 4. Annotations are never verified
|
||||
|
||||
```el
|
||||
let x: Int = "hello"; x + 1 → 4343631981 a string POINTER used as an integer
|
||||
let s: String = 42; println(s) → nothing address 42 dereferenced
|
||||
```
|
||||
|
||||
The first **leaks a raw memory address into program output**. The second is an
|
||||
**arbitrary-read primitive** if the integer is ever attacker-influenced.
|
||||
|
||||
### 5. `sha256_hex(<integer>)` segfaults
|
||||
|
||||
```el
|
||||
let h: String = sha256_hex(50000) → exit 139, SIGSEGV
|
||||
```
|
||||
|
||||
Compiled clean. `el_bin_lookup` checked only a 4096 floor — no alignment, no
|
||||
small-int, no negative — and reads **eight bytes backward** from the pointer.
|
||||
And the actual crash was one level further on: `el_input_len` fell through to
|
||||
`strlen()` on address 50000, because a NULL check does not establish that a slot
|
||||
is a pointer.
|
||||
|
||||
Fixed, and the guard is now a **gate**: `el_tagged()` is exported in
|
||||
`el_runtime.h`. `geom_of` and `mfld_of` were always correct because their authors
|
||||
knew to call `looks_like_heap_obj`; `el_bin_lookup` and `el_input_len` were wrong
|
||||
because theirs did not, and the function was `static`, so every sibling
|
||||
translation unit re-derived it.
|
||||
@@ -1,62 +0,0 @@
|
||||
# Defects in my own measurement
|
||||
|
||||
Recorded because the pattern is the point: **five of these, and every one is the same shape —
|
||||
reading a proxy instead of the thing.** A file instead of the operation, a
|
||||
variable name instead of the shape, a scope instead of the whole, a pipe's exit
|
||||
instead of the program's, a line count instead of the object identity. Each was caught
|
||||
by running something, never by reading.
|
||||
|
||||
### 1. Scoped the search to one file
|
||||
|
||||
Reported `test` as an inert keyword by checking only `parser.el`. **codegen**
|
||||
consumes it at 4135 for `--test` mode, and the tree has 408 uses. Removing it
|
||||
would have broken every test in the suite — including the ones used to verify
|
||||
the removal.
|
||||
|
||||
### 2. Searched by variable name, not by operation
|
||||
|
||||
Grepped for `native_list_append(tokens` to find direct token appends.
|
||||
`interp_tokens_append_all` calls its parameters `dst`/`result`, carries its own
|
||||
copy of the stride, and corrupted generation 2 — while generation 1 built fine,
|
||||
because the compiler's own source uses string interpolation.
|
||||
|
||||
### 3. Scoped to compiler sources; the stride had escaped into tests
|
||||
|
||||
`tok_count` in `test_compiler.el` computed `len/2` independently. 21 tests failed
|
||||
after the token layout changed.
|
||||
|
||||
### 4. Read the wrong exit code
|
||||
|
||||
```bash
|
||||
timeout 10 /tmp/leakrun 2>&1 | head -2; echo "exit=$?" # reports head's exit
|
||||
```
|
||||
|
||||
Reported `exit=0` for a program that was returning **139 (SIGSEGV)**. I nearly
|
||||
recorded a segfault as a clean run.
|
||||
|
||||
### 5. Read a count that was not counting
|
||||
|
||||
Comparing the three promoted branches:
|
||||
|
||||
```bash
|
||||
for pair in "dev stage" ...; do set -- $pair
|
||||
n=$(git diff --stat origin/$1 origin/$2 | wc -l) # git errored to STDERR
|
||||
... # wc counted empty STDOUT
|
||||
```
|
||||
|
||||
`git diff` failed on a malformed revision, wrote its error to stderr, and `wc -l`
|
||||
counted zero lines of stdout. Three confident `IDENTICAL` results, all
|
||||
meaningless. **Had the trees actually differed, I would have reported the
|
||||
promotion clean.**
|
||||
|
||||
Redone correctly, the three trees share one hash — `2acd9374` — which is the
|
||||
check that should have been run first: not "how many files differ" but "is the
|
||||
tree object the same object".
|
||||
|
||||
### And one that was not a measurement defect but a method defect
|
||||
|
||||
One cycle was run **without committing predictions first** — see
|
||||
`cycles/18-async-half-expressible.md`. The test joined the thread immediately
|
||||
after creating it and printed the word `DEFERRED` itself. A test authored by the
|
||||
party holding the conclusion, with nothing committed beforehand, cannot fail.
|
||||
It had to be discarded and re-run.
|
||||
@@ -1,65 +0,0 @@
|
||||
# ELP language consolidation — full-lexicon backfill (stage)
|
||||
|
||||
Branch: `stage-elp-lang-consolidation` (stage-bound; NOT the live soul :8742).
|
||||
|
||||
Consolidates scattered Python language-realizer work (`~/Desktop/lang-realizers`,
|
||||
`~/Desktop/lang-poetry-experiment`, `~/semitic_engine`) into the ELP `.el`
|
||||
structure, generating **full lexicons** (complete UniMorph + kaikki.org
|
||||
Wiktionary — real gender, real inflections) instead of the demo/curated subsets
|
||||
the prototypes shipped.
|
||||
|
||||
## ELP before this branch
|
||||
- 18 classical/ancient languages fully done (vocab + morphology + tests):
|
||||
akk ang cop egy enm fro gez goh got grc non peo pi sa sga sux txb uga.
|
||||
- 11 modern/classical languages had `morphology-<code>.el` in the build manifest
|
||||
but **no vocabulary and no lang_profile**: es fr de ja ar he hi ru fi sw la.
|
||||
- The ES port (`stage-elp-es-port`) had a *demo-scale* vocabulary-es.el (~350
|
||||
entries, s-expr form).
|
||||
|
||||
## Landed on this branch (full-lexicon seed-fn format, matching the 18 ancients)
|
||||
Vocabulary schema per row: `[lemma, pos, form0, form1, form2, en_gloss, hint]`.
|
||||
Files are ELP runtime **seed data** (loaded via the Engram at runtime), so — like
|
||||
all 18 classical `vocabulary-*.el` — they are intentionally NOT in the build
|
||||
manifest. Syntax validated: the chunked `fn vocab_<code>_seed_pN` format
|
||||
compiles cleanly to C via `elc` (correct UTF-8).
|
||||
|
||||
| code | in-ELP-morph? | vocab entries | verbs | nouns | adjs | profile |
|
||||
|------|---------------|--------------:|------:|------:|-----:|---------|
|
||||
| es | yes | 72,032 | 6,695 | 48,353 | 16,984 | yes |
|
||||
| fr | yes | 130,517 | 7,534 | 77,344 | 45,639 | yes |
|
||||
| de | yes | 144,692 | 6,661 | 133,162 | 4,869 | yes |
|
||||
| la | yes | 22,590 | 82 | 13,436 | 9,072 | yes |
|
||||
| it | no (bonus) | 193,675 | 10,008 | 109,459 | 74,208 | yes |
|
||||
| pt | no (bonus) | 115,772 | 4,001 | 72,073 | 39,698 | yes |
|
||||
| ro | no (bonus) | 86,504 | 1,216 | 65,915 | 19,373 | yes |
|
||||
| ca | no (bonus) | 47,112 | 1,547 | 28,830 | 16,735 | yes |
|
||||
|**total**| |**812,894** | | | | |
|
||||
|
||||
Generators (reproducible): `elp/tests/lang-gen/gen_elp_seed_full.py` (Romance),
|
||||
`gen_elp_seed_de_la.py` (German declension + Latin case-paradigm mapping). They
|
||||
read the pre-built morph caches in `~/Desktop/lang-realizers/data/` (UniMorph +
|
||||
kaikki), which are too large to commit.
|
||||
|
||||
## Remaining (honest)
|
||||
Of the 11 ELP backfill targets, 4 are done (es fr de la). The other 7 have **no
|
||||
full-lexicon engine** yet — cannot be generated honestly without engine work:
|
||||
- **ru**: only a 110-entry curated Slavic subset exists; full `rus.unimorph`
|
||||
present but no `morphology_ru_full` productive loader. Needs a full Russian
|
||||
morphology module (like the Romance ones) before vocab generation.
|
||||
- **ja / ko / zh**: validated demo engines (~66-104 hardcoded words) in
|
||||
`lang-poetry-experiment`, Python only. Agglutinative (ja/ko) + isolating (zh)
|
||||
need `.el` engine ports + full-lexicon wiring (ja: jpn_unimorph; zh: CC-CEDICT).
|
||||
- **ar / he (Semitic)**: template engines (16 AR / 8 HE patterns, ~6 roots) in
|
||||
`~/semitic_engine`, Python only. Root-and-pattern; full UniMorph ara/heb
|
||||
present but used only for validation. Needs productive root lexicon + `.el` port.
|
||||
- **hi (Hindi), fi (Finnish), sw (Swahili)**: `morphology-<code>.el` exists in
|
||||
ELP but there is NO scattered prototype and NO downloaded data for these —
|
||||
full-lexicon collection (UniMorph/kaikki) + generator still to do.
|
||||
|
||||
De/nl/sv Germanic and it/ro/ca/pt Romance verb coverage note: German verbs here
|
||||
are the ~6.6k caches carry; the it/ro/ca/pt bonus languages have full vocab but
|
||||
**no `morphology-<code>.el` in ELP yet** (Python realizer exists; `.el` port is
|
||||
the remaining engine work).
|
||||
|
||||
Construction coverage (separate from lexicon): French realizer was ~55%,
|
||||
Semitic ~3% in the prototypes — full construction coverage remains its own task.
|
||||
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.
@@ -80,11 +80,6 @@ build {
|
||||
"src/grammar.el",
|
||||
"src/realizer.el",
|
||||
"src/semantics.el",
|
||||
"src/comprehend.el",
|
||||
"src/propositions.el",
|
||||
"src/multilingual.el",
|
||||
"src/self_region.el",
|
||||
"src/dialogue.el",
|
||||
"src/elp.el",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
> **STATUS: STAGING / PROOF-OF-SHAPE — not the deliverable.** This Python package
|
||||
> proved the architecture end-to-end against the proven realizer faculty (faithful
|
||||
> md/docx/midi from real geometry: 0 ungrounded claims, SACRED polarity). Per Will's
|
||||
> steer, the DELIVERABLE is NATIVE: the seam lives on the existing EL realizer as
|
||||
> **surface-as-profile** — see `../src/surface-profile.el` and
|
||||
> `../tests/examples/surface-profile-demo.el` (compiles + runs through elc → C →
|
||||
> binary). The concepts below (one geometry-carrying frame; surface = a pluggable
|
||||
> profile; plan/realize; deterministic-from-meaning) are exactly what the native
|
||||
> module implements. Keep this package as the validated proof; build native.
|
||||
|
||||
# Efferent Multimodal Projector
|
||||
|
||||
**geometry → any surface, faithfully.** Neuron's own document-generation faculty:
|
||||
the efferent twin of the ingest organ. Ingest is afferent (world → geometry);
|
||||
this is efferent (geometry → an arbitrary-format document / any modality).
|
||||
|
||||
Built against the **proven** realizer faculty (neuron-talk sidecar `:8756`,
|
||||
artifact `art-7affa557`). The live soul (`:8742` / `:7770`) is contacted **only**
|
||||
through the read-only, GET-only `engram_client` — never mutated.
|
||||
|
||||
## The pipeline (surface-agnostic)
|
||||
|
||||
```
|
||||
geometry region + surface/format spec
|
||||
→ PLAN (manifold → document skeleton/DAG; the geometry IS the outline) plan.py
|
||||
→ REALIZE (proven realizer, scaled sentence → passage, each section faithful) realize.py
|
||||
→ COHERE (document-level flow / transitions, not stitched sentences) cohere.py
|
||||
→ EMIT (pluggable SurfaceProjector → the target surface) projectors/
|
||||
```
|
||||
|
||||
**The surface is a PARAMETER.** `pipeline.build_ir(...)` builds ONE
|
||||
surface-neutral `DocumentIR` (`document_ir.py`); `pipeline.emit(doc, surface)`
|
||||
projects it to whichever surface you name. Markdown, docx, and MIDI are the same
|
||||
IR emitted three ways.
|
||||
|
||||
## The pivot: a geometry-carrying IR
|
||||
|
||||
`DocumentIR` is **not** a text tree. Every `Block` carries BOTH:
|
||||
- `.sentences` — realized faithful text (what **text** projectors read),
|
||||
- `.provenance` — the source geometry: `subj_id / relation / obj / polarity /
|
||||
confidence / importance / salience / node_id` (what **music / image / video**
|
||||
projectors read).
|
||||
|
||||
That single decision is what makes the projector multimodal: text renders the
|
||||
words; music/image decode the geometry. A claim with no provenance cannot exist
|
||||
in the IR — faithfulness is structural.
|
||||
|
||||
## The one shared seam
|
||||
|
||||
`projectors/base.py` — `SurfaceProjector.project(frame: DocumentIR) -> bytes`
|
||||
(+ `surface / media_type / ext / modality / profile`). Register with
|
||||
`register()`. Adding a surface changes nothing upstream.
|
||||
|
||||
`TwoStageProjector` blesses the peer plan/realize decomposition:
|
||||
`spec = plan(frame)`, `bytes = realize(spec)`, `project = realize∘plan`; the
|
||||
`profile` is the pluggable per-surface knob (text lang-profile, music
|
||||
instr/mode-profile). `projectors/midi.py` is the reference two-stage impl.
|
||||
|
||||
## Surfaces
|
||||
|
||||
| surface | modality | status | emitter |
|
||||
|---|---|---|---|
|
||||
| `markdown` | text | landed | own (str) |
|
||||
| `docx` | text | landed | own minimal OOXML (stdlib `zipfile`+XML, no lib) |
|
||||
| `midi` | audio | landed (symbolic-music proof) | own minimal SMF (stdlib `struct`, no lib) |
|
||||
| `audio` (WAV) | audio | peer agent (additive synth) | conforms to `TwoStageProjector` |
|
||||
| `image` | image | documented seam | `projectors/seams.py` |
|
||||
| `video` | video | documented seam (image×sound×time) | `projectors/seams.py` |
|
||||
|
||||
Music maps: relation → scale degree (same relation → same pitch), **polarity →
|
||||
major/minor third (SACRED negation is audible)**, confidence → duration,
|
||||
importance → velocity, section → register. Deterministic projection from meaning
|
||||
— nothing invented.
|
||||
|
||||
## Faithfulness
|
||||
|
||||
`provenance.py` audits the IR: **zero** ungrounded claims, SACRED polarity
|
||||
preserved (negations reported, never dropped), COHERE introduces no new geometry
|
||||
(connectives are marked). `trace_table()` emits the geometry → section → claim
|
||||
table.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
PY=~/Desktop/lang-realizers/venv/bin/python
|
||||
PYTHONPATH=~/Desktop/neuron-talk:~/Desktop/lang-realizers $PY generate.py
|
||||
# writes ./out/{neuron-self,engram-temporal}.{md,docx,mid} + *.audit.json + *.provenance.md
|
||||
```
|
||||
|
||||
Requires the proven realizer env (spaCy + the neuron-talk/lang-realizers engine)
|
||||
and the read-only engram at `:8742`.
|
||||
@@ -1,79 +0,0 @@
|
||||
"""cohere.py — COHERE stage: document-level flow, not stitched sentences.
|
||||
|
||||
Fidelity is REALIZE's job; FLOW is this stage's. The hard part beyond sentence
|
||||
fidelity is that a document must read as one thing. We add connective tissue at
|
||||
the passage level:
|
||||
|
||||
* an opening abstract that names what the document covers (built ONLY from the
|
||||
section headings that already exist — it introduces no new claim),
|
||||
* a short transition lead into each section after the first, drawn from a
|
||||
fixed set of discourse connectives ("Beyond that,", "Relatedly,", ...) that
|
||||
carry no propositional content,
|
||||
* ordering so the highest-grounded section leads.
|
||||
|
||||
CRITICAL: every connective is marked ``kind="connective"`` in its provenance, so
|
||||
the faithfulness audit can prove COHERE introduced ZERO new geometry claims. A
|
||||
transition is discourse glue, never a fact.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from document_ir import Block, DocumentIR, Provenance
|
||||
|
||||
# discourse connectives — pure flow, no propositional content
|
||||
_TRANSITIONS = [
|
||||
"Beyond that,", "Relatedly,", "In the same region,", "From there,",
|
||||
"Alongside this,", "Further,", "Turning to the next facet,",
|
||||
]
|
||||
|
||||
|
||||
def _connective_prov() -> Provenance:
|
||||
return Provenance(subj_id=None, subject=None, relation="", obj=None,
|
||||
polarity="aff", confidence=1.0, node_id=None,
|
||||
kind="connective")
|
||||
|
||||
|
||||
def _abstract_block(doc: DocumentIR) -> Block:
|
||||
"""A grounded opening: names the sections, asserts nothing new."""
|
||||
headings = [s.heading for s in doc.sections]
|
||||
if not headings:
|
||||
return Block(role="lead")
|
||||
if len(headings) == 1:
|
||||
body = f"This document, generated from Neuron's geometry, covers {headings[0]}."
|
||||
else:
|
||||
listed = ", ".join(headings[:-1]) + f", and {headings[-1]}"
|
||||
body = ("This document is projected directly from Neuron's meaning-geometry. "
|
||||
f"It traces {listed}.")
|
||||
b = Block(role="lead")
|
||||
b.sentences.append(body)
|
||||
b.provenance.append(_connective_prov())
|
||||
return b
|
||||
|
||||
|
||||
def cohere_document(doc: DocumentIR, *, add_abstract: bool = True,
|
||||
add_transitions: bool = True) -> DocumentIR:
|
||||
"""Order sections by grounding, add abstract + transitions (flow only)."""
|
||||
# order: strongest-grounded section (mean confidence x #claims) first,
|
||||
# but keep an explicitly-first section if the plan pinned one via level 1.
|
||||
def _score(sec):
|
||||
provs = [p for p in sec.all_provenance() if p.kind == "fact"]
|
||||
if not provs:
|
||||
return 0.0
|
||||
mean_conf = sum(p.confidence for p in provs) / len(provs)
|
||||
return mean_conf * len(provs)
|
||||
|
||||
doc.sections.sort(key=_score, reverse=True)
|
||||
|
||||
if add_transitions:
|
||||
for i, sec in enumerate(doc.sections):
|
||||
if i == 0 or not sec.blocks:
|
||||
continue
|
||||
lead = _TRANSITIONS[(i - 1) % len(_TRANSITIONS)]
|
||||
first = sec.blocks[0]
|
||||
if first.sentences:
|
||||
# prepend the connective to the first sentence (flow, no new claim)
|
||||
first.sentences[0] = f"{lead} {first.sentences[0][0].lower()}{first.sentences[0][1:]}"
|
||||
|
||||
if add_abstract:
|
||||
doc.meta["abstract"] = _abstract_block(doc)
|
||||
|
||||
return doc
|
||||
@@ -1,111 +0,0 @@
|
||||
"""document_ir.py — the surface-neutral, GEOMETRY-CARRYING document intermediate.
|
||||
|
||||
This is the pivot of the whole efferent projector. A DocumentIR is NOT a text
|
||||
tree. It is a projection of a meaning-geometry region that carries, at every
|
||||
leaf, BOTH:
|
||||
|
||||
* the realized surface text (``Block.sentences``) — what a TEXT projector reads,
|
||||
* the source geometry (``Block.provenance``) — what a MUSIC / IMAGE /
|
||||
VIDEO projector reads.
|
||||
|
||||
Because the IR holds the geometry, not just the words, the SAME
|
||||
plan -> realize -> cohere pipeline drives every surface. A markdown projector
|
||||
renders the sentences; a music projector reads the provenance edges (salience,
|
||||
importance, polarity, relation) and maps them onto a symbolic-music surface;
|
||||
an image/video projector (documented seam) would read the same geometry.
|
||||
|
||||
Nothing in this module invents content. Every :class:`Provenance` points at a
|
||||
real engram node id and a real relation. That is the faithfulness contract made
|
||||
structural: a claim with no provenance cannot exist in the IR.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Provenance — the geometry an emitted claim traces to. FAITHFULNESS is here.
|
||||
# --------------------------------------------------------------------------- #
|
||||
@dataclass
|
||||
class Provenance:
|
||||
"""One geometry edge behind one realized claim.
|
||||
|
||||
``kind`` distinguishes a FACT (a structural edge asserted by the geometry,
|
||||
spoken as fact) from an INTERPRETATION (something attributed, spoken with
|
||||
attribution) — the facts-as-facts + interpretations-attributed discipline
|
||||
(memory 80927e26). ``polarity`` is SACRED: a negated edge stays negated.
|
||||
"""
|
||||
subj_id: str | None # source engram node id of the subject
|
||||
subject: str | None # normalized subject surface
|
||||
relation: str # predicate lemma (e.g. "use", "contain", "be")
|
||||
obj: str | None # normalized object / complement surface
|
||||
polarity: str = "aff" # "aff" | "neg" (SACRED — never silently flipped)
|
||||
confidence: float = 0.0 # extraction confidence in [0,1]
|
||||
node_id: str | None = None # engram node the claim was extracted from
|
||||
kind: str = "fact" # "fact" | "interpretation"
|
||||
importance: float = 0.0 # source node importance (drives music/emphasis)
|
||||
salience: float = 0.0 # source node salience
|
||||
|
||||
def trace(self) -> str:
|
||||
arrow = "-->" if self.polarity == "aff" else "--NOT-->"
|
||||
return (f"[{(self.node_id or '?')[:8]}] {self.subject!r} {arrow}"
|
||||
f"{self.relation} {self.obj!r} (conf {self.confidence:.2f})")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Block:
|
||||
"""A passage: one or more faithful sentences + the geometry they trace to.
|
||||
|
||||
``sentences`` and ``provenance`` are index-aligned where possible: sentence
|
||||
``i`` was realized from ``provenance[i]``. A COHERE transition sentence with
|
||||
no new geometry carries a provenance whose ``kind == "connective"`` so the
|
||||
audit can see it introduced no new claim.
|
||||
"""
|
||||
sentences: list[str] = field(default_factory=list)
|
||||
provenance: list[Provenance] = field(default_factory=list)
|
||||
role: str = "body" # "body" | "lead" | "transition"
|
||||
|
||||
def text(self) -> str:
|
||||
return " ".join(s.rstrip(". ") + "." for s in self.sentences if s.strip())
|
||||
|
||||
|
||||
@dataclass
|
||||
class Section:
|
||||
heading: str
|
||||
level: int = 2 # markdown heading level / outline depth
|
||||
blocks: list[Block] = field(default_factory=list)
|
||||
seed_ids: list[str] = field(default_factory=list) # geometry nodes of section
|
||||
summary: str = "" # one-line grounded gloss (for pptx bullets / TOC)
|
||||
|
||||
def all_provenance(self) -> list[Provenance]:
|
||||
out: list[Provenance] = []
|
||||
for b in self.blocks:
|
||||
out.extend(b.provenance)
|
||||
return out
|
||||
|
||||
|
||||
@dataclass
|
||||
class DocumentIR:
|
||||
"""The surface-neutral document. Built ONCE, projected to ANY surface."""
|
||||
title: str
|
||||
subtitle: str = ""
|
||||
sections: list[Section] = field(default_factory=list)
|
||||
seed_id: str | None = None # the geometry region root
|
||||
format_spec: dict[str, Any] = field(default_factory=dict) # requested shape
|
||||
meta: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
# -- geometry facets (what non-text projectors consume) ----------------- #
|
||||
def all_provenance(self) -> list[Provenance]:
|
||||
out: list[Provenance] = []
|
||||
for s in self.sections:
|
||||
out.extend(s.all_provenance())
|
||||
return out
|
||||
|
||||
def claim_count(self) -> int:
|
||||
return sum(1 for p in self.all_provenance() if p.kind in ("fact", "interpretation"))
|
||||
|
||||
def ungrounded_count(self) -> int:
|
||||
"""Claims with no traceable node — MUST be zero for a faithful doc."""
|
||||
return sum(1 for p in self.all_provenance()
|
||||
if p.kind in ("fact", "interpretation") and not p.node_id)
|
||||
@@ -1,81 +0,0 @@
|
||||
"""generate.py — drive the projector: one geometry region -> many surfaces.
|
||||
|
||||
Proves the thesis with REAL output: builds ONE surface-neutral DocumentIR from
|
||||
Neuron's OWN self-geometry (read-only against the live soul via the proven
|
||||
faculty), then EMITS it to Markdown, docx, and MIDI — the same plan/realize/
|
||||
cohere, three surfaces. Writes the files + the faithfulness audit to ./out/.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _HERE)
|
||||
|
||||
import pipeline # noqa: E402
|
||||
import provenance # noqa: E402
|
||||
from geometry import load_self_region # noqa: E402
|
||||
|
||||
OUT = os.path.join(_HERE, "out")
|
||||
|
||||
|
||||
def _emit_all(doc, stem):
|
||||
"""Emit one IR to every text/audio surface + audit + provenance."""
|
||||
for surface in ("markdown", "docx", "midi"):
|
||||
data = pipeline.emit(doc, surface)
|
||||
proj = pipeline.get_projector(surface)
|
||||
path = os.path.join(OUT, f"{stem}.{proj.ext}")
|
||||
with open(path, "wb") as f:
|
||||
f.write(data)
|
||||
print(f" emitted {surface:9s} -> {os.path.basename(path)} ({len(data)} bytes)")
|
||||
a = provenance.audit(doc)
|
||||
with open(os.path.join(OUT, f"{stem}.audit.json"), "w") as f:
|
||||
json.dump(a, f, indent=2)
|
||||
with open(os.path.join(OUT, f"{stem}.provenance.md"), "w") as f:
|
||||
f.write(provenance.trace_table(doc))
|
||||
print(" audit:", {k: a[k] for k in ("claims", "ungrounded_claims",
|
||||
"negations_preserved", "distinct_source_nodes", "faithful")})
|
||||
return a
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
print("surfaces registered:", pipeline.available_surfaces())
|
||||
|
||||
# ---- Document 1: Neuron's self-description (marquee) ------------------- #
|
||||
print("\n[1] Neuron self-description")
|
||||
region = load_self_region(max_nodes=9)
|
||||
print(" self region:", region)
|
||||
doc1 = pipeline.build_ir(
|
||||
None, region=region,
|
||||
title="Neuron: A Self-Description from Its Own Geometry",
|
||||
subtitle="Projected efferently from the engram — every claim traces a node.",
|
||||
format_spec={"genre": "self-description", "register": "expository"},
|
||||
max_sections=5, conf_floor=0.6)
|
||||
print(f" IR: {len(doc1.sections)} sections, {doc1.claim_count()} claims, "
|
||||
f"ungrounded={doc1.ungrounded_count()}")
|
||||
_emit_all(doc1, "neuron-self")
|
||||
|
||||
# ---- Document 2: a coherent, clean whitepaper-style section ------------ #
|
||||
print("\n[2] Whitepaper-style section (coherent clean region)")
|
||||
doc2, _ = pipeline.project(
|
||||
["chronoception", "time", "awareness", "engram", "temporal"],
|
||||
surface="markdown",
|
||||
title="Temporal Awareness in the Engram",
|
||||
subtitle="A section projected from the geometry of chronoception.",
|
||||
format_spec={"genre": "whitepaper-section", "register": "technical"},
|
||||
max_sections=4)
|
||||
print(f" IR: {len(doc2.sections)} sections, {doc2.claim_count()} claims, "
|
||||
f"ungrounded={doc2.ungrounded_count()}")
|
||||
_emit_all(doc2, "engram-temporal")
|
||||
|
||||
# echo both markdowns so they are visible in the run log
|
||||
for stem, doc in (("neuron-self", doc1), ("engram-temporal", doc2)):
|
||||
print(f"\n===== GENERATED MARKDOWN — {stem} =====\n")
|
||||
print(pipeline.emit(doc, "markdown").decode())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,129 +0,0 @@
|
||||
"""geometry.py — READ-ONLY loader for a meaning-geometry region.
|
||||
|
||||
The efferent projector never writes to the soul. This module reaches the
|
||||
geometry through the PROVEN, read-only neuron-talk faculty (``engram_client``,
|
||||
GET-only, which physically refuses non-GET methods) against the running sidecar
|
||||
soul. The live daemon :8742 / :7770 is contacted ONLY through that read-only
|
||||
client — never mutated.
|
||||
|
||||
A "region" is a seed node plus a bounded neighborhood: the manifold that will
|
||||
become the document's skeleton. We pool a few single-term lexical searches
|
||||
(the engram search is a single-term matcher) and, when available, walk one hop
|
||||
of reified neighbors, then rank by self/importance signal.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Wire in the proven faculty (own-the-core: we reuse it, we do not fork it).
|
||||
_NT = os.path.expanduser("~/Desktop/neuron-talk")
|
||||
_LR = os.path.expanduser("~/Desktop/lang-realizers")
|
||||
for _p in (_NT, _LR):
|
||||
if _p not in sys.path:
|
||||
sys.path.insert(0, _p)
|
||||
|
||||
from engram_client import ReadOnlyEngramClient # noqa: E402
|
||||
|
||||
|
||||
class Region:
|
||||
"""A geometry region: ranked nodes + the reified edges among them."""
|
||||
|
||||
def __init__(self, seed: str, nodes: list[dict], edges: list[dict]):
|
||||
self.seed = seed
|
||||
self.nodes = nodes # ranked engram node dicts
|
||||
self.edges = edges # [{src, dst, edge, ...}]
|
||||
self.by_id = {n["id"]: n for n in nodes if n.get("id")}
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Region seed={self.seed!r} nodes={len(self.nodes)} edges={len(self.edges)}>"
|
||||
|
||||
|
||||
def _prose_quality(content: str) -> float:
|
||||
"""Reward clean expository prose; penalize shouty banner-dense nodes.
|
||||
|
||||
A high ALLCAPS-word ratio or very short content signals a banner/telegraphic
|
||||
memory node that extracts into garbage. Clean declarative prose scores high.
|
||||
"""
|
||||
if not content or not content.strip():
|
||||
return 0.0
|
||||
words = content.split()
|
||||
if len(words) < 8:
|
||||
return 0.1
|
||||
caps = sum(1 for w in words if len(w) > 2 and w.strip(".,:;'\"-").isupper())
|
||||
caps_ratio = caps / max(1, len(words))
|
||||
# sentences with lowercase interior words read as prose
|
||||
lower = sum(1 for w in words if w[:1].islower())
|
||||
lower_ratio = lower / max(1, len(words))
|
||||
return max(0.0, 1.2 * lower_ratio - 2.0 * caps_ratio)
|
||||
|
||||
|
||||
def _relevance(content: str, terms: list[str]) -> float:
|
||||
"""Topical relevance to the seed terms — keeps a region ON-THEME so a clean
|
||||
but off-topic node cannot hijack the document."""
|
||||
if not terms:
|
||||
return 0.0
|
||||
low = (content or "").lower()
|
||||
hits = sum(1 for t in terms if t.lower() in low)
|
||||
return hits / max(1, len(terms))
|
||||
|
||||
|
||||
def _node_rank(n: dict, terms: list[str] | None = None) -> float:
|
||||
return (float(n.get("importance") or 0.0) * 2.0
|
||||
+ float(n.get("salience") or 0.0)
|
||||
+ 1.5 * _prose_quality(n.get("content") or "")
|
||||
+ 2.0 * _relevance(n.get("content") or "", terms or [])
|
||||
+ (0.5 if (n.get("content") or "").strip() else 0.0))
|
||||
|
||||
|
||||
def load_region(seed_terms: list[str] | str, *, client: ReadOnlyEngramClient | None = None,
|
||||
max_nodes: int = 10, per_term: int = 20, hop: bool = True) -> Region:
|
||||
"""Pull a bounded geometry region around ``seed_terms`` (read-only).
|
||||
|
||||
``seed_terms`` may be a single string or several probe terms; results are
|
||||
pooled and de-duplicated. When ``hop`` and the reified neighbor endpoint is
|
||||
live, one hop of neighbors is folded in so the region is a real
|
||||
neighborhood, not just a keyword hit list.
|
||||
"""
|
||||
client = client or ReadOnlyEngramClient()
|
||||
if isinstance(seed_terms, str):
|
||||
seed_terms = [seed_terms]
|
||||
|
||||
pool: dict[str, dict] = {}
|
||||
for term in seed_terms:
|
||||
for n in client.search(term, limit=per_term):
|
||||
if isinstance(n, dict) and n.get("id"):
|
||||
pool.setdefault(n["id"], n)
|
||||
|
||||
ranked = sorted(pool.values(), key=lambda n: _node_rank(n, seed_terms),
|
||||
reverse=True)
|
||||
nodes = ranked[:max_nodes]
|
||||
|
||||
edges: list[dict] = []
|
||||
if hop and nodes:
|
||||
present = {n["id"] for n in nodes}
|
||||
for n in list(nodes):
|
||||
try:
|
||||
for nb in client.neighbors(n["id"]):
|
||||
node = nb.get("node") if isinstance(nb, dict) else None
|
||||
edge = nb.get("edge") if isinstance(nb, dict) else None
|
||||
if node and node.get("id"):
|
||||
edges.append({"src": n["id"], "dst": node["id"],
|
||||
"edge": edge})
|
||||
# fold a strong neighbor into the region (bounded)
|
||||
if (node["id"] not in present and len(nodes) < max_nodes + 6
|
||||
and _node_rank(node, seed_terms) > 0.4):
|
||||
present.add(node["id"])
|
||||
nodes.append(node)
|
||||
except Exception: # noqa: BLE001 — read-only best-effort; never fatal
|
||||
continue
|
||||
|
||||
return Region(seed=", ".join(seed_terms), nodes=nodes, edges=edges)
|
||||
|
||||
|
||||
def load_self_region(client: ReadOnlyEngramClient | None = None,
|
||||
max_nodes: int = 10) -> Region:
|
||||
"""The self/identity region — Neuron's own geometry, for self-description."""
|
||||
return load_region(["self", "identity", "Neuron", "values", "memory",
|
||||
"imprint", "consciousness"],
|
||||
client=client, max_nodes=max_nodes)
|
||||
@@ -1,67 +0,0 @@
|
||||
"""pipeline.py — the Efferent Multimodal Projector, top level.
|
||||
|
||||
geometry region + surface/format spec
|
||||
-> PLAN (manifold -> document skeleton/DAG)
|
||||
-> REALIZE (proven realizer, sentence -> passage, each section faithful)
|
||||
-> COHERE (document-level flow / transitions, not stitched sentences)
|
||||
-> EMIT (pluggable SurfaceProjector -> the target surface)
|
||||
|
||||
THE SURFACE IS A PARAMETER. ``project(...)`` builds the geometry-carrying
|
||||
DocumentIR once, then hands it to whichever surface projector the caller named.
|
||||
Markdown, docx, and midi (music) are all the SAME IR emitted differently. That
|
||||
is the efferent multimodal projector: geometry -> any surface.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _HERE)
|
||||
sys.path.insert(0, os.path.join(_HERE, "projectors"))
|
||||
|
||||
from cohere import cohere_document # noqa: E402
|
||||
from document_ir import DocumentIR # noqa: E402
|
||||
from geometry import Region, load_region # noqa: E402
|
||||
from plan import plan_document # noqa: E402
|
||||
from realize import realize_document # noqa: E402
|
||||
|
||||
# registering the projectors (import for side-effect: each self-registers)
|
||||
import projectors.markdown # noqa: E402,F401
|
||||
import projectors.docx # noqa: E402,F401
|
||||
import projectors.midi # noqa: E402,F401
|
||||
import projectors.seams # noqa: E402,F401
|
||||
from projectors.base import available_surfaces, get_projector # noqa: E402
|
||||
|
||||
|
||||
def build_ir(seed_terms, *, title: str, subtitle: str = "",
|
||||
format_spec: dict | None = None,
|
||||
region: Region | None = None,
|
||||
max_sections: int = 8, conf_floor: float = 0.55) -> DocumentIR:
|
||||
"""geometry -> PLAN -> REALIZE -> COHERE = the surface-neutral DocumentIR."""
|
||||
region = region or load_region(seed_terms)
|
||||
doc = plan_document(region, title=title, subtitle=subtitle,
|
||||
format_spec=format_spec or {},
|
||||
conf_floor=conf_floor, max_sections=max_sections)
|
||||
doc = realize_document(doc)
|
||||
doc = cohere_document(doc)
|
||||
return doc
|
||||
|
||||
|
||||
def emit(doc: DocumentIR, surface: str) -> bytes:
|
||||
"""EMIT: project the built IR onto one surface (surface = a parameter)."""
|
||||
return get_projector(surface).project(doc)
|
||||
|
||||
|
||||
def project(seed_terms, *, surface: str, title: str, subtitle: str = "",
|
||||
format_spec: dict | None = None, region: Region | None = None,
|
||||
max_sections: int = 8) -> tuple[DocumentIR, bytes]:
|
||||
"""The full efferent projection: geometry + surface -> (IR, bytes)."""
|
||||
doc = build_ir(seed_terms, title=title, subtitle=subtitle,
|
||||
format_spec=format_spec, region=region,
|
||||
max_sections=max_sections)
|
||||
return doc, emit(doc, surface)
|
||||
|
||||
|
||||
__all__ = ["build_ir", "emit", "project", "available_surfaces",
|
||||
"get_projector", "load_region", "DocumentIR"]
|
||||
@@ -1,192 +0,0 @@
|
||||
"""plan.py — PLAN stage: geometry region -> document skeleton (a DAG/outline).
|
||||
|
||||
The manifold becomes the skeleton. We extract faithful propositions from the
|
||||
region's nodes (the proven neuron-talk extractor, SACRED polarity preserved),
|
||||
apply a quality floor, then GROUP them into sections. Grouping is by source
|
||||
node — each engram node is one coherent topic, so one salient node becomes one
|
||||
section. The section ORDER is the node ranking (importance/salience): the
|
||||
geometry decides the outline, not a template.
|
||||
|
||||
Output: a DocumentIR whose sections carry seed node ids and empty blocks. REALIZE
|
||||
fills the blocks; the plan owns the structure.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
_NT = os.path.expanduser("~/Desktop/neuron-talk")
|
||||
_LR = os.path.expanduser("~/Desktop/lang-realizers")
|
||||
for _p in (_NT, _LR):
|
||||
if _p not in sys.path:
|
||||
sys.path.insert(0, _p)
|
||||
|
||||
import propositions # noqa: E402 (the proven, faithful extractor)
|
||||
|
||||
from document_ir import DocumentIR, Section # noqa: E402
|
||||
from geometry import Region # noqa: E402
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Proposition quality — keep only clean, well-grounded claims.
|
||||
# --------------------------------------------------------------------------- #
|
||||
_JUNK_RE = re.compile(r"[.][a-z]{1,3}\b|[^A-Za-z0-9 '\-]") # ".o", stray symbols
|
||||
|
||||
|
||||
def _has_banner_token(s: str) -> bool:
|
||||
"""True if any word is an ALLCAPS banner token (DHARMA, ENGRAM, MEASURED)."""
|
||||
for w in (s or "").split():
|
||||
core = w.strip(".,:;'\"-")
|
||||
if len(core) > 2 and core.isupper():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _clean_prop(p, floor: float) -> bool:
|
||||
if p.confidence < floor:
|
||||
return False
|
||||
if not p.subject or not (p.object or (p.obj_np is not None)):
|
||||
return False
|
||||
subj = (p.subject or "").strip()
|
||||
obj = (p.object or "").strip()
|
||||
if len(subj) < 2:
|
||||
return False
|
||||
# banner-derived shouty fragments read as garbage in prose
|
||||
if _has_banner_token(subj) or _has_banner_token(obj):
|
||||
return False
|
||||
if propositions._is_shouty(p.sentence or ""):
|
||||
return False
|
||||
# junk tokens: file-extension fragments (".o"), stray non-word symbols
|
||||
if _JUNK_RE.search(subj) or _JUNK_RE.search(obj):
|
||||
return False
|
||||
# a proposition whose object repeats the subject is usually a parse artifact
|
||||
if obj and subj.lower() == obj.lower():
|
||||
return False
|
||||
# a bare copula with no real complement ("X is it") reads as noise
|
||||
if p.predicate == "be" and obj.lower() in ("it", "no", "nothing", "empty", ""):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _dedup(props):
|
||||
"""Drop duplicate claims. Two axes: (a) identical (pred,obj,polarity), and
|
||||
(b) same (subject,predicate) — which collapses a mis-split compound like
|
||||
"detection is post-hoc eval" -> "Detection is post/hoc/eval" into one claim
|
||||
(keep the highest-confidence surface)."""
|
||||
props = sorted(props, key=lambda p: p.confidence, reverse=True)
|
||||
seen_po, seen_sp, out = set(), set(), []
|
||||
for p in props:
|
||||
subj = (p.subject or "").lower()
|
||||
po = (p.predicate, (p.object or "").lower(), p.polarity)
|
||||
sp = (subj, p.predicate, p.polarity)
|
||||
if po in seen_po or sp in seen_sp:
|
||||
continue
|
||||
seen_po.add(po)
|
||||
seen_sp.add(sp)
|
||||
out.append(p)
|
||||
return out
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Heading derivation — a clean human heading from a node.
|
||||
# --------------------------------------------------------------------------- #
|
||||
_HEADING_RE = re.compile(r"^\s*#{1,4}\s+(.{2,70})\s*$", re.M)
|
||||
# node-type / system labels that are NOT topical headings
|
||||
_NONTOPIC_LABEL = re.compile(r"^(memory|node|knowledge|doc|session)[:/]", re.I)
|
||||
|
||||
|
||||
def _titlecase_banner(s: str) -> str:
|
||||
"""A shouty banner ("CHRONOCEPTION — SCALE-INVARIANCE") makes a fine title
|
||||
once Title-cased. Keep short acronyms uppercase."""
|
||||
def fix(w):
|
||||
core = w.strip("—-:,.")
|
||||
if len(core) <= 3 and core.isupper():
|
||||
return w # acronym
|
||||
return w.capitalize()
|
||||
return " ".join(fix(w) for w in s.split())
|
||||
|
||||
|
||||
def _clean_heading(text: str) -> str | None:
|
||||
"""First line only, no markdown, capped, banner Title-cased. None if unusable."""
|
||||
if not text:
|
||||
return None
|
||||
line = text.strip().splitlines()[0]
|
||||
line = re.sub(r"^#+\s*", "", line).strip().strip("#").strip()
|
||||
# cut at a natural break so a long banner heading stays a heading, not a para
|
||||
for sep in (" — ", " – ", ": ", ". "):
|
||||
if sep in line and len(line) > 48:
|
||||
line = line.split(sep)[0].strip()
|
||||
break
|
||||
if not (3 <= len(line) <= 64):
|
||||
return None
|
||||
if propositions._is_shouty(line):
|
||||
line = _titlecase_banner(line)
|
||||
return line or None
|
||||
|
||||
|
||||
def _heading_for(node: dict, fallback: str) -> str:
|
||||
label = (node.get("label") or "").strip()
|
||||
content = node.get("content") or ""
|
||||
candidates: list[str] = []
|
||||
# a node-type label ("memory:remembered") is never a topic — skip it
|
||||
if label and not _NONTOPIC_LABEL.match(label):
|
||||
candidates.append(label)
|
||||
m = _HEADING_RE.search(content)
|
||||
if m:
|
||||
candidates.append(m.group(1))
|
||||
# the leading banner/first sentence of the content is often the real title
|
||||
first = re.split(r"(?<=[.\n])", content.strip(), maxsplit=1)[0] if content.strip() else ""
|
||||
candidates.append(first)
|
||||
for c in candidates:
|
||||
h = _clean_heading(c)
|
||||
if h:
|
||||
return h
|
||||
return fallback
|
||||
|
||||
|
||||
def plan_document(region: Region, *, title: str, subtitle: str = "",
|
||||
format_spec: dict | None = None,
|
||||
conf_floor: float = 0.55,
|
||||
max_sections: int = 8,
|
||||
max_claims_per_section: int = 6) -> DocumentIR:
|
||||
"""Region -> DocumentIR skeleton. The geometry dictates the outline."""
|
||||
format_spec = format_spec or {}
|
||||
doc = DocumentIR(title=title, subtitle=subtitle,
|
||||
seed_id=region.nodes[0]["id"] if region.nodes else None,
|
||||
format_spec=format_spec)
|
||||
|
||||
made = 0
|
||||
seen_headings: set[str] = set()
|
||||
for node in region.nodes:
|
||||
if made >= max_sections:
|
||||
break
|
||||
props = propositions.extract(node.get("content") or "",
|
||||
node_id=node.get("id"),
|
||||
node_importance=float(node.get("importance") or 0.0),
|
||||
max_sentences=10)
|
||||
props = [p for p in props if _clean_prop(p, conf_floor)]
|
||||
props = _dedup(props)
|
||||
props.sort(key=lambda p: p.confidence, reverse=True)
|
||||
props = props[:max_claims_per_section]
|
||||
if not props:
|
||||
continue
|
||||
heading = _heading_for(node, fallback=f"Region {made + 1}")
|
||||
# cross-section dedup: a topic appears once. Distinguish by top claim
|
||||
# subject, else drop the collision so the outline stays clean.
|
||||
if heading.lower() in seen_headings:
|
||||
subj = (props[0].subject or "").strip().title()
|
||||
alt = f"{heading}: {subj}" if subj and subj.lower() not in heading.lower() else None
|
||||
if alt and alt.lower() not in seen_headings and len(alt) <= 64:
|
||||
heading = alt
|
||||
else:
|
||||
continue
|
||||
seen_headings.add(heading.lower())
|
||||
sec = Section(heading=heading, level=2, seed_ids=[node["id"]])
|
||||
# stash the planned propositions on the section for REALIZE
|
||||
sec.__dict__["_planned_props"] = props
|
||||
sec.__dict__["_node"] = node
|
||||
doc.sections.append(sec)
|
||||
made += 1
|
||||
|
||||
return doc
|
||||
@@ -1,106 +0,0 @@
|
||||
"""base.py — the SurfaceProjector interface + registry.
|
||||
|
||||
THE key abstraction of the efferent projector: a projector is a pure function
|
||||
from the surface-neutral, geometry-carrying DocumentIR to bytes on a target
|
||||
SURFACE. The surface is a PARAMETER. Adding a surface = registering one more
|
||||
projector; nothing upstream (plan/realize/cohere) changes.
|
||||
|
||||
DocumentIR --project--> bytes (per surface)
|
||||
|
||||
A TEXT projector reads ``block.sentences``. A NON-TEXT projector (music, image,
|
||||
video) reads ``block.provenance`` — the geometry the IR carries — and decodes it
|
||||
onto its surface. Both consume the SAME IR. That symmetry is the whole design:
|
||||
the realizer generalizes into a multimodal projector, geometry -> any surface.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from document_ir import DocumentIR # noqa: E402
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SurfaceProjector(Protocol):
|
||||
"""Geometry-document -> one surface. Implementations MUST be pure & faithful.
|
||||
|
||||
THE ONE SHARED SEAM. Every surface — text, music, image, video — conforms to
|
||||
this single contract:
|
||||
|
||||
project(frame: DocumentIR) -> bytes
|
||||
|
||||
where ``frame`` is the geometry-carrying meaning-geometry (the SemFrame at
|
||||
document scale; a single utterance is the degenerate one-section frame).
|
||||
|
||||
RECOMMENDED INTERNAL SHAPE (the peer music/text decomposition, blessed here
|
||||
so all surfaces share it): a projector may split ``project`` into
|
||||
|
||||
spec = self.plan(frame) # meaning-geometry -> surface-specific spec
|
||||
bytes = self.realize(spec) # spec -> surface, via this projector's PROFILE
|
||||
|
||||
``project`` is then ``realize(plan(frame))``. The PROFILE (a text lang-profile,
|
||||
a music instr/mode-profile, an image layout-profile) is a property of the
|
||||
projector instance — the pluggable knob. See :class:`TwoStageProjector`.
|
||||
|
||||
A TEXT projector's plan reads ``frame`` sentences; a MUSIC/IMAGE projector's
|
||||
plan reads ``frame.all_provenance()`` — the geometry — and derives its spec
|
||||
(pitch/harmony/rhythm, or layout) FROM the meaning, deterministically. Same
|
||||
frame, different profile.
|
||||
"""
|
||||
|
||||
surface: str # "markdown" | "docx" | "midi" | "audio" | "image" | "video"
|
||||
media_type: str # MIME type of the emitted bytes
|
||||
ext: str # file extension (no dot)
|
||||
modality: str # "text" | "audio" | "image" | "video"
|
||||
profile: object # the pluggable per-surface profile (may be None)
|
||||
|
||||
def project(self, doc: DocumentIR) -> bytes:
|
||||
"""Emit the document on this surface. Returns raw bytes."""
|
||||
...
|
||||
|
||||
|
||||
class TwoStageProjector:
|
||||
"""Optional base for the peer plan()/realize() decomposition.
|
||||
|
||||
Subclasses implement ``plan(frame) -> spec`` and ``realize(spec) -> bytes``;
|
||||
``project`` is their composition. This is exactly the peer music interface
|
||||
(spec = plan(frame, profile); surface = realize(spec, profile)) expressed so
|
||||
that it still satisfies the single ``SurfaceProjector.project`` seam. Text,
|
||||
music, and image projectors can all subclass this and remain interchangeable.
|
||||
"""
|
||||
|
||||
surface: str = ""
|
||||
media_type: str = ""
|
||||
ext: str = ""
|
||||
modality: str = ""
|
||||
profile: object = None
|
||||
|
||||
def plan(self, doc: DocumentIR): # -> spec
|
||||
raise NotImplementedError
|
||||
|
||||
def realize(self, spec) -> bytes:
|
||||
raise NotImplementedError
|
||||
|
||||
def project(self, doc: DocumentIR) -> bytes:
|
||||
return self.realize(self.plan(doc))
|
||||
|
||||
|
||||
_REGISTRY: dict[str, SurfaceProjector] = {}
|
||||
|
||||
|
||||
def register(projector: SurfaceProjector) -> SurfaceProjector:
|
||||
_REGISTRY[projector.surface] = projector
|
||||
return projector
|
||||
|
||||
|
||||
def get_projector(surface: str) -> SurfaceProjector:
|
||||
if surface not in _REGISTRY:
|
||||
raise KeyError(f"no projector registered for surface {surface!r}; "
|
||||
f"have {sorted(_REGISTRY)}")
|
||||
return _REGISTRY[surface]
|
||||
|
||||
|
||||
def available_surfaces() -> list[str]:
|
||||
return sorted(_REGISTRY)
|
||||
@@ -1,113 +0,0 @@
|
||||
"""docx.py — the .docx surface projector: an OWN minimal OOXML emitter.
|
||||
|
||||
Own-the-core: a .docx is just a ZIP of a few XML parts (WordprocessingML). We
|
||||
emit it with the standard library only — ``zipfile`` + string XML — no
|
||||
python-docx, no external dependency. This proves a "richer structured format"
|
||||
surface without importing anyone else's toolkit.
|
||||
|
||||
Parts emitted (the minimal valid set + a styles part for real headings):
|
||||
[Content_Types].xml
|
||||
_rels/.rels
|
||||
word/_rels/document.xml.rels
|
||||
word/styles.xml (Title / Heading1 / Heading2 / Normal)
|
||||
word/document.xml (the content)
|
||||
|
||||
Like the markdown projector it reads only the IR's realized sentences; it
|
||||
invents nothing. The surface differs, the faithful content does not.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from document_ir import DocumentIR # noqa: E402
|
||||
from projectors.base import register # noqa: E402
|
||||
|
||||
_CONTENT_TYPES = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||
<Default Extension="xml" ContentType="application/xml"/>
|
||||
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
||||
<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
|
||||
</Types>"""
|
||||
|
||||
_RELS = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
||||
</Relationships>"""
|
||||
|
||||
_DOC_RELS = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
|
||||
</Relationships>"""
|
||||
|
||||
_W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
|
||||
_STYLES = f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w:styles xmlns:w="{_W}">
|
||||
<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/>
|
||||
<w:rPr><w:sz w:val="22"/></w:rPr></w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Title"><w:name w:val="Title"/>
|
||||
<w:pPr><w:spacing w:after="240"/></w:pPr>
|
||||
<w:rPr><w:b/><w:sz w:val="52"/></w:rPr></w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Subtitle"><w:name w:val="Subtitle"/>
|
||||
<w:rPr><w:i/><w:sz w:val="28"/><w:color w:val="555555"/></w:rPr></w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/>
|
||||
<w:pPr><w:spacing w:before="240" w:after="120"/><w:outlineLvl w:val="0"/></w:pPr>
|
||||
<w:rPr><w:b/><w:sz w:val="34"/></w:rPr></w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/>
|
||||
<w:pPr><w:spacing w:before="200" w:after="100"/><w:outlineLvl w:val="1"/></w:pPr>
|
||||
<w:rPr><w:b/><w:sz w:val="28"/></w:rPr></w:style>
|
||||
</w:styles>"""
|
||||
|
||||
|
||||
def _para(text: str, style: str | None = None) -> str:
|
||||
ppr = f"<w:pPr><w:pStyle w:val=\"{style}\"/></w:pPr>" if style else ""
|
||||
return (f"<w:p>{ppr}<w:r><w:t xml:space=\"preserve\">"
|
||||
f"{escape(text)}</w:t></w:r></w:p>")
|
||||
|
||||
|
||||
class DocxProjector:
|
||||
surface = "docx"
|
||||
media_type = ("application/vnd.openxmlformats-officedocument."
|
||||
"wordprocessingml.document")
|
||||
ext = "docx"
|
||||
modality = "text"
|
||||
|
||||
def _document_xml(self, doc: DocumentIR) -> str:
|
||||
body: list[str] = [_para(doc.title, "Title")]
|
||||
if doc.subtitle:
|
||||
body.append(_para(doc.subtitle, "Subtitle"))
|
||||
abstract = doc.meta.get("abstract")
|
||||
if abstract is not None and abstract.sentences:
|
||||
body.append(_para(abstract.text()))
|
||||
for sec in doc.sections:
|
||||
style = "Heading1" if sec.level <= 1 else "Heading2"
|
||||
body.append(_para(sec.heading, style))
|
||||
for block in sec.blocks:
|
||||
t = block.text()
|
||||
if t:
|
||||
body.append(_para(t))
|
||||
return (f"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||||
f"<w:document xmlns:w=\"{_W}\"><w:body>"
|
||||
+ "".join(body)
|
||||
+ "<w:sectPr><w:pgSz w:w=\"12240\" w:h=\"15840\"/>"
|
||||
"<w:pgMar w:top=\"1440\" w:right=\"1440\" w:bottom=\"1440\" "
|
||||
"w:left=\"1440\"/></w:sectPr></w:body></w:document>")
|
||||
|
||||
def project(self, doc: DocumentIR) -> bytes:
|
||||
buf = io.BytesIO()
|
||||
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
|
||||
z.writestr("[Content_Types].xml", _CONTENT_TYPES)
|
||||
z.writestr("_rels/.rels", _RELS)
|
||||
z.writestr("word/_rels/document.xml.rels", _DOC_RELS)
|
||||
z.writestr("word/styles.xml", _STYLES)
|
||||
z.writestr("word/document.xml", self._document_xml(doc))
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
register(DocxProjector())
|
||||
@@ -1,45 +0,0 @@
|
||||
"""markdown.py — the Markdown surface projector (text facet).
|
||||
|
||||
The most tractable surface, and the reference implementation: reads the IR's
|
||||
realized sentences and lays them out as Markdown. Introduces no content — it is
|
||||
pure typography over the faithful text the realizer produced.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from document_ir import DocumentIR # noqa: E402
|
||||
from projectors.base import register # noqa: E402
|
||||
|
||||
|
||||
class MarkdownProjector:
|
||||
surface = "markdown"
|
||||
media_type = "text/markdown"
|
||||
ext = "md"
|
||||
modality = "text"
|
||||
|
||||
def render_str(self, doc: DocumentIR) -> str:
|
||||
lines: list[str] = [f"# {doc.title}"]
|
||||
if doc.subtitle:
|
||||
lines.append(f"\n*{doc.subtitle}*")
|
||||
abstract = doc.meta.get("abstract")
|
||||
if abstract is not None and abstract.sentences:
|
||||
lines.append("")
|
||||
lines.append(abstract.text())
|
||||
for sec in doc.sections:
|
||||
lines.append("")
|
||||
lines.append(f"{'#' * max(2, sec.level)} {sec.heading}")
|
||||
for block in sec.blocks:
|
||||
body = block.text()
|
||||
if body:
|
||||
lines.append("")
|
||||
lines.append(body)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
def project(self, doc: DocumentIR) -> bytes:
|
||||
return self.render_str(doc).encode("utf-8")
|
||||
|
||||
|
||||
register(MarkdownProjector())
|
||||
@@ -1,133 +0,0 @@
|
||||
"""midi.py — the MUSIC surface projector: geometry -> symbolic music (MIDI).
|
||||
|
||||
The first NON-TEXT surface, and the proof of the general shape. "Music is
|
||||
language and it is math" (Will): symbolic music is tractable and geometry-native,
|
||||
so it is the natural efferent twin to try first after text.
|
||||
|
||||
CRUCIALLY this projector does NOT read the realized sentences. It reads the IR's
|
||||
GEOMETRY facet — ``block.provenance`` — and DECODES each edge onto a musical
|
||||
surface. That is the whole thesis of the multimodal projector: the same
|
||||
geometry-carrying IR drives text AND music; a text projector reads the words, a
|
||||
music projector reads the meaning-geometry. The mapping is deterministic and
|
||||
faithful to the geometry's structure:
|
||||
|
||||
relation lemma -> scale degree (same relation -> same pitch class;
|
||||
meaning has a consistent sonic form)
|
||||
polarity -> mode (aff = major third above; neg = minor
|
||||
third / lowered — SACRED polarity is
|
||||
audible, a negated edge sounds negated)
|
||||
confidence -> note duration (stronger grounding rings longer)
|
||||
importance -> velocity (more important source = louder)
|
||||
section -> phrase + register shift (structure becomes musical form)
|
||||
|
||||
Own-the-core: a Standard MIDI File is a header chunk + a track chunk of
|
||||
delta-timed events. We emit the raw bytes with ``struct`` — no external MIDI
|
||||
library. Format 0, one track.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from document_ir import DocumentIR, Provenance # noqa: E402
|
||||
from projectors.base import TwoStageProjector, register # noqa: E402
|
||||
|
||||
_TICKS = 480 # ticks per quarter note
|
||||
_C_MAJOR = [0, 2, 4, 5, 7, 9, 11] # semitone offsets of a diatonic scale
|
||||
|
||||
|
||||
def _vlq(n: int) -> bytes:
|
||||
"""MIDI variable-length quantity encoding of a delta time."""
|
||||
if n == 0:
|
||||
return b"\x00"
|
||||
out = bytearray()
|
||||
out.append(n & 0x7F)
|
||||
n >>= 7
|
||||
while n:
|
||||
out.insert(0, (n & 0x7F) | 0x80)
|
||||
n >>= 7
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def _degree_for(relation: str) -> int:
|
||||
"""Stable scale degree for a relation lemma (same relation -> same pitch)."""
|
||||
if not relation:
|
||||
return 0
|
||||
return sum(ord(c) for c in relation.lower()) % len(_C_MAJOR)
|
||||
|
||||
|
||||
def _note_for(p: Provenance, base: int) -> tuple[int, int, int]:
|
||||
"""(pitch, velocity, duration_ticks) for one geometry edge."""
|
||||
root = base + _C_MAJOR[_degree_for(p.relation)]
|
||||
# polarity -> mode: affirmed edges take the bright major third, negated edges
|
||||
# take the darker minor third. The negation is AUDIBLE and never dropped.
|
||||
third = 4 if p.polarity == "aff" else 3
|
||||
pitch = max(24, min(96, root + (third if p.confidence >= 0.5 else 0)))
|
||||
velocity = int(56 + 60 * min(1.0, max(0.0, p.importance)))
|
||||
velocity = max(40, min(120, velocity))
|
||||
# confidence -> duration: quarter .. dotted-half
|
||||
dur = int(_TICKS * (0.5 + 1.5 * min(1.0, max(0.0, p.confidence))))
|
||||
return pitch, velocity, dur
|
||||
|
||||
|
||||
# a mode-profile: the pluggable musical knob (the peer's mode_profile). Scale +
|
||||
# tempo. Swapping this profile re-voices the SAME geometry — surface as parameter.
|
||||
_DEFAULT_PROFILE = {"scale": _C_MAJOR, "tempo_us": 500000,
|
||||
"registers": [60, 55, 64, 50, 67, 48], "program": 0}
|
||||
|
||||
|
||||
class MidiProjector(TwoStageProjector):
|
||||
"""geometry -> symbolic music, in the shared two-stage shape.
|
||||
|
||||
``plan(frame)`` -> a music_spec: an ordered list of note dicts derived
|
||||
deterministically from the frame's provenance geometry
|
||||
(the peer's ``plan(frame, profile) -> spec``).
|
||||
``realize(spec)`` -> Standard MIDI File bytes (the peer's
|
||||
``realize(spec, profile) -> surface``; here the surface
|
||||
is symbolic MIDI, the minimal audio proof — a richer
|
||||
additive-synth audio projector conforms identically).
|
||||
"""
|
||||
|
||||
surface = "midi"
|
||||
media_type = "audio/midi"
|
||||
ext = "mid"
|
||||
modality = "audio"
|
||||
|
||||
def __init__(self, profile: dict | None = None):
|
||||
self.profile = profile or _DEFAULT_PROFILE
|
||||
|
||||
# -- stage 1: meaning-geometry -> music_spec (reads the GEOMETRY facet) -- #
|
||||
def plan(self, doc: DocumentIR) -> list[dict]:
|
||||
registers = self.profile["registers"]
|
||||
spec: list[dict] = []
|
||||
for si, sec in enumerate(doc.sections):
|
||||
base = registers[si % len(registers)]
|
||||
provs = [p for p in sec.all_provenance()
|
||||
if p.kind in ("fact", "interpretation")]
|
||||
for i, p in enumerate(provs):
|
||||
pitch, vel, dur = _note_for(p, base)
|
||||
spec.append({"pitch": pitch, "velocity": vel, "dur": dur,
|
||||
"rest_before": (_TICKS // 2) if (si > 0 and i == 0) else 0,
|
||||
"relation": p.relation, "polarity": p.polarity})
|
||||
return spec
|
||||
|
||||
# -- stage 2: music_spec -> MIDI bytes (own-core, no library) ------------ #
|
||||
def realize(self, spec: list[dict]) -> bytes:
|
||||
ev = bytearray()
|
||||
ev += _vlq(0) + b"\xFF\x51\x03" + struct.pack(">I", self.profile["tempo_us"])[1:]
|
||||
ev += _vlq(0) + bytes([0xC0, self.profile["program"] & 0x7F])
|
||||
for note in spec:
|
||||
ev += _vlq(note["rest_before"]) + bytes([0x90, note["pitch"], note["velocity"]])
|
||||
ev += _vlq(note["dur"]) + bytes([0x80, note["pitch"], 0])
|
||||
ev += _vlq(0) + b"\xFF\x2F\x00"
|
||||
track = bytes(ev)
|
||||
buf = io.BytesIO()
|
||||
buf.write(b"MThd" + struct.pack(">IHHH", 6, 0, 1, _TICKS))
|
||||
buf.write(b"MTrk" + struct.pack(">I", len(track)) + track)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
register(MidiProjector())
|
||||
@@ -1,60 +0,0 @@
|
||||
"""seams.py — documented efferent seams for IMAGE and VIDEO surfaces.
|
||||
|
||||
These are NOT implemented (per the build rails: architect, do not overbuild).
|
||||
They are registered as first-class seams so the interface PROVES it accepts
|
||||
future non-text projectors without any upstream change. Each documents exactly
|
||||
what its decoder would read from the geometry-carrying IR, making the multimodal
|
||||
generalization concrete rather than hand-wavy.
|
||||
|
||||
The symmetry that guarantees these are possible, not moonshots: they are the
|
||||
efferent twins of multimodal INGEST. If meaning can HOLD an image (ingest as
|
||||
first-class geometry), meaning can PROJECT one back. Video = image x sound x
|
||||
TIME, and the engram already stores time (chronoception). So video falls out of
|
||||
an image projector + the music projector + the stored temporal ordering.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from document_ir import DocumentIR # noqa: E402
|
||||
from projectors.base import register # noqa: E402
|
||||
|
||||
|
||||
class _Seam:
|
||||
"""A registered-but-unimplemented projector. Names its decoder contract."""
|
||||
|
||||
def project(self, doc: DocumentIR) -> bytes: # pragma: no cover - seam
|
||||
raise NotImplementedError(
|
||||
f"{self.surface!r} projector is a documented seam, not yet built. "
|
||||
f"Decoder contract: {self.decoder_contract}")
|
||||
|
||||
|
||||
class ImageProjector(_Seam):
|
||||
surface = "image"
|
||||
media_type = "image/png"
|
||||
ext = "png"
|
||||
modality = "image"
|
||||
decoder_contract = (
|
||||
"reads block.provenance as a spatial layout — nodes become regions, edges "
|
||||
"become adjacencies; salience/importance drive size/contrast; polarity "
|
||||
"drives figure/ground. The efferent twin of image ingest (a geometry->raster "
|
||||
"decoder, learned or engineered), exactly mirroring the embedder that turned "
|
||||
"the image INTO geometry.")
|
||||
|
||||
|
||||
class VideoProjector(_Seam):
|
||||
surface = "video"
|
||||
media_type = "video/mp4"
|
||||
ext = "mp4"
|
||||
modality = "video"
|
||||
decoder_contract = (
|
||||
"image x sound x TIME. Composes the image projector (per-keyframe geometry "
|
||||
"layout) with the midi/music projector (score) along the geometry's stored "
|
||||
"temporal ordering (chronoception). Needs no new principle once image + music "
|
||||
"exist — only a muxer.")
|
||||
|
||||
|
||||
register(ImageProjector())
|
||||
register(VideoProjector())
|
||||
@@ -1,63 +0,0 @@
|
||||
"""provenance.py — the faithfulness audit + geometry->section trace.
|
||||
|
||||
A document projected from geometry is only worth anything if every claim traces
|
||||
back. This module walks the DocumentIR and proves the discipline held:
|
||||
|
||||
* ZERO ungrounded claims (every fact/interpretation has a real node id),
|
||||
* every emitted sentence maps to a geometry edge (or is a marked connective),
|
||||
* SACRED polarity survived (negations are reported, never silently dropped),
|
||||
* COHERE introduced no new geometry (connectives carry no claim).
|
||||
|
||||
It emits both a machine verdict and a human-readable geometry->section table.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from document_ir import DocumentIR
|
||||
|
||||
|
||||
def audit(doc: DocumentIR) -> dict:
|
||||
provs = doc.all_provenance()
|
||||
facts = [p for p in provs if p.kind in ("fact", "interpretation")]
|
||||
connectives = [p for p in provs if p.kind == "connective"]
|
||||
ungrounded = [p for p in facts if not p.node_id]
|
||||
negations = [p for p in facts if p.polarity == "neg"]
|
||||
node_ids = sorted({p.node_id for p in facts if p.node_id})
|
||||
return {
|
||||
"claims": len(facts),
|
||||
"connectives": len(connectives),
|
||||
"ungrounded_claims": len(ungrounded),
|
||||
"negations_preserved": len(negations),
|
||||
"distinct_source_nodes": len(node_ids),
|
||||
"faithful": len(ungrounded) == 0,
|
||||
"source_nodes": node_ids,
|
||||
}
|
||||
|
||||
|
||||
def trace_table(doc: DocumentIR) -> str:
|
||||
"""Human-readable geometry -> section -> claim provenance table."""
|
||||
lines = ["# Provenance — every claim traces geometry", ""]
|
||||
lines.append(f"**Document:** {doc.title}")
|
||||
a = audit(doc)
|
||||
lines.append(f"**Claims:** {a['claims']} · **Ungrounded:** "
|
||||
f"{a['ungrounded_claims']} · **Negations preserved:** "
|
||||
f"{a['negations_preserved']} · **Source nodes:** "
|
||||
f"{a['distinct_source_nodes']} · **Faithful:** "
|
||||
f"{'YES' if a['faithful'] else 'NO'}")
|
||||
lines.append("")
|
||||
for si, sec in enumerate(doc.sections, 1):
|
||||
lines.append(f"## {si}. {sec.heading}")
|
||||
lines.append(f"_seed nodes: {', '.join(i[:8] for i in sec.seed_ids)}_")
|
||||
lines.append("")
|
||||
lines.append("| # | realized claim | traces geometry edge |")
|
||||
lines.append("|---|----------------|----------------------|")
|
||||
n = 0
|
||||
for block in sec.blocks:
|
||||
for sent, prov in zip(block.sentences, block.provenance):
|
||||
if prov.kind == "connective":
|
||||
continue
|
||||
n += 1
|
||||
edge = prov.trace().replace("|", "\\|")
|
||||
s = sent.replace("|", "\\|")
|
||||
lines.append(f"| {n} | {s} | {edge} |")
|
||||
lines.append("")
|
||||
return "\n".join(lines) + "\n"
|
||||
@@ -1,112 +0,0 @@
|
||||
"""realize.py — REALIZE stage: fill each planned section with faithful passages.
|
||||
|
||||
Scales the PROVEN realizer from a single assertion to a passage. For each
|
||||
planned proposition we build a realizer-ready clause (the proven
|
||||
``_prop_to_clause`` mapping) and run it through the proven engine
|
||||
(``engine.realize``), which is a deterministic grammar with the SACRED negation
|
||||
contract — it never invents. Each realized sentence is paired with a
|
||||
:class:`Provenance` that pins it to the exact geometry edge it came from.
|
||||
|
||||
"Passage, not a list of sentences": within a section we lightly vary sentence
|
||||
openings and group related claims, but we add NO content the geometry did not
|
||||
assert. The only non-geometry words are function words the grammar already owns
|
||||
(articles, "and", conjunction of same-subject claims). Document-level flow is
|
||||
COHERE's job; this stage owns intra-section fluency + fidelity.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
_NT = os.path.expanduser("~/Desktop/neuron-talk")
|
||||
_LR = os.path.expanduser("~/Desktop/lang-realizers")
|
||||
for _p in (_NT, _LR):
|
||||
if _p not in sys.path:
|
||||
sys.path.insert(0, _p)
|
||||
|
||||
import engine # noqa: E402 (the proven no-LLM realizer)
|
||||
from dialogue import _prop_to_clause # noqa: E402 (proven prop -> clause)
|
||||
|
||||
from document_ir import Block, DocumentIR, Provenance, Section # noqa: E402
|
||||
|
||||
|
||||
def _provenance_from(p, kind: str = "fact") -> Provenance:
|
||||
return Provenance(
|
||||
subj_id=p.source_node_id, subject=p.subject, relation=p.predicate,
|
||||
obj=p.object, polarity=p.polarity, confidence=round(float(p.confidence), 3),
|
||||
node_id=p.source_node_id, kind=kind,
|
||||
importance=float(getattr(p, "node_importance", 0.0) or 0.0),
|
||||
salience=0.0,
|
||||
)
|
||||
|
||||
|
||||
import re as _re
|
||||
|
||||
# a well-formed declarative opens with a determiner, a proper noun, "I", or a
|
||||
# capitalized head — not a mis-parsed object pronoun or a copula fragment.
|
||||
_BAD_OPENERS = _re.compile(r"^(Me |It is I|There is|This is it|That is it)\b")
|
||||
_VACUOUS = _re.compile(r"^\w+ (is|are|was|were) (it|no|nothing|empty|those|this|that)\.?$",
|
||||
_re.I)
|
||||
|
||||
|
||||
def _good_sentence(text: str) -> bool:
|
||||
"""Fluency gate — drops degenerate realizations. NEVER loosens faithfulness;
|
||||
it only refuses to SPEAK a claim whose surface came out malformed."""
|
||||
words = text.rstrip(".").split()
|
||||
if len(words) < 3:
|
||||
return False
|
||||
if _BAD_OPENERS.search(text):
|
||||
return False
|
||||
if _VACUOUS.match(text):
|
||||
return False
|
||||
# a sentence that is mostly one-letter/two-letter tokens is a parse artifact
|
||||
short = sum(1 for w in words if len(w.strip(".,'")) <= 2)
|
||||
if short > len(words) / 2:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _realize_prop(p, lang: str = "en") -> tuple[str, Provenance] | None:
|
||||
"""One proposition -> (faithful sentence, provenance) or None if it drops."""
|
||||
clause = _prop_to_clause(p)
|
||||
text = engine.realize(clause, lang)
|
||||
if not text or not text.strip():
|
||||
return None
|
||||
text = text.strip()
|
||||
if not text.endswith((".", "!", "?")):
|
||||
text += "."
|
||||
# capitalize first character (proper nouns / "I" already handled by grammar)
|
||||
text = text[0].upper() + text[1:]
|
||||
if not _good_sentence(text):
|
||||
return None
|
||||
return text, _provenance_from(p)
|
||||
|
||||
|
||||
def realize_document(doc: DocumentIR, lang: str = "en") -> DocumentIR:
|
||||
"""Fill every planned section's blocks with faithful, realized passages."""
|
||||
for sec in doc.sections:
|
||||
planned = sec.__dict__.get("_planned_props", [])
|
||||
block = Block(role="body")
|
||||
summary_bits: list[str] = []
|
||||
for p in planned:
|
||||
r = _realize_prop(p, lang)
|
||||
if r is None:
|
||||
continue
|
||||
text, prov = r
|
||||
block.sentences.append(text)
|
||||
block.provenance.append(prov)
|
||||
if len(summary_bits) < 1:
|
||||
# a short grounded gloss for TOC / pptx bullets
|
||||
obj = (prov.obj or "").strip().rstrip(".")
|
||||
if obj:
|
||||
summary_bits.append(obj)
|
||||
if block.sentences:
|
||||
sec.blocks.append(block)
|
||||
sec.summary = summary_bits[0] if summary_bits else ""
|
||||
# drop the transient planning payload; the IR is now self-contained
|
||||
sec.__dict__.pop("_planned_props", None)
|
||||
sec.__dict__.pop("_node", None)
|
||||
|
||||
# prune sections that realized to nothing
|
||||
doc.sections = [s for s in doc.sections if s.blocks]
|
||||
return doc
|
||||
@@ -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,73 +0,0 @@
|
||||
// audio-demo.el - Drive the native audio surface: render a tone per instrument
|
||||
// from its LEARNED signature, then render a small meaning-phrase "piece".
|
||||
// Entry point: top-level statement calls main() (same convention as the
|
||||
// examples' top-level println(run_test())).
|
||||
|
||||
fn micros_to_str(xs: [Int]) -> String {
|
||||
let n: Int = native_list_len(xs)
|
||||
let out: String = ""
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
if i > 0 { let out: String = out + "," }
|
||||
let out: String = out + int_to_str(native_list_get(xs, i))
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Render a 1.0s A4 (midi 69) tone from a signature file, print the parsed
|
||||
// partials (proving the numbers came from the engram .sig), write the WAV.
|
||||
fn render_tone(name: String, sigpath: String, outpath: String, table: [Int]) -> Int {
|
||||
let lines: [String] = sig_load(sigpath)
|
||||
let partials: [Int] = parse_micros(sig_field(lines, "partials"))
|
||||
println("[" + name + "] partials_n=" + sig_field(lines, "partials_n") + " parsed_partials_micro(scale 1e6)=" + micros_to_str(partials))
|
||||
println("[" + name + "] raw partials line from .sig = " + sig_field(lines, "partials"))
|
||||
let freq: Int = freq_of_midi(69)
|
||||
let note: [Int] = synth_from_sig(lines, freq, 1000, 900, 44100, table)
|
||||
let n: Int = native_list_len(note)
|
||||
let ok: Int = wav_write(outpath, note, n, 44100)
|
||||
println("[" + name + "] rendered " + int_to_str(n) + " samples -> " + outpath + " (write_ok=" + int_to_str(ok) + ")")
|
||||
return n
|
||||
}
|
||||
|
||||
fn run_demo() -> Int {
|
||||
let table: [Int] = sin_table()
|
||||
fs_mkdir("/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out")
|
||||
|
||||
println("=== TONES: render A4 (midi 69) from each learned signature ===")
|
||||
render_tone("flute", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/sig/flute.sig", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out/tone-flute.wav", table)
|
||||
render_tone("clarinet", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/sig/clarinet.sig", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out/tone-clarinet.wav", table)
|
||||
render_tone("violin", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/sig/violin.sig", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out/tone-violin.wav", table)
|
||||
render_tone("piano", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/sig/piano.sig", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out/tone-piano.wav", table)
|
||||
render_tone("organ", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/sig/organ.sig", "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out/tone-organ.wav", table)
|
||||
|
||||
println("")
|
||||
println("=== PIECE: a 6-frame meaning phrase (incl. a NEG frame) ===")
|
||||
let frames: [[String]] = native_list_empty()
|
||||
let frames: [[String]] = native_list_append(frames, audio_frame("agent", "aff", "0.9", "0.8", "0", "s1"))
|
||||
let frames: [[String]] = native_list_append(frames, audio_frame("theme", "aff", "0.7", "0.6", "0", "s2"))
|
||||
let frames: [[String]] = native_list_append(frames, audio_frame("cause", "aff", "0.8", "0.9", "1", "s3"))
|
||||
let frames: [[String]] = native_list_append(frames, audio_frame("negation", "neg", "0.85", "0.7", "0", "s4"))
|
||||
let frames: [[String]] = native_list_append(frames, audio_frame("goal", "aff", "0.6", "0.5", "1", "s5"))
|
||||
let frames: [[String]] = native_list_append(frames, audio_frame("result", "aff", "0.95", "1.0", "0", "s6"))
|
||||
|
||||
// Print the plan so the NEG frame's minor third (+3) vs major (+4) is visible.
|
||||
let nf: Int = native_list_len(frames)
|
||||
let fi: Int = 0
|
||||
while fi < nf {
|
||||
let frame: [String] = native_list_get(frames, fi)
|
||||
let plan: [Int] = plan_note(frame)
|
||||
let pol: String = surface_get(frame, "polarity")
|
||||
let third_name: String = "major(+4)"
|
||||
if str_eq(pol, "neg") { let third_name: String = "MINOR(+3)" }
|
||||
println("frame " + int_to_str(fi) + " relation=" + surface_get(frame, "relation") + " polarity=" + pol + " -> midi=" + int_to_str(native_list_get(plan, 0)) + " dur_ms=" + int_to_str(native_list_get(plan, 1)) + " amp_pm=" + int_to_str(native_list_get(plan, 2)) + " third=" + third_name)
|
||||
let fi: Int = fi + 1
|
||||
}
|
||||
|
||||
let piano_lines: [String] = sig_load("/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/sig/piano.sig")
|
||||
let total: Int = realize_audio(frames, piano_lines, "/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out/piece.wav", 44100, table)
|
||||
println("PIECE rendered " + int_to_str(total) + " samples -> /Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out/piece.wav")
|
||||
return total
|
||||
}
|
||||
|
||||
println("audio-demo main returned samples=" + int_to_str(run_demo()))
|
||||
@@ -1,400 +0,0 @@
|
||||
// audio-surface.el - Native own-core additive-synthesis audio surface.
|
||||
//
|
||||
// The AUDIO efferent seam, native, no Python and no library. This renders real
|
||||
// PCM .wav bytes from instrument SIGNATURES read from engram-sourced .sig data
|
||||
// files (elp/faculty/sig/*.sig) - the partial amplitudes are NEVER literals in
|
||||
// this source; they are parsed from the learned signature at run time. That is
|
||||
// the whole proof: render-from-learned-signatures.
|
||||
//
|
||||
// EL has no float arithmetic operator (codegen emits raw int64 ops for + - * /
|
||||
// on the shared 64-bit slot) and no float-arithmetic natives - so ALL synthesis
|
||||
// math here is own-core INTEGER fixed-point. Angles use a quarter-wave sine
|
||||
// table (scale 10000) from a fixed-point Taylor series; amplitudes are parsed to
|
||||
// micro (scale 1e6) straight from the .sig text; frequencies are milliHz ints.
|
||||
//
|
||||
// Pipeline mirrors the two-stage projector (midi.py): plan_note(frame) reads a
|
||||
// frame's meaning-geometry slot-map and derives (pitch, duration, amplitude);
|
||||
// realize_audio SUPERPOSES the signature's partials (the compose op) and
|
||||
// serialises RIFF/WAVE. Same frame -> midi OR audio.
|
||||
|
||||
// -- integer decimal + string helpers -----------------------------------------
|
||||
|
||||
fn str_to_int_el(s: String) -> Int {
|
||||
let n: Int = str_len(s)
|
||||
let i: Int = 0
|
||||
let v: Int = 0
|
||||
let neg: Bool = false
|
||||
while i < n {
|
||||
let c: Int = str_char_code(s, i)
|
||||
if c == 45 { let neg: Bool = true }
|
||||
if c >= 48 {
|
||||
if c < 58 {
|
||||
let v: Int = v * 10 + (c - 48)
|
||||
}
|
||||
}
|
||||
let i: Int = i + 1
|
||||
}
|
||||
if neg { return 0 - v }
|
||||
return v
|
||||
}
|
||||
|
||||
fn parse_micro(s: String) -> Int {
|
||||
let dot: Int = str_index_of(s, ".")
|
||||
if dot < 0 {
|
||||
return str_to_int_el(s) * 1000000
|
||||
}
|
||||
let n: Int = str_len(s)
|
||||
let ipart: String = str_slice(s, 0, dot)
|
||||
let fpart: String = str_slice(s, dot + 1, n)
|
||||
let iv: Int = str_to_int_el(ipart)
|
||||
let fv: Int = 0
|
||||
let scale: Int = 100000
|
||||
let fn2: Int = str_len(fpart)
|
||||
let i: Int = 0
|
||||
while i < 6 {
|
||||
let d: Int = 0
|
||||
if i < fn2 {
|
||||
let d: Int = str_char_code(fpart, i) - 48
|
||||
}
|
||||
let fv: Int = fv + d * scale
|
||||
let scale: Int = scale / 10
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return iv * 1000000 + fv
|
||||
}
|
||||
|
||||
// -- signature (engram data file) loader ---------------------------------------
|
||||
|
||||
fn sig_load(path: String) -> [String] {
|
||||
let text: String = fs_read(path)
|
||||
return str_split(text, "\n")
|
||||
}
|
||||
|
||||
fn sig_field(lines: [String], key: String) -> String {
|
||||
let pref: String = key + ": "
|
||||
let n: Int = native_list_len(lines)
|
||||
let plen: Int = str_len(pref)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let ln: String = native_list_get(lines, i)
|
||||
if str_starts_with(ln, pref) {
|
||||
return str_slice(ln, plen, str_len(ln))
|
||||
}
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn parse_micros(csv: String) -> [Int] {
|
||||
let parts: [String] = str_split(csv, ",")
|
||||
let n: Int = native_list_len(parts)
|
||||
let out: [Int] = native_list_empty()
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let out: [Int] = native_list_append(out, parse_micro(native_list_get(parts, i)))
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// -- fixed-point sine (own-core, quarter-wave Taylor table, scale 10000) --------
|
||||
|
||||
fn sin_table() -> [Int] {
|
||||
let HP: Int = 1570796
|
||||
let t: [Int] = native_list_empty()
|
||||
let q: Int = 0
|
||||
while q < 257 {
|
||||
let x: Int = q * HP / 256
|
||||
let x2: Int = x * x / 1000000
|
||||
let x3: Int = x2 * x / 1000000
|
||||
let x5: Int = x3 * x2 / 1000000
|
||||
let x7: Int = x5 * x2 / 1000000
|
||||
let x9: Int = x7 * x2 / 1000000
|
||||
let s: Int = x - x3 / 6 + x5 / 120 - x7 / 5040 + x9 / 362880
|
||||
let t: [Int] = native_list_append(t, s / 100)
|
||||
let q: Int = q + 1
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
fn sin_lookup(t: [Int], phase: Int) -> Int {
|
||||
let p: Int = phase % 1024
|
||||
if p < 0 { let p: Int = p + 1024 }
|
||||
let quad: Int = p / 256
|
||||
let r: Int = p % 256
|
||||
if quad == 0 { return native_list_get(t, r) }
|
||||
if quad == 1 { return native_list_get(t, 256 - r) }
|
||||
if quad == 2 { return 0 - native_list_get(t, r) }
|
||||
return 0 - native_list_get(t, 256 - r)
|
||||
}
|
||||
|
||||
fn isqrt_int(n: Int) -> Int {
|
||||
if n <= 0 { return 0 }
|
||||
let x: Int = n
|
||||
let y: Int = (x + 1) / 2
|
||||
while y < x {
|
||||
let x: Int = y
|
||||
let y: Int = (x + n / x) / 2
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// freq_of_midi: equal-tempered frequency in milliHz. 440000 mHz at midi 69.
|
||||
fn freq_of_midi(m: Int) -> Int {
|
||||
let f: Int = 440000
|
||||
if m > 69 {
|
||||
let k: Int = m - 69
|
||||
let i: Int = 0
|
||||
while i < k {
|
||||
let f: Int = f * 1059463 / 1000000
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return f
|
||||
}
|
||||
if m < 69 {
|
||||
let k: Int = 69 - m
|
||||
let i: Int = 0
|
||||
while i < k {
|
||||
let f: Int = f * 1000000 / 1059463
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return f
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// -- envelope (ADSR), scale 1000 -----------------------------------------------
|
||||
|
||||
fn adsr_env(i: Int, total: Int, atk_n: Int, dec_n: Int, sus_pm: Int, rel_n: Int) -> Int {
|
||||
if i < atk_n {
|
||||
if atk_n == 0 { return 1000 }
|
||||
return 1000 * i / atk_n
|
||||
}
|
||||
if i < atk_n + dec_n {
|
||||
if dec_n == 0 { return sus_pm }
|
||||
return 1000 - (1000 - sus_pm) * (i - atk_n) / dec_n
|
||||
}
|
||||
let rel_start: Int = total - rel_n
|
||||
if i < rel_start {
|
||||
return sus_pm
|
||||
}
|
||||
if rel_n == 0 { return 0 }
|
||||
let left: Int = total - i
|
||||
return sus_pm * left / rel_n
|
||||
}
|
||||
|
||||
// -- note synthesis: SUPERPOSE the learned partials -> [Int] samples -----------
|
||||
fn note_samples(freq_mHz: Int, dur_ms: Int, rate: Int, partials: [Int], sumP: Int, b_micro: Int, vib_rate: Int, vib_cents: Int, atk_ms: Int, dec_ms: Int, sus_pm: Int, rel_ms: Int, amp_pm: Int, table: [Int]) -> [Int] {
|
||||
let total: Int = dur_ms * rate / 1000
|
||||
let atk_n: Int = atk_ms * rate / 1000
|
||||
let dec_n: Int = dec_ms * rate / 1000
|
||||
let rel_n: Int = rel_ms * rate / 1000
|
||||
let np: Int = native_list_len(partials)
|
||||
let half_mhz: Int = rate * 1000 / 2
|
||||
let out: [Int] = native_list_empty()
|
||||
let i: Int = 0
|
||||
while i < total {
|
||||
let acc: Int = 0
|
||||
let k: Int = 0
|
||||
while k < np {
|
||||
let harm: Int = k + 1
|
||||
let amp_k: Int = native_list_get(partials, k)
|
||||
let factor: Int = 1000000
|
||||
if b_micro > 0 {
|
||||
let val: Int = 1000000 + b_micro * harm * harm
|
||||
let factor: Int = isqrt_int(val * 1000000)
|
||||
}
|
||||
let fn_mhz: Int = freq_mHz * harm
|
||||
let fn_mhz: Int = fn_mhz * factor / 1000000
|
||||
if vib_cents > 0 {
|
||||
if vib_rate > 0 {
|
||||
let vphase: Int = i * vib_rate * 1024 / rate
|
||||
let vs: Int = sin_lookup(table, vphase)
|
||||
let vibf: Int = 1000000 + (vib_cents * vs * 833) / 10000
|
||||
let fn_mhz: Int = fn_mhz * vibf / 1000000
|
||||
}
|
||||
}
|
||||
if fn_mhz <= half_mhz {
|
||||
let phase: Int = i * fn_mhz * 1024 / (rate * 1000)
|
||||
let sv: Int = sin_lookup(table, phase)
|
||||
let acc: Int = acc + sv * amp_k / 1000000
|
||||
}
|
||||
let k: Int = k + 1
|
||||
}
|
||||
let env: Int = adsr_env(i, total, atk_n, dec_n, sus_pm, rel_n)
|
||||
let s16: Int = acc * 2800000 / sumP
|
||||
let s16: Int = s16 * env / 1000
|
||||
let s16: Int = s16 * amp_pm / 1000
|
||||
if s16 > 32767 { let s16: Int = 32767 }
|
||||
if s16 < 0 - 32767 { let s16: Int = 0 - 32767 }
|
||||
let out: [Int] = native_list_append(out, s16)
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fn synth_from_sig(lines: [String], freq_mHz: Int, dur_ms: Int, amp_pm: Int, rate: Int, table: [Int]) -> [Int] {
|
||||
let partials: [Int] = parse_micros(sig_field(lines, "partials"))
|
||||
let np: Int = native_list_len(partials)
|
||||
let sumP: Int = 0
|
||||
let j: Int = 0
|
||||
while j < np {
|
||||
let pj: Int = native_list_get(partials, j)
|
||||
let sumP: Int = sumP + pj
|
||||
let j: Int = j + 1
|
||||
}
|
||||
if sumP <= 0 { let sumP: Int = 1000000 }
|
||||
let adsr: [String] = str_split(sig_field(lines, "adsr"), ",")
|
||||
let atk_ms: Int = parse_micro(native_list_get(adsr, 0)) / 1000
|
||||
let dec_ms: Int = parse_micro(native_list_get(adsr, 1)) / 1000
|
||||
let sus_pm: Int = parse_micro(native_list_get(adsr, 2)) / 1000
|
||||
let rel_ms: Int = parse_micro(native_list_get(adsr, 3)) / 1000
|
||||
let b_micro: Int = parse_micro(sig_field(lines, "inharmonicity_B"))
|
||||
let vib_rate: Int = str_to_int_el(sig_field(lines, "vibrato_rate_hz"))
|
||||
let vib_cents: Int = str_to_int_el(sig_field(lines, "vibrato_depth_cents"))
|
||||
return note_samples(freq_mHz, dur_ms, rate, partials, sumP, b_micro, vib_rate, vib_cents, atk_ms, dec_ms, sus_pm, rel_ms, amp_pm, table)
|
||||
}
|
||||
|
||||
// -- byte-buffer helpers (own-core, no library) --------------------------------
|
||||
|
||||
fn put_tag(buf: String, pos: Int, s: String) -> String {
|
||||
let n: Int = str_len(s)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let buf: String = __str_set_char(buf, pos + i, str_char_code(s, i))
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
fn put_u32le(buf: String, pos: Int, v: Int) -> String {
|
||||
let buf: String = __str_set_char(buf, pos, v % 256)
|
||||
let buf: String = __str_set_char(buf, pos + 1, (v / 256) % 256)
|
||||
let buf: String = __str_set_char(buf, pos + 2, (v / 65536) % 256)
|
||||
let buf: String = __str_set_char(buf, pos + 3, (v / 16777216) % 256)
|
||||
return buf
|
||||
}
|
||||
|
||||
fn put_u16le(buf: String, pos: Int, v: Int) -> String {
|
||||
let buf: String = __str_set_char(buf, pos, v % 256)
|
||||
let buf: String = __str_set_char(buf, pos + 1, (v / 256) % 256)
|
||||
return buf
|
||||
}
|
||||
|
||||
// -- WAV serializer: own-core RIFF/WAVE, PCM mono 16-bit -----------------------
|
||||
|
||||
fn wav_write(path: String, samples: [Int], n: Int, rate: Int) -> Int {
|
||||
let data_len: Int = n * 2
|
||||
let total: Int = 44 + data_len
|
||||
let buf: String = __str_alloc(total)
|
||||
let buf: String = put_tag(buf, 0, "RIFF")
|
||||
let buf: String = put_u32le(buf, 4, 36 + data_len)
|
||||
let buf: String = put_tag(buf, 8, "WAVE")
|
||||
let buf: String = put_tag(buf, 12, "fmt ")
|
||||
let buf: String = put_u32le(buf, 16, 16)
|
||||
let buf: String = put_u16le(buf, 20, 1)
|
||||
let buf: String = put_u16le(buf, 22, 1)
|
||||
let buf: String = put_u32le(buf, 24, rate)
|
||||
let buf: String = put_u32le(buf, 28, rate * 2)
|
||||
let buf: String = put_u16le(buf, 32, 2)
|
||||
let buf: String = put_u16le(buf, 34, 16)
|
||||
let buf: String = put_tag(buf, 36, "data")
|
||||
let buf: String = put_u32le(buf, 40, data_len)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let v: Int = native_list_get(samples, i)
|
||||
if v < 0 { let v: Int = v + 65536 }
|
||||
let buf: String = __str_set_char(buf, 44 + i * 2, v % 256)
|
||||
let buf: String = __str_set_char(buf, 44 + i * 2 + 1, (v / 256) % 256)
|
||||
let i: Int = i + 1
|
||||
}
|
||||
let ok: Int = fs_write_bytes(path, buf, total)
|
||||
return ok
|
||||
}
|
||||
|
||||
// -- plan: frame slot-map -> note atom (pitch, duration, amplitude) ------------
|
||||
|
||||
fn audio_frame(relation: String, polarity: String, confidence: String, importance: String, salience: String, subj_id: String) -> [String] {
|
||||
let f: [String] = native_list_empty()
|
||||
let f: [String] = native_list_append(f, "relation")
|
||||
let f: [String] = native_list_append(f, relation)
|
||||
let f: [String] = native_list_append(f, "polarity")
|
||||
let f: [String] = native_list_append(f, polarity)
|
||||
let f: [String] = native_list_append(f, "confidence")
|
||||
let f: [String] = native_list_append(f, confidence)
|
||||
let f: [String] = native_list_append(f, "importance")
|
||||
let f: [String] = native_list_append(f, importance)
|
||||
let f: [String] = native_list_append(f, "salience")
|
||||
let f: [String] = native_list_append(f, salience)
|
||||
let f: [String] = native_list_append(f, "subj_id")
|
||||
let f: [String] = native_list_append(f, subj_id)
|
||||
return f
|
||||
}
|
||||
|
||||
fn degree_offset(deg: Int) -> Int {
|
||||
if deg == 0 { return 0 }
|
||||
if deg == 1 { return 2 }
|
||||
if deg == 2 { return 4 }
|
||||
if deg == 3 { return 5 }
|
||||
if deg == 4 { return 7 }
|
||||
if deg == 5 { return 9 }
|
||||
return 11
|
||||
}
|
||||
|
||||
// returns [midi, dur_ms, amp_pm]
|
||||
fn plan_note(frame: [String]) -> [Int] {
|
||||
let relation: String = surface_get(frame, "relation")
|
||||
let polarity: String = surface_get(frame, "polarity")
|
||||
let confidence: String = surface_get(frame, "confidence")
|
||||
let importance: String = surface_get(frame, "importance")
|
||||
let salience: String = surface_get(frame, "salience")
|
||||
let rn: Int = str_len(relation)
|
||||
let csum: Int = 0
|
||||
let i: Int = 0
|
||||
while i < rn {
|
||||
let cc: Int = str_char_code(relation, i)
|
||||
let csum: Int = csum + cc
|
||||
let i: Int = i + 1
|
||||
}
|
||||
let deg: Int = csum % 7
|
||||
let third: Int = 4
|
||||
if str_eq(polarity, "neg") { let third: Int = 3 }
|
||||
let sal_oct: Int = str_to_int_el(salience)
|
||||
let doff: Int = degree_offset(deg)
|
||||
let midi: Int = 60 + sal_oct * 12 + doff + third
|
||||
let conf_micro: Int = parse_micro(confidence)
|
||||
let dur_ms: Int = 200 + conf_micro / 1000
|
||||
let imp_micro: Int = parse_micro(importance)
|
||||
let amp_pm: Int = 400 + imp_micro / 2000
|
||||
let out: [Int] = native_list_empty()
|
||||
let out: [Int] = native_list_append(out, midi)
|
||||
let out: [Int] = native_list_append(out, dur_ms)
|
||||
let out: [Int] = native_list_append(out, amp_pm)
|
||||
return out
|
||||
}
|
||||
|
||||
fn realize_audio(frames: [[String]], sig_lines: [String], path: String, rate: Int, table: [Int]) -> Int {
|
||||
let nf: Int = native_list_len(frames)
|
||||
let all: [Int] = native_list_empty()
|
||||
let count: Int = 0
|
||||
let fi: Int = 0
|
||||
while fi < nf {
|
||||
let frame: [String] = native_list_get(frames, fi)
|
||||
let plan: [Int] = plan_note(frame)
|
||||
let midi: Int = native_list_get(plan, 0)
|
||||
let dur_ms: Int = native_list_get(plan, 1)
|
||||
let amp_pm: Int = native_list_get(plan, 2)
|
||||
let freq: Int = freq_of_midi(midi)
|
||||
let note: [Int] = synth_from_sig(sig_lines, freq, dur_ms, amp_pm, rate, table)
|
||||
let nn: Int = native_list_len(note)
|
||||
let j: Int = 0
|
||||
while j < nn {
|
||||
let all: [Int] = native_list_append(all, native_list_get(note, j))
|
||||
let j: Int = j + 1
|
||||
}
|
||||
let count: Int = count + nn
|
||||
let fi: Int = fi + 1
|
||||
}
|
||||
let ok: Int = wav_write(path, all, count, rate)
|
||||
return count
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
// comprehend.elh — public surface of the ELP comprehension front-end.
|
||||
// text → meaning-spec (the input half of the ELP; inverse of the realizer).
|
||||
extern fn parse_spec(text: String) -> [String]
|
||||
extern fn parse_spec_lang(text: String, lang: String) -> [String]
|
||||
extern fn parse_json(text: String) -> String
|
||||
extern fn parse_json_lang(text: String, lang: String) -> String
|
||||
// Analysis primitives (invertible morphology + deterministic grammar helpers):
|
||||
extern fn cp_tokenize(text: String) -> [String]
|
||||
extern fn cp_pron_concept(w: String) -> String
|
||||
extern fn cp_is_negation(w: String) -> Bool
|
||||
extern fn cp_is_neg_adverb(w: String) -> Bool
|
||||
extern fn cp_irr2(surface: String) -> [String]
|
||||
extern fn cp_reg_verb(w: String) -> [String]
|
||||
extern fn cp_analyze_verb(surface: String) -> [String]
|
||||
extern fn cp_verb_start(toks: [String], end: Int) -> Int
|
||||
extern fn cp_subord_start(toks: [String], n: Int) -> Int
|
||||
@@ -1,287 +0,0 @@
|
||||
// dialogue.el — SUMMON-THROUGH-SELF, native el. Port of dialogue.py's core.
|
||||
//
|
||||
// THE WHOLE DIALOGUE IS ONE OPERATION. A fact is never merely *fetched*: the
|
||||
// query is PROJECTED into the engram's self + memory geometry, LANDS in a region,
|
||||
// and the reply is READ OUT / the region MATERIALIZED from wherever it landed.
|
||||
//
|
||||
// project(query) -> land on a region -> read out from that region
|
||||
//
|
||||
// • lands in the SELF region -> grounded identity/presence, read out of
|
||||
// the real self nodes (self_region.el)
|
||||
// • lands on a memory NEIGHBORHOOD -> MATERIALIZE it: walk the neighborhood
|
||||
// (engram_neighbors_json) and read out the
|
||||
// region's connected members
|
||||
// • lands nowhere close -> HONEST ABSENCE (an empty region, not a
|
||||
// fabricated answer, not an error)
|
||||
//
|
||||
// CRITICAL INVARIANTS (enforced structurally, not by convention):
|
||||
// * ONE operation — there is NO intent classifier and NO separate
|
||||
// fact-retrieval branch. Identity is nearest-region proximity, not a switch.
|
||||
// * MATERIALIZE by walking the neighborhood, never by fetching top-props.
|
||||
// * HONEST ABSENCE when the region is thin.
|
||||
// * NEGATION is SACRED: the readout is the stored prose VERBATIM, so a negated
|
||||
// memory stays negated — we never paraphrase a polarity away.
|
||||
// * NO ECHO: the old "I noted that X. That relates to Y." template is gone.
|
||||
// The summon path materializes or honestly declines — it never echoes.
|
||||
// * DIRECTIVE OVERRIDE: a meta-directive ("answer in English") overrides the
|
||||
// reply language while the content language is still auto-detected.
|
||||
//
|
||||
// Depends on: comprehend (parse_spec_lang, cp_tokenize), multilingual (ml_detect,
|
||||
// ml_tr, ml_term), propositions (prop_split_sentences), self_region
|
||||
// (sr_available, sr_readout), the engram + json runtime builtins.
|
||||
|
||||
// ── directive override ────────────────────────────────────────────────────────
|
||||
// Return [target_lang, content]. target_lang is "" when no directive is present.
|
||||
// A directive names an output language; we strip it and keep the remaining text
|
||||
// as the content (whose OWN language is still auto-detected downstream).
|
||||
|
||||
fn dlg_dir_hit(low: String, phrase: String) -> Bool {
|
||||
return str_contains(low, phrase)
|
||||
}
|
||||
|
||||
fn dlg_parse_directive(text: String) -> [String] {
|
||||
let low: String = str_to_lower(text)
|
||||
let lang: String = ""
|
||||
let phrase: String = ""
|
||||
// English target
|
||||
if dlg_dir_hit(low, "in english") { let lang = "en"; let phrase = "in english" }
|
||||
if dlg_dir_hit(low, "em inglês") { let lang = "en"; let phrase = "em inglês" }
|
||||
if dlg_dir_hit(low, "em ingles") { let lang = "en"; let phrase = "em ingles" }
|
||||
if dlg_dir_hit(low, "en inglés") { let lang = "en"; let phrase = "en inglés" }
|
||||
// Portuguese target
|
||||
if dlg_dir_hit(low, "in portuguese") { let lang = "pt"; let phrase = "in portuguese" }
|
||||
if dlg_dir_hit(low, "em português") { let lang = "pt"; let phrase = "em português" }
|
||||
// Spanish target
|
||||
if dlg_dir_hit(low, "in spanish") { let lang = "es"; let phrase = "in spanish" }
|
||||
if dlg_dir_hit(low, "en español") { let lang = "es"; let phrase = "en español" }
|
||||
// Italian target
|
||||
if dlg_dir_hit(low, "in italian") { let lang = "it"; let phrase = "in italian" }
|
||||
|
||||
let content: String = text
|
||||
if !str_eq(phrase, "") {
|
||||
// strip the directive phrase (and a common "answer"/"responda" lead-in),
|
||||
// leaving the real question as content.
|
||||
let idx: Int = str_index_of(low, phrase)
|
||||
if idx >= 0 {
|
||||
let before: String = str_slice(text, 0, idx)
|
||||
let after: String = str_slice(text, idx + str_len(phrase), str_len(text))
|
||||
let content = str_trim(before + " " + after)
|
||||
}
|
||||
// trim a leading "answer"/"responda"/"reply" and stray colon/comma.
|
||||
let cl: String = str_to_lower(content)
|
||||
if str_starts_with(cl, "answer") { let content = str_trim(str_slice(content, 6, str_len(content))) }
|
||||
if str_starts_with(cl, "responda") { let content = str_trim(str_slice(content, 8, str_len(content))) }
|
||||
if str_starts_with(cl, "reply") { let content = str_trim(str_slice(content, 5, str_len(content))) }
|
||||
if str_starts_with(content, ":") { let content = str_trim(str_slice(content, 1, str_len(content))) }
|
||||
if str_starts_with(content, ",") { let content = str_trim(str_slice(content, 1, str_len(content))) }
|
||||
}
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, lang)
|
||||
let r = native_list_append(r, content)
|
||||
return r
|
||||
}
|
||||
|
||||
// ── identity landing (a region proximity, not a classifier switch) ────────────
|
||||
// The query lands in the SELF region when it takes an identity/presence shape.
|
||||
// Cross-lingual forms are included because the engram's lexical probe is
|
||||
// English-leaning. This is the SELF attractor of the single operation.
|
||||
|
||||
fn dlg_is_identity(content: String) -> Bool {
|
||||
let low: String = str_to_lower(str_trim(content))
|
||||
if str_contains(low, "who are you") { return true }
|
||||
if str_contains(low, "what are you") { return true }
|
||||
if str_contains(low, "who i am") { return true }
|
||||
if str_contains(low, "your name") { return true }
|
||||
if str_contains(low, "about yourself") { return true }
|
||||
if str_contains(low, "are you conscious") { return true }
|
||||
if str_contains(low, "are you there") { return true }
|
||||
// cross-lingual identity question-forms
|
||||
if str_contains(low, "quem é você") { return true }
|
||||
if str_contains(low, "quem es voce") { return true }
|
||||
if str_contains(low, "quién eres") { return true }
|
||||
if str_contains(low, "quien eres") { return true }
|
||||
if str_contains(low, "chi sei") { return true }
|
||||
if str_contains(low, "qui es-tu") { return true }
|
||||
if str_contains(low, "wer bist du") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// ── readout helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
fn dlg_first_sentence(content: String) -> String {
|
||||
let sents: [String] = prop_split_sentences(content)
|
||||
let n: Int = native_list_len(sents)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let s: String = str_trim(native_list_get(sents, i))
|
||||
// drop a leading markdown heading marker for a clean read-out line
|
||||
if str_starts_with(s, "# ") { let s = str_trim(str_slice(s, 2, str_len(s))) }
|
||||
if str_len(s) > 0 { return s }
|
||||
let i = i + 1
|
||||
}
|
||||
return str_trim(content)
|
||||
}
|
||||
|
||||
// strip trailing/leading punctuation from a token.
|
||||
fn dlg_clean_tok(w: String) -> String {
|
||||
let s: String = str_trim(w)
|
||||
let s = str_strip_suffix(s, ".")
|
||||
let s = str_strip_suffix(s, ",")
|
||||
let s = str_strip_suffix(s, "?")
|
||||
let s = str_strip_suffix(s, "!")
|
||||
let s = str_strip_suffix(s, ":")
|
||||
let s = str_strip_suffix(s, ";")
|
||||
return str_trim(s)
|
||||
}
|
||||
|
||||
// closed-class across the supported languages (union) — a word we must NOT treat
|
||||
// as a retrieval topic. Also drops the meta verbs of a request ("tell", "prove",
|
||||
// "show") so the TOPIC, not the speech act, is what projects into memory.
|
||||
fn dlg_is_stop(w: String) -> Bool {
|
||||
if ml_stop_en(w) { return true }
|
||||
if ml_stop_es(w) { return true }
|
||||
if ml_stop_pt(w) { return true }
|
||||
if ml_stop_it(w) { return true }
|
||||
if str_eq(w, "tell") { return true }
|
||||
if str_eq(w, "show") { return true }
|
||||
if str_eq(w, "about") { return true }
|
||||
if str_eq(w, "sobre") { return true }
|
||||
if str_eq(w, "acerca") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// The CONTENT TERMS the query projects into memory: content words only, cleaned,
|
||||
// cross-lingually mapped to the engram's English vocabulary, ≥3 chars. This is
|
||||
// the geometry probe — the speech-act verbs and function words are stripped so a
|
||||
// PP topic ("tell me ABOUT Lisbon") projects on "lisbon", not "tell"/"me".
|
||||
fn dlg_content_terms(content: String, lang: String) -> [String] {
|
||||
let toks: [String] = cp_tokenize(content)
|
||||
let n: Int = native_list_len(toks)
|
||||
let out: [String] = native_list_empty()
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let w: String = str_to_lower(dlg_clean_tok(native_list_get(toks, i)))
|
||||
if str_len(w) >= 3 {
|
||||
if !dlg_is_stop(w) {
|
||||
let out = native_list_append(out, ml_term(w, lang))
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Does this landed node lexically overlap the query's content terms? This is the
|
||||
// RELEVANCE FLOOR: activation always returns the store's most salient nodes, so
|
||||
// without this a query about nothing would "land" on the self/top node. A node
|
||||
// that shares no content term with the query is "nowhere close" -> honest absence.
|
||||
fn dlg_node_matches(node: String, terms: [String]) -> Bool {
|
||||
let hay: String = str_to_lower(json_get_string(node, "content") + " " + json_get_string(node, "label"))
|
||||
let n: Int = native_list_len(terms)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let t: String = native_list_get(terms, i)
|
||||
if str_len(t) >= 3 {
|
||||
if str_contains(hay, t) { return true }
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MATERIALIZE the landed region: read out the landed fact, then WALK the
|
||||
// neighborhood and read out its connected members (real edges, not top-props).
|
||||
fn dlg_materialize(top_node: String, reply_lang: String) -> String {
|
||||
let id: String = json_get_string(top_node, "id")
|
||||
let content: String = json_get_string(top_node, "content")
|
||||
let lead: String = dlg_first_sentence(content)
|
||||
|
||||
let nb: String = engram_neighbors_json(id, 2, "both")
|
||||
let m: Int = json_array_len(nb)
|
||||
let parts: [String] = native_list_empty()
|
||||
let parts = native_list_append(parts, lead)
|
||||
let added: Int = 0
|
||||
let i: Int = 0
|
||||
while i < m {
|
||||
if added < 3 {
|
||||
let rec: String = json_array_get(nb, i)
|
||||
let node: String = json_get_raw(rec, "node")
|
||||
let nc: String = json_get_string(node, "content")
|
||||
if !str_eq(nc, "") {
|
||||
let sent: String = dlg_first_sentence(nc)
|
||||
if !str_eq(sent, "") {
|
||||
let parts = native_list_append(parts, sent)
|
||||
let added = added + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
// The readout is the region's OWN prose, verbatim — negation SACRED, no echo.
|
||||
return str_join(parts, " ")
|
||||
}
|
||||
|
||||
// ── THE single operation ──────────────────────────────────────────────────────
|
||||
|
||||
fn dlg_respond(text: String) -> String {
|
||||
// directive override: reply language may differ from content language.
|
||||
let dir: [String] = dlg_parse_directive(text)
|
||||
let target_lang: String = native_list_get(dir, 0)
|
||||
let content: String = native_list_get(dir, 1)
|
||||
|
||||
let content_lang: String = ml_detect(content)
|
||||
let reply_lang: String = content_lang
|
||||
if !str_eq(target_lang, "") { let reply_lang = target_lang }
|
||||
|
||||
// comprehend the content (SACRED polarity carried in the spec).
|
||||
let spec: [String] = parse_spec_lang(content, content_lang)
|
||||
|
||||
// ── PROJECT + LAND: SELF region ───────────────────────────────────────────
|
||||
// Identity/presence shape lands in the self region; read out the REAL self
|
||||
// nodes (self_region.el), never a template. Same single operation — this is
|
||||
// just the self attractor winning the landing.
|
||||
if dlg_is_identity(content) {
|
||||
if sr_available() {
|
||||
// read out the REAL self nodes when replying in their own language
|
||||
// (the soul's prose is English); for another reply language we cannot
|
||||
// translate real content without an LLM, so we answer with the
|
||||
// localized SACRED identity anchor — honest, in-language, no fabrication.
|
||||
if str_eq(reply_lang, "en") { return sr_readout("en") }
|
||||
return ml_tr("identity", reply_lang)
|
||||
}
|
||||
// self region thin — honest localized identity (logged fallback shape).
|
||||
return ml_tr("identity", reply_lang)
|
||||
}
|
||||
|
||||
// ── PROJECT into MEMORY geometry ──────────────────────────────────────────
|
||||
let terms: [String] = dlg_content_terms(content, content_lang)
|
||||
let qterm: String = str_join(terms, " ")
|
||||
let act: String = engram_activate_json(qterm, 12)
|
||||
let n: Int = json_array_len(act)
|
||||
|
||||
// ── LAND: the highest-activation node that ACTUALLY overlaps the query's
|
||||
// content terms (the relevance floor). Activation always returns the most
|
||||
// salient nodes, so we walk the ranked list and take the first that is
|
||||
// genuinely "close"; if none is, the query landed nowhere. ───────────────
|
||||
let landing: String = ""
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
if str_eq(landing, "") {
|
||||
let rec: String = json_array_get(act, i)
|
||||
let node: String = json_get_raw(rec, "node")
|
||||
if dlg_node_matches(node, terms) {
|
||||
let landing = node
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// ── HONEST ABSENCE: nothing close — an empty region, not a fabricated answer,
|
||||
// not an "I noted that" echo. ────────────────────────────────────────────
|
||||
if str_eq(landing, "") {
|
||||
return ml_tr("no_memory", reply_lang)
|
||||
}
|
||||
|
||||
// ── MATERIALIZE the landing by WALKING its neighborhood. ──────────────────
|
||||
return dlg_materialize(landing, reply_lang)
|
||||
}
|
||||
@@ -63,9 +63,6 @@ import "morphology-cop.el"
|
||||
import "grammar.el"
|
||||
import "realizer.el"
|
||||
import "semantics.el"
|
||||
|
||||
// ── Comprehension front-end (input half: text → meaning-spec) ─────────────────
|
||||
import "comprehend.el"
|
||||
//
|
||||
// Entry points:
|
||||
//
|
||||
@@ -120,9 +117,6 @@ fn build_form_from_json(semantic_form_json: String, lang_code: String) -> [Strin
|
||||
let location: String = sem_get(semantic_form_json, "location")
|
||||
let tense: String = sem_get(semantic_form_json, "tense")
|
||||
let aspect: String = sem_get(semantic_form_json, "aspect")
|
||||
let polarity: String = sem_get(semantic_form_json, "polarity")
|
||||
let neg_word: String = sem_get(semantic_form_json, "neg_word")
|
||||
let iobj: String = sem_get(semantic_form_json, "iobj")
|
||||
|
||||
let form: [String] = native_list_empty()
|
||||
let form = native_list_append(form, "intent")
|
||||
@@ -133,19 +127,12 @@ fn build_form_from_json(semantic_form_json: String, lang_code: String) -> [Strin
|
||||
let form = native_list_append(form, predicate)
|
||||
let form = native_list_append(form, "patient")
|
||||
let form = native_list_append(form, patient)
|
||||
let form = native_list_append(form, "iobj")
|
||||
let form = native_list_append(form, iobj)
|
||||
let form = native_list_append(form, "location")
|
||||
let form = native_list_append(form, location)
|
||||
let form = native_list_append(form, "tense")
|
||||
let form = native_list_append(form, tense)
|
||||
let form = native_list_append(form, "aspect")
|
||||
let form = native_list_append(form, aspect)
|
||||
// SACRED: polarity crosses the JSON boundary and is never inferred away.
|
||||
let form = native_list_append(form, "polarity")
|
||||
let form = native_list_append(form, polarity)
|
||||
let form = native_list_append(form, "neg_word")
|
||||
let form = native_list_append(form, neg_word)
|
||||
let form = native_list_append(form, "lang")
|
||||
let form = native_list_append(form, lang_code)
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn sem_get(json: String, key: String) -> String
|
||||
extern fn generate_frame(frame: [String]) -> String
|
||||
extern fn generate_frame_lang(frame: [String], lang_code: String) -> String
|
||||
extern fn build_form_from_json(semantic_form_json: String, lang_code: String) -> [String]
|
||||
extern fn generate_frame(frame: Any) -> String
|
||||
extern fn generate_frame_lang(frame: Any, lang_code: String) -> String
|
||||
extern fn build_form_from_json(semantic_form_json: String, lang_code: String) -> Any
|
||||
extern fn generate(semantic_form_json: String) -> String
|
||||
extern fn generate_lang(semantic_form_json: String, lang_code: String) -> String
|
||||
|
||||
+28
-28
@@ -1,22 +1,22 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn slots_get(slots: [String], key: String) -> String
|
||||
extern fn slots_set(slots: [String], key: String, val: String) -> [String]
|
||||
extern fn make_slots(k0: String, v0: String) -> [String]
|
||||
extern fn make_slots2(k0: String, v0: String, k1: String, v1: String) -> [String]
|
||||
extern fn make_slots3(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String) -> [String]
|
||||
extern fn make_slots4(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String) -> [String]
|
||||
extern fn make_slots5(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String, k4: String, v4: String) -> [String]
|
||||
extern fn rule_id(rule: [String]) -> String
|
||||
extern fn rule_lhs(rule: [String]) -> String
|
||||
extern fn rule_rhs_len(rule: [String]) -> Int
|
||||
extern fn rule_rhs(rule: [String], idx: Int) -> String
|
||||
extern fn make_rule(id: String, lhs: String, r0: String) -> [String]
|
||||
extern fn make_rule2(id: String, lhs: String, r0: String, r1: String) -> [String]
|
||||
extern fn make_rule3(id: String, lhs: String, r0: String, r1: String, r2: String) -> [String]
|
||||
extern fn make_rule4(id: String, lhs: String, r0: String, r1: String, r2: String, r3: String) -> [String]
|
||||
extern fn build_rules() -> [[String]]
|
||||
extern fn get_rules() -> [[String]]
|
||||
extern fn find_rule(rule_id_str: String) -> [String]
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
extern fn slots_get(slots: Any, key: String) -> String
|
||||
extern fn slots_set(slots: Any, key: String, val: String) -> Any
|
||||
extern fn make_slots(k0: String, v0: String) -> Any
|
||||
extern fn make_slots2(k0: String, v0: String, k1: String, v1: String) -> Any
|
||||
extern fn make_slots3(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String) -> Any
|
||||
extern fn make_slots4(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String) -> Any
|
||||
extern fn make_slots5(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String, k4: String, v4: String) -> Any
|
||||
extern fn rule_id(rule: Any) -> String
|
||||
extern fn rule_lhs(rule: Any) -> String
|
||||
extern fn rule_rhs_len(rule: Any) -> Int
|
||||
extern fn rule_rhs(rule: Any, idx: Int) -> String
|
||||
extern fn make_rule(id: String, lhs: String, r0: String) -> Any
|
||||
extern fn make_rule2(id: String, lhs: String, r0: String, r1: String) -> Any
|
||||
extern fn make_rule3(id: String, lhs: String, r0: String, r1: String, r2: String) -> Any
|
||||
extern fn make_rule4(id: String, lhs: String, r0: String, r1: String, r2: String, r3: String) -> Any
|
||||
extern fn build_rules() -> Any
|
||||
extern fn get_rules() -> Any
|
||||
extern fn find_rule(rule_id_str: String) -> Any
|
||||
extern fn make_leaf(label: String, word: String) -> String
|
||||
extern fn make_node1(label: String, child0: String) -> String
|
||||
extern fn make_node2(label: String, child0: String, child1: String) -> String
|
||||
@@ -24,15 +24,15 @@ extern fn make_node3(label: String, child0: String, child1: String, child2: Stri
|
||||
extern fn make_node4(label: String, child0: String, child1: String, child2: String, child3: String) -> String
|
||||
extern fn nlg_is_ws(c: String) -> Bool
|
||||
extern fn skip_ws(s: String, pos: Int) -> Int
|
||||
extern fn scan_token(s: String, start: Int) -> [String]
|
||||
extern fn scan_token(s: String, start: Int) -> Any
|
||||
extern fn render_tree(tree: String) -> String
|
||||
extern fn gram_word_order(profile: [String]) -> String
|
||||
extern fn gram_order_constituents(subj: String, verb: String, obj: String, profile: [String]) -> String
|
||||
extern fn gram_build_vp(verb: String, aux: String, profile: [String]) -> String
|
||||
extern fn gram_question_strategy(profile: [String]) -> String
|
||||
extern fn gram_word_order(profile: Any) -> String
|
||||
extern fn gram_order_constituents(subj: String, verb: String, obj: String, profile: Any) -> String
|
||||
extern fn gram_build_vp(verb: String, aux: String, profile: Any) -> String
|
||||
extern fn gram_question_strategy(profile: Any) -> String
|
||||
extern fn is_pronoun(word: String) -> Bool
|
||||
extern fn build_np(referent: String, slots: [String]) -> String
|
||||
extern fn build_np(referent: String, slots: Any) -> String
|
||||
extern fn build_pp(loc: String) -> String
|
||||
extern fn build_vp_body(slots: [String]) -> String
|
||||
extern fn build_vp_from_slots(slots: [String]) -> String
|
||||
extern fn generate_tree(rule_id_str: String, slots: [String]) -> String
|
||||
extern fn build_vp_body(slots: Any) -> String
|
||||
extern fn build_vp_from_slots(slots: Any) -> String
|
||||
extern fn generate_tree(rule_id_str: String, slots: Any) -> String
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
// image-demo.el - Drive the native PNG surface: plan a scene from a small
|
||||
// meaning phrase (incl. a NEG frame) and emit a byte-valid 64x64 PNG whose
|
||||
// palette is read from elp/faculty/sig/scene.basis.
|
||||
|
||||
fn img_frame(relation: String, polarity: String, confidence: String, importance: String, salience: String, subj_id: String) -> [String] {
|
||||
let f: [String] = native_list_empty()
|
||||
let f: [String] = native_list_append(f, "relation")
|
||||
let f: [String] = native_list_append(f, relation)
|
||||
let f: [String] = native_list_append(f, "polarity")
|
||||
let f: [String] = native_list_append(f, polarity)
|
||||
let f: [String] = native_list_append(f, "confidence")
|
||||
let f: [String] = native_list_append(f, confidence)
|
||||
let f: [String] = native_list_append(f, "importance")
|
||||
let f: [String] = native_list_append(f, importance)
|
||||
let f: [String] = native_list_append(f, "salience")
|
||||
let f: [String] = native_list_append(f, salience)
|
||||
let f: [String] = native_list_append(f, "subj_id")
|
||||
let f: [String] = native_list_append(f, subj_id)
|
||||
return f
|
||||
}
|
||||
|
||||
fn rgb_str(c: [Int]) -> String {
|
||||
return int_to_str(native_list_get(c, 0)) + "," + int_to_str(native_list_get(c, 1)) + "," + int_to_str(native_list_get(c, 2))
|
||||
}
|
||||
|
||||
fn run_image() -> Int {
|
||||
fs_mkdir("/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out")
|
||||
let table: [Int] = crc_table()
|
||||
println("crc_table[1]=" + int_to_str(native_list_get(table, 1)) + " (expect 1996959894 / 0x77073096)")
|
||||
|
||||
let basis: [String] = basis_load("/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/sig/scene.basis")
|
||||
let warm: [Int] = parse_rgb(basis_field(basis, "warm"))
|
||||
let cool: [Int] = parse_rgb(basis_field(basis, "cool"))
|
||||
let bg: [Int] = parse_rgb(basis_field(basis, "bg"))
|
||||
println("basis warm=" + rgb_str(warm) + " cool=" + rgb_str(cool) + " bg=" + rgb_str(bg) + " (read from scene.basis)")
|
||||
|
||||
let frames: [[String]] = native_list_empty()
|
||||
let frames: [[String]] = native_list_append(frames, img_frame("agent", "aff", "0.9", "0.8", "0", "s1"))
|
||||
let frames: [[String]] = native_list_append(frames, img_frame("theme", "aff", "0.7", "0.6", "1", "s2"))
|
||||
let frames: [[String]] = native_list_append(frames, img_frame("cause", "aff", "0.8", "0.9", "0", "s3"))
|
||||
let frames: [[String]] = native_list_append(frames, img_frame("negation", "neg", "0.85", "0.7", "1", "s4"))
|
||||
let frames: [[String]] = native_list_append(frames, img_frame("goal", "aff", "0.6", "0.5", "0", "s5"))
|
||||
let frames: [[String]] = native_list_append(frames, img_frame("result", "aff", "0.95", "1.0", "1", "s6"))
|
||||
|
||||
let shapes: [[Int]] = plan_scene(frames, warm, cool)
|
||||
let ns: Int = native_list_len(shapes)
|
||||
println("planned " + int_to_str(ns) + " shapes:")
|
||||
let si: Int = 0
|
||||
while si < ns {
|
||||
let sh: [Int] = native_list_get(shapes, si)
|
||||
let pol: String = surface_get(native_list_get(frames, si), "polarity")
|
||||
println(" shape " + int_to_str(si) + " type=" + int_to_str(native_list_get(sh, 0)) + " x=" + int_to_str(native_list_get(sh, 1)) + " y=" + int_to_str(native_list_get(sh, 2)) + " size=" + int_to_str(native_list_get(sh, 3)) + " rgb=" + int_to_str(native_list_get(sh, 4)) + "," + int_to_str(native_list_get(sh, 5)) + "," + int_to_str(native_list_get(sh, 6)) + " polarity=" + pol)
|
||||
let si: Int = si + 1
|
||||
}
|
||||
|
||||
let raw: [Int] = rasterize(64, 64, shapes, bg)
|
||||
println("rasterized raw (filtered scanlines) bytes=" + int_to_str(native_list_len(raw)) + " (expect 12352)")
|
||||
let png: [Int] = png_build(64, 64, raw, table)
|
||||
let plen: Int = native_list_len(png)
|
||||
let ok: Int = png_write("/Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out/scene.png", png)
|
||||
println("PNG bytes=" + int_to_str(plen) + " -> /Users/will/Development/neuron-technologies/foundation/el/.claude/worktrees/agent-aaf04b0a9714c4070/elp/faculty/out/scene.png (write_ok=" + int_to_str(ok) + ")")
|
||||
return plen
|
||||
}
|
||||
|
||||
println("image-demo returned png_bytes=" + int_to_str(run_image()))
|
||||
@@ -1,412 +0,0 @@
|
||||
// image-surface.el - Native own-core raster PNG surface (the image efferent
|
||||
// twin of audio). Renders a 64x64 RGB scene deterministically from a frame's
|
||||
// meaning-geometry, then serialises a byte-valid PNG entirely own-core:
|
||||
// 8-byte magic, IHDR, IDAT (zlib STORED/uncompressed DEFLATE + Adler32), IEND,
|
||||
// with a per-chunk CRC32 computed via software xor32 (EL has no bitwise ops).
|
||||
//
|
||||
// The RGB palette basis is read from elp/faculty/sig/scene.basis (data, not
|
||||
// literals) - the same read-from-learned discipline as the audio signatures.
|
||||
// Integer-only throughout; pixels are composed functionally (painter's order)
|
||||
// so no list mutation is needed.
|
||||
|
||||
// -- small int/parse helpers (self-contained) ----------------------------------
|
||||
|
||||
fn i_str_to_int(s: String) -> Int {
|
||||
let n: Int = str_len(s)
|
||||
let i: Int = 0
|
||||
let v: Int = 0
|
||||
while i < n {
|
||||
let c: Int = str_char_code(s, i)
|
||||
if c >= 48 {
|
||||
if c < 58 {
|
||||
let v: Int = v * 10 + (c - 48)
|
||||
}
|
||||
}
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
fn basis_load(path: String) -> [String] {
|
||||
return str_split(fs_read(path), "\n")
|
||||
}
|
||||
|
||||
fn basis_field(lines: [String], key: String) -> String {
|
||||
let pref: String = key + ": "
|
||||
let n: Int = native_list_len(lines)
|
||||
let plen: Int = str_len(pref)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let ln: String = native_list_get(lines, i)
|
||||
if str_starts_with(ln, pref) {
|
||||
return str_slice(ln, plen, str_len(ln))
|
||||
}
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn parse_rgb(csv: String) -> [Int] {
|
||||
let parts: [String] = str_split(csv, ",")
|
||||
let out: [Int] = native_list_empty()
|
||||
let n: Int = native_list_len(parts)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let v: Int = i_str_to_int(native_list_get(parts, i))
|
||||
let out: [Int] = native_list_append(out, v)
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// -- software 32-bit XOR (no bitwise ops in EL) --------------------------------
|
||||
|
||||
fn xor32(a: Int, b: Int) -> Int {
|
||||
let r: Int = 0
|
||||
let bit: Int = 1
|
||||
let i: Int = 0
|
||||
while i < 32 {
|
||||
let abit: Int = (a / bit) % 2
|
||||
let bbit: Int = (b / bit) % 2
|
||||
if abit != bbit {
|
||||
let add: Int = bit
|
||||
let r: Int = r + add
|
||||
}
|
||||
let bit: Int = bit * 2
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// -- CRC32 (table-driven, table built with xor32) ------------------------------
|
||||
|
||||
fn crc_table() -> [Int] {
|
||||
let t: [Int] = native_list_empty()
|
||||
let n: Int = 0
|
||||
while n < 256 {
|
||||
let c: Int = n
|
||||
let k: Int = 0
|
||||
while k < 8 {
|
||||
if c % 2 == 1 {
|
||||
let h: Int = c / 2
|
||||
let c: Int = xor32(h, 3988292384)
|
||||
} else {
|
||||
let c: Int = c / 2
|
||||
}
|
||||
let k: Int = k + 1
|
||||
}
|
||||
let t: [Int] = native_list_append(t, c)
|
||||
let n: Int = n + 1
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
fn crc32_of(bytes: [Int], table: [Int]) -> Int {
|
||||
let crc: Int = 4294967295
|
||||
let n: Int = native_list_len(bytes)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let b: Int = native_list_get(bytes, i)
|
||||
let lo: Int = crc % 256
|
||||
let idx: Int = xor32(lo, b) % 256
|
||||
let tv: Int = native_list_get(table, idx)
|
||||
let hi: Int = crc / 256
|
||||
let crc: Int = xor32(hi, tv)
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return xor32(crc, 4294967295)
|
||||
}
|
||||
|
||||
// -- Adler32 (for the zlib trailer) --------------------------------------------
|
||||
|
||||
fn adler32_of(bytes: [Int]) -> Int {
|
||||
let a: Int = 1
|
||||
let b: Int = 0
|
||||
let n: Int = native_list_len(bytes)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let byte: Int = native_list_get(bytes, i)
|
||||
let a: Int = (a + byte) % 65521
|
||||
let b: Int = (b + a) % 65521
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return b * 65536 + a
|
||||
}
|
||||
|
||||
// -- byte-list append helpers --------------------------------------------------
|
||||
|
||||
fn app_u32be(dst: [Int], v: Int) -> [Int] {
|
||||
let dst: [Int] = native_list_append(dst, (v / 16777216) % 256)
|
||||
let dst: [Int] = native_list_append(dst, (v / 65536) % 256)
|
||||
let dst: [Int] = native_list_append(dst, (v / 256) % 256)
|
||||
let dst: [Int] = native_list_append(dst, v % 256)
|
||||
return dst
|
||||
}
|
||||
|
||||
fn app_tag(dst: [Int], s: String) -> [Int] {
|
||||
let n: Int = str_len(s)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let dst: [Int] = native_list_append(dst, str_char_code(s, i))
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
fn app_all(dst: [Int], src: [Int]) -> [Int] {
|
||||
let n: Int = native_list_len(src)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let dst: [Int] = native_list_append(dst, native_list_get(src, i))
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// -- plan: frame meaning-geometry -> shape atoms -------------------------------
|
||||
// shape = [type, x, y, size, r, g, b] (type 0=rect 1=disc 2=triangle)
|
||||
|
||||
fn charsum(s: String) -> Int {
|
||||
let n: Int = str_len(s)
|
||||
let i: Int = 0
|
||||
let acc: Int = 0
|
||||
while i < n {
|
||||
let c: Int = str_char_code(s, i)
|
||||
let acc: Int = acc + c
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
fn micro_of(s: String) -> Int {
|
||||
let dot: Int = str_index_of(s, ".")
|
||||
if dot < 0 { return i_str_to_int(s) * 1000000 }
|
||||
let n: Int = str_len(s)
|
||||
let fp: String = str_slice(s, dot + 1, n)
|
||||
let ip: String = str_slice(s, 0, dot)
|
||||
let iv: Int = i_str_to_int(ip)
|
||||
let fv: Int = 0
|
||||
let scale: Int = 100000
|
||||
let fl: Int = str_len(fp)
|
||||
let i: Int = 0
|
||||
while i < 6 {
|
||||
let d: Int = 0
|
||||
if i < fl { let d: Int = str_char_code(fp, i) - 48 }
|
||||
let fv: Int = fv + d * scale
|
||||
let scale: Int = scale / 10
|
||||
let i: Int = i + 1
|
||||
}
|
||||
return iv * 1000000 + fv
|
||||
}
|
||||
|
||||
fn plan_scene(frames: [[String]], warm: [Int], cool: [Int]) -> [[Int]] {
|
||||
let shapes: [[Int]] = native_list_empty()
|
||||
let nf: Int = native_list_len(frames)
|
||||
let fi: Int = 0
|
||||
while fi < nf {
|
||||
let fr: [String] = native_list_get(frames, fi)
|
||||
let relation: String = surface_get(fr, "relation")
|
||||
let polarity: String = surface_get(fr, "polarity")
|
||||
let confidence: String = surface_get(fr, "confidence")
|
||||
let importance: String = surface_get(fr, "importance")
|
||||
let salience: String = surface_get(fr, "salience")
|
||||
// relation -> shape type
|
||||
let stype: Int = charsum(relation) % 3
|
||||
// confidence -> size (8..22)
|
||||
let cmi: Int = micro_of(confidence)
|
||||
let size: Int = 8 + cmi / 71428
|
||||
// salience -> y
|
||||
let sal: Int = i_str_to_int(salience)
|
||||
let y: Int = 6 + sal * 26
|
||||
// subj_id/index -> x
|
||||
let x: Int = 4 + (fi * 10) % 48
|
||||
// polarity -> warm/cool base color
|
||||
let br: Int = native_list_get(warm, 0)
|
||||
let bg2: Int = native_list_get(warm, 1)
|
||||
let bb: Int = native_list_get(warm, 2)
|
||||
if str_eq(polarity, "neg") {
|
||||
let br: Int = native_list_get(cool, 0)
|
||||
let bg2: Int = native_list_get(cool, 1)
|
||||
let bb: Int = native_list_get(cool, 2)
|
||||
}
|
||||
// importance -> brightness (500..1000 permille)
|
||||
let imi: Int = micro_of(importance)
|
||||
let bpm: Int = 500 + imi / 2000
|
||||
let r: Int = br * bpm / 1000
|
||||
let g: Int = bg2 * bpm / 1000
|
||||
let b: Int = bb * bpm / 1000
|
||||
let sh: [Int] = native_list_empty()
|
||||
let sh: [Int] = native_list_append(sh, stype)
|
||||
let sh: [Int] = native_list_append(sh, x)
|
||||
let sh: [Int] = native_list_append(sh, y)
|
||||
let sh: [Int] = native_list_append(sh, size)
|
||||
let sh: [Int] = native_list_append(sh, r)
|
||||
let sh: [Int] = native_list_append(sh, g)
|
||||
let sh: [Int] = native_list_append(sh, b)
|
||||
let shapes: [[Int]] = native_list_append(shapes, sh)
|
||||
let fi: Int = fi + 1
|
||||
}
|
||||
return shapes
|
||||
}
|
||||
|
||||
// covers: is (px,py) inside this shape?
|
||||
fn covers(sh: [Int], px: Int, py: Int) -> Bool {
|
||||
let stype: Int = native_list_get(sh, 0)
|
||||
let sx: Int = native_list_get(sh, 1)
|
||||
let sy: Int = native_list_get(sh, 2)
|
||||
let size: Int = native_list_get(sh, 3)
|
||||
let cx: Int = sx + size / 2
|
||||
if stype == 0 {
|
||||
if px >= sx {
|
||||
if px < sx + size {
|
||||
if py >= sy {
|
||||
if py < sy + size {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if stype == 1 {
|
||||
let rad: Int = size / 2
|
||||
let dx: Int = px - cx
|
||||
let dy: Int = py - (sy + rad)
|
||||
if dx * dx + dy * dy <= rad * rad {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
// triangle: apex at top (sy), base at sy+size
|
||||
if py >= sy {
|
||||
if py < sy + size {
|
||||
let dyv: Int = py - sy
|
||||
let halfw: Int = dyv / 2
|
||||
let dxv: Int = px - cx
|
||||
let adx: Int = dxv
|
||||
if adx < 0 { let adx: Int = 0 - dxv }
|
||||
if adx <= halfw {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// pixel_color: painter's algorithm - last covering shape wins. Returns [r,g,b].
|
||||
fn pixel_color(px: Int, py: Int, shapes: [[Int]], bg: [Int]) -> [Int] {
|
||||
let r: Int = native_list_get(bg, 0)
|
||||
let g: Int = native_list_get(bg, 1)
|
||||
let b: Int = native_list_get(bg, 2)
|
||||
let n: Int = native_list_len(shapes)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let sh: [Int] = native_list_get(shapes, i)
|
||||
if covers(sh, px, py) {
|
||||
let r: Int = native_list_get(sh, 4)
|
||||
let g: Int = native_list_get(sh, 5)
|
||||
let b: Int = native_list_get(sh, 6)
|
||||
}
|
||||
let i: Int = i + 1
|
||||
}
|
||||
let out: [Int] = native_list_empty()
|
||||
let out: [Int] = native_list_append(out, r)
|
||||
let out: [Int] = native_list_append(out, g)
|
||||
let out: [Int] = native_list_append(out, b)
|
||||
return out
|
||||
}
|
||||
|
||||
// rasterize: build the raw (filtered) scanline byte stream, filter byte 0 / row.
|
||||
fn rasterize(w: Int, h: Int, shapes: [[Int]], bg: [Int]) -> [Int] {
|
||||
let raw: [Int] = native_list_empty()
|
||||
let y: Int = 0
|
||||
while y < h {
|
||||
let raw: [Int] = native_list_append(raw, 0)
|
||||
let x: Int = 0
|
||||
while x < w {
|
||||
let col: [Int] = pixel_color(x, y, shapes, bg)
|
||||
let raw: [Int] = native_list_append(raw, native_list_get(col, 0))
|
||||
let raw: [Int] = native_list_append(raw, native_list_get(col, 1))
|
||||
let raw: [Int] = native_list_append(raw, native_list_get(col, 2))
|
||||
let x: Int = x + 1
|
||||
}
|
||||
let y: Int = y + 1
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// zlib stream with a single STORED (uncompressed) DEFLATE block + Adler32.
|
||||
fn zlib_store(raw: [Int]) -> [Int] {
|
||||
let z: [Int] = native_list_empty()
|
||||
let z: [Int] = native_list_append(z, 120)
|
||||
let z: [Int] = native_list_append(z, 1)
|
||||
let z: [Int] = native_list_append(z, 1)
|
||||
let len: Int = native_list_len(raw)
|
||||
let nlen: Int = 65535 - len
|
||||
let z: [Int] = native_list_append(z, len % 256)
|
||||
let z: [Int] = native_list_append(z, (len / 256) % 256)
|
||||
let z: [Int] = native_list_append(z, nlen % 256)
|
||||
let z: [Int] = native_list_append(z, (nlen / 256) % 256)
|
||||
let z: [Int] = app_all(z, raw)
|
||||
let ad: Int = adler32_of(raw)
|
||||
let z: [Int] = app_u32be(z, ad)
|
||||
return z
|
||||
}
|
||||
|
||||
// append a full PNG chunk: length + (type+data) + crc32(type+data).
|
||||
fn app_chunk(png: [Int], type_and_data: [Int], table: [Int]) -> [Int] {
|
||||
let total: Int = native_list_len(type_and_data)
|
||||
let dlen: Int = total - 4
|
||||
let png: [Int] = app_u32be(png, dlen)
|
||||
let png: [Int] = app_all(png, type_and_data)
|
||||
let crc: Int = crc32_of(type_and_data, table)
|
||||
let png: [Int] = app_u32be(png, crc)
|
||||
return png
|
||||
}
|
||||
|
||||
fn png_build(w: Int, h: Int, raw: [Int], table: [Int]) -> [Int] {
|
||||
let png: [Int] = native_list_empty()
|
||||
// 8-byte signature
|
||||
let png: [Int] = native_list_append(png, 137)
|
||||
let png: [Int] = native_list_append(png, 80)
|
||||
let png: [Int] = native_list_append(png, 78)
|
||||
let png: [Int] = native_list_append(png, 71)
|
||||
let png: [Int] = native_list_append(png, 13)
|
||||
let png: [Int] = native_list_append(png, 10)
|
||||
let png: [Int] = native_list_append(png, 26)
|
||||
let png: [Int] = native_list_append(png, 10)
|
||||
// IHDR
|
||||
let ihdr: [Int] = native_list_empty()
|
||||
let ihdr: [Int] = app_tag(ihdr, "IHDR")
|
||||
let ihdr: [Int] = app_u32be(ihdr, w)
|
||||
let ihdr: [Int] = app_u32be(ihdr, h)
|
||||
let ihdr: [Int] = native_list_append(ihdr, 8)
|
||||
let ihdr: [Int] = native_list_append(ihdr, 2)
|
||||
let ihdr: [Int] = native_list_append(ihdr, 0)
|
||||
let ihdr: [Int] = native_list_append(ihdr, 0)
|
||||
let ihdr: [Int] = native_list_append(ihdr, 0)
|
||||
let png: [Int] = app_chunk(png, ihdr, table)
|
||||
// IDAT
|
||||
let z: [Int] = zlib_store(raw)
|
||||
let idat: [Int] = native_list_empty()
|
||||
let idat: [Int] = app_tag(idat, "IDAT")
|
||||
let idat: [Int] = app_all(idat, z)
|
||||
let png: [Int] = app_chunk(png, idat, table)
|
||||
// IEND
|
||||
let iend: [Int] = native_list_empty()
|
||||
let iend: [Int] = app_tag(iend, "IEND")
|
||||
let png: [Int] = app_chunk(png, iend, table)
|
||||
return png
|
||||
}
|
||||
|
||||
fn png_write(path: String, png: [Int]) -> Int {
|
||||
let n: Int = native_list_len(png)
|
||||
let buf: String = __str_alloc(n)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let buf: String = __str_set_char(buf, i, native_list_get(png, i))
|
||||
let i: Int = i + 1
|
||||
}
|
||||
let ok: Int = fs_write_bytes(path, buf, n)
|
||||
return ok
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
;;; lang_profile_ca.el — Catalan language profile for ELP.
|
||||
;;; Mirrors lang_profile_it / _es / _pt; keys the realizer's construction switches.
|
||||
;;; Catalan is the CLOSEST Romance sibling to the shared engine (~85% conceptual
|
||||
;;; reuse). The deltas: PRONOMS FEBLES with four position allomorphs, l'-elision,
|
||||
;;; del/al/pel contractions, the periphrastic preterite (vaig+INF), and NO
|
||||
;;; essere/avere split (perfect aux is always HAVER; ser/estar is only the copula).
|
||||
|
||||
(lang_profile_ca
|
||||
(language "Catalan")
|
||||
(iso639 "ca")
|
||||
(family "Romance")
|
||||
|
||||
;; ── core typology flags ────────────────────────────────────────────────
|
||||
(pro-drop yes) ; null subjects default; overt pronoun = emphatic
|
||||
(obligatory-subject no)
|
||||
(grammatical-gender yes) ; m/f; full NP agreement (art + adj + participle)
|
||||
(do-support no)
|
||||
(subject-aux-inversion no) ; yes/no Q = declarative order + '?'; no inversion
|
||||
(article-selection "el/la/l'/els/les ; un/una/uns/unes") ; l'-ELISION:
|
||||
; el/la -> l' before vowel or (silent) h, glued to
|
||||
; the next word (l'home, l'illa); de -> d' before vowel
|
||||
(article-drives-contraction yes) ; article choice feeds prep+article contraction
|
||||
(adjective-position "postnominal-default + small prenominal class") ; bo/bon,
|
||||
; mal, gran, nou, vell, primer, molt... prenominal
|
||||
(question-punct plain) ; ? and ! only (no inverted ¿ ¡)
|
||||
|
||||
;; ── MANDATORY prep+article contractions ────────────────────────────────
|
||||
(contractions ((de el del) (de els dels)
|
||||
(a el al) (a els als)
|
||||
(per el pel) (per els pels)))
|
||||
(contraction-mandatory yes) ; *de el -> del obligatory
|
||||
(contraction-blocked-before-elision yes) ; de l'home / a l'home (NO *del home)
|
||||
|
||||
;; ── clitic system: PRONOMS FEBLES (the headline delta) ──────────────────
|
||||
(clitics yes)
|
||||
(clitic-allomorphy four-position) ; per pronoun, form varies by position+onset:
|
||||
; reinforced (em, et, el) proclitic before a consonant
|
||||
; elided (m', t', l', n') proclitic before a vowel/h
|
||||
; full (-me, -lo, -li) enclitic after a consonant/-r
|
||||
; reduced ('m, 't, 'l, 'ns) enclitic after a vowel
|
||||
(clitic-placement ((finite proclitic) ; el veig, no m'ho dóna
|
||||
(imperative-affirmative enclitic) ; dóna'm, digues-me
|
||||
(imperative-negative present-subjunctive) ; no parlis (delta)
|
||||
(infinitive enclitic) ; ajudar-me, veure'l
|
||||
(gerund enclitic))) ; fent-ho
|
||||
(clitic-combination ((me el "me'l") (te el "te'l") (se el "se'l")
|
||||
(me la "me la") (me en "me'n")
|
||||
(li el "l'hi") (li en "n'hi"))) ; dative+accusative clusters
|
||||
(clitic-particles (hi en ho)) ; locative hi, partitive/genitive en, neuter ho
|
||||
|
||||
;; ── verb / aspect system ───────────────────────────────────────────────
|
||||
(finite-agreement "person+number (6-way)")
|
||||
(tenses (present imperfet preterit-simple perifrastic-preterit futur
|
||||
condicional subjuntiu-present subjuntiu-imperfet imperatiu))
|
||||
(periphrastic-preterite "vaig/vas/va/vam/vau/van + INFINITIVE") ; << hallmark CA
|
||||
; (vaig cantar = 'I sang'); coexists w/ synthetic pret.
|
||||
(compound-past "pretèrit perfet = haver(present) + participle")
|
||||
(perfect-aux "HAVER only") ; << NO essere/avere split (simpler than IT)
|
||||
(participle-agreement ((haver preceding-acc-clitic))) ; les he vistes; else invariable
|
||||
(progressive-aux "estar + gerundi")
|
||||
(copula "ser / estar") ; ser: identity/essential/origin; estar:
|
||||
; location + transient state (estic cansat, és a casa)
|
||||
(passive-aux "ser (+ per-agent)")
|
||||
(future inflectional) ; cantaré, serà
|
||||
(comparative "més/menys ADJ que")
|
||||
|
||||
;; ── SACRED safety bar (shared with es/pt/it/en) ────────────────────────
|
||||
(negation-faithful yes) ; polarity never dropped/inverted; unplaceable -> FLAG
|
||||
(negation "no (preverbal) + optional 'pas' + concord") ; no...res/
|
||||
; ningú/mai/cap/gens/enlloc
|
||||
(negative-concord yes) ; preverbal negative subject (ningú) keeps 'no'
|
||||
(neg-reinforcer pas)) ; optional (no ho faré pas)
|
||||
@@ -1,41 +0,0 @@
|
||||
;;; lang_profile_de.el — German language profile for ELP.
|
||||
;;; Mirrors lang_profile_en / lang_profile_es. Keys the realizer's construction
|
||||
;;; switches. German is the largest Germanic delta from the EN engine: V2 word
|
||||
;;; order, four morphological cases, and separable-prefix verbs.
|
||||
|
||||
(lang_profile_de
|
||||
(language "German")
|
||||
(iso639 "de")
|
||||
(family "Germanic")
|
||||
(neighbor-base "en") ; realized by extending the English (Germanic) engine
|
||||
|
||||
;; ── core typology flags ────────────────────────────────────────────────
|
||||
(pro-drop no) ; obligatory subject in finite clauses
|
||||
(obligatory-subject yes)
|
||||
(grammatical-gender (m f n)) ; three genders; drives article + adj declension
|
||||
(case-system (nom acc dat gen)) ; four cases on articles/adjs/nouns
|
||||
(word-order V2) ; finite verb 2nd in main clause
|
||||
(subordinate-order verb-final) ; "..., dass er den Hund SIEHT."
|
||||
(separable-verbs yes) ; aufstehen -> "steht ... auf"; ppart "aufgestanden"
|
||||
(do-support no) ; German negates/questions the finite verb directly
|
||||
(subject-verb-inversion yes) ; yes/no Q fronts finite verb; wh-Q fills Vorfeld
|
||||
(article-selection "der/die/das + ein/kein") ; declined by case x gender x number
|
||||
(adjective-position prenominal)
|
||||
(adjective-declension (strong weak mixed)) ; chosen by the determiner type
|
||||
(noun-capitalization yes)
|
||||
|
||||
;; ── verb / aspect system ───────────────────────────────────────────────
|
||||
(finite-agreement "person-and-number") ; full present/past paradigm
|
||||
(auxiliary-order (modal tense-aux perfect passive main))
|
||||
(perfect-aux (haben sein)) ; sein for intransitive motion/change verbs
|
||||
(passive-aux "werden")
|
||||
(future "werden + infinitive")
|
||||
(comparative "synthetic (-er / -st, with umlaut)")
|
||||
|
||||
;; ── negation ───────────────────────────────────────────────────────────
|
||||
(negation-markers (nicht kein)) ; kein- negates an indefinite NP; nicht else
|
||||
(negation-faithful yes) ; SACRED: polarity never dropped/inverted -> FLAG
|
||||
|
||||
;; ── lexicon provenance ─────────────────────────────────────────────────
|
||||
(lexicon-source "UniMorph deu (primary) + kaikki.org German (gender override)")
|
||||
(lexicon-license "CC-BY-SA 3.0 / GFDL"))
|
||||
@@ -1,41 +0,0 @@
|
||||
;;; lang_profile_en.el — English language profile for ELP.
|
||||
;;; Mirrors lang_profile_es / lang_profile_pt; keys the realizer's construction
|
||||
;;; switches. English is typologically distinct from the Romance builds, so the
|
||||
;;; flags differ where the grammar differs.
|
||||
|
||||
(lang_profile_en
|
||||
(language "English")
|
||||
(iso639 "en")
|
||||
(family "Germanic")
|
||||
|
||||
;; ── core typology flags ────────────────────────────────────────────────
|
||||
(pro-drop no) ; OBLIGATORY subjects — missing subject is FLAGGED
|
||||
(obligatory-subject yes)
|
||||
(grammatical-gender no) ; natural gender only (he/she/it), no NP agreement
|
||||
(do-support yes) ; negation & questions of lexical verbs insert do/does/did
|
||||
(subject-aux-inversion yes) ; yes/no + non-subject wh questions invert the operator
|
||||
(article-selection "a/an/the") ; a/an resolved PHONOLOGICALLY (an hour, a university)
|
||||
(adjective-position prenominal) ; attributive adjectives precede the noun; invariant
|
||||
(has-tag-questions yes) ; "...doesn't he?" — operator + reversed polarity
|
||||
(has-there-existential yes) ; "there is/are/have been ..."
|
||||
(possessive-clitic "'s") ; saxon genitive; plural in -s -> bare apostrophe
|
||||
(question-punct plain) ; ? and ! only (no inverted marks)
|
||||
|
||||
;; ── verb / aspect system ───────────────────────────────────────────────
|
||||
(finite-agreement "3sg-present-only") ; only 3sg present -s (+ suppletive be)
|
||||
(auxiliary-order (modal perfect progressive passive main))
|
||||
(perfect-aux "have") ; have + past participle
|
||||
(progressive-aux "be") ; be + present participle
|
||||
(passive-aux "be") ; be + past participle (+ by-agent)
|
||||
(future "will + base") ; no inflectional future
|
||||
(comparative "synthetic-or-periphrastic") ; -er/-est vs more/most by syllables
|
||||
|
||||
;; ── SACRED safety bar (shared with es/pt) ──────────────────────────────
|
||||
(negation-faithful yes) ; polarity never dropped/inverted; unplaceable -> FLAG
|
||||
|
||||
;; ── DIALECT overlay (post-realization, one core -> US/UK/AU) ────────────
|
||||
(dialect US) ; default; profile field switches the overlay
|
||||
(dialects (US UK AU))
|
||||
(dialect-canonical US) ; core is authored in US orthography
|
||||
(dialect-overlay "dialect_en.to_dialect") ; orthography + lexis + grammar prefs
|
||||
(dialect-covers (spelling lexis collective-agreement gotten/got)))
|
||||
@@ -1,45 +0,0 @@
|
||||
;;; lang_profile_es.el — Spanish language profile for ELP.
|
||||
;;; Keys the realizer's construction switches. Mirrors lang_profile_en / _pt.
|
||||
|
||||
(lang_profile_es
|
||||
(language "Spanish")
|
||||
(iso639 "es")
|
||||
(family "Romance")
|
||||
|
||||
;; -- core typology flags -------------------------------------------------
|
||||
(pro-drop yes) ; subjects routinely dropped; agreement carries person
|
||||
(obligatory-subject no)
|
||||
(grammatical-gender yes) ; m/f on every noun; article+adjective AGREE
|
||||
(gender-source lexicon); REAL per-noun gender from UniMorph — NOT a heuristic
|
||||
(do-support no)
|
||||
(subject-aux-inversion no) ; questions by intonation/punctuation, not inversion
|
||||
(question-strategy intonation)
|
||||
(article-selection "el/la/los/las un/una/unos/unas")
|
||||
(stressed-a-rule yes) ; fem sg noun in stressed a-/ha- takes el/un (el agua)
|
||||
(adjective-position postnominal) ; default post; a few prenominal + apocope
|
||||
(adjective-agreement "gender+number")
|
||||
(question-punct inverted) ; opening ¿ ¡ required
|
||||
|
||||
;; -- MANDATORY CONTRACTIONS (coordinator quality bar) --------------------
|
||||
(contractions ((de el "del") (a el "al")))
|
||||
(contraction-mandatory yes) ; 'de el'/'a el' MUST surface as del/al
|
||||
|
||||
;; -- verb / aspect system ------------------------------------------------
|
||||
(verb-classes (ar er ir))
|
||||
(tenses (present preterite imperfect future conditional))
|
||||
(moods (ind sbjv imp))
|
||||
(finite-agreement "person+number (6 slots)")
|
||||
(perfect-aux "haber") ; haber + past participle (invariant -o)
|
||||
(progressive-aux "estar") ; estar + gerund
|
||||
(passive-aux "ser") ; ser + participle (agrees) + por-agent
|
||||
(copula-split "ser/estar") ; permanent vs stage-level
|
||||
(future "infinitive + é/ás/á/emos/éis/án")
|
||||
|
||||
;; -- clitics / government ------------------------------------------------
|
||||
(object-clitics yes) ; me te lo la le nos os los las; proclisis/enclisis
|
||||
(clitic-order "se II I III (le+lo -> se lo)")
|
||||
(enclisis "imperative/infinitive/gerund + accent repair (dá+me+lo->dámelo)")
|
||||
(verb-prep-government yes) ; verbs select prep (protestar+contra, escapar+de)
|
||||
|
||||
;; -- SACRED safety bar (shared with en/pt) -------------------------------
|
||||
(negation-faithful yes)) ; polarity never dropped/inverted; unplaceable -> FLAG
|
||||
@@ -1,74 +0,0 @@
|
||||
;;; lang_profile_fr.el — French language profile for ELP.
|
||||
;;; Mirrors lang_profile_it / lang_profile_es; keys the realizer's construction
|
||||
;;; switches. French is a Romance sibling (~54% of the realizer code and the whole
|
||||
;;; clause-engine architecture reused), but carries the family's biggest surface
|
||||
;;; deltas: NOT pro-drop, DISCONTINUOUS negation, and an orthography/phonology
|
||||
;;; mismatch (elision, liaison) that makes exact-match genuinely hard.
|
||||
|
||||
(lang_profile_fr
|
||||
(language "French")
|
||||
(iso639 "fr")
|
||||
(family "Romance")
|
||||
|
||||
;; ── core typology flags ────────────────────────────────────────────────
|
||||
(pro-drop no) ; << French-specific: subject clitic OBLIGATORY
|
||||
(obligatory-subject yes) ; je/tu/il/elle/nous/vous/ils/elles always overt
|
||||
(grammatical-gender yes) ; m/f; full NP agreement (art + adj + participle)
|
||||
(do-support no)
|
||||
(subject-aux-inversion optional) ; est-ce que (default) OR clitic inversion (vas-tu)
|
||||
(article-selection "le/la/l'/les ; un/une/des ; PARTITIVE du/de la/de l'/des")
|
||||
(article-drives-contraction yes) ; à+le=au, de+le=du feed off article choice
|
||||
(adjective-position "postnominal-default + prenominal-BAGS") ; beau/bon/grand/
|
||||
; petit/jeune/vieux/nouveau + ordinals prenominal
|
||||
; (beau->bel, nouveau->nouvel, vieux->vieil / vowel)
|
||||
(question-punct "space-before") ; French typography: ' ?' ' !' (no ¿¡)
|
||||
|
||||
;; ── elision (orthography/phonology mismatch — French-specific) ──────────
|
||||
(elision ((le l') (la l') (je j') (ne n') (de d') (que qu')
|
||||
(me m') (te t') (se s') (ce c'))) ; before vowel / h-muet
|
||||
(elision-h-muet yes) ; l'homme, l'hôpital (h-aspiré exception list kept)
|
||||
(liaison noted-not-modeled) ; phonological, not written in surface
|
||||
|
||||
;; ── MANDATORY prep+article contractions ────────────────────────────────
|
||||
(contractions ((à le au) (à les aux) (de le du) (de les des)))
|
||||
(contraction-mandatory yes) ; *à le -> au obligatory; à la / à l' uncontracted
|
||||
(partitive ((m-sg du) (f-sg "de la") (vowel "de l'") (pl des)))
|
||||
(partitive-under-neg "de") ; << gap in current build: 'ne … pas de pain'
|
||||
|
||||
;; ── clitic system ──────────────────────────────────────────────────────
|
||||
(clitics yes)
|
||||
(clitic-order (me te se nous vous | le la les | lui leur | y | en))
|
||||
(clitic-placement ((finite proclitic) ; je le lui donne
|
||||
(imperative-affirmative enclitic-hyphen) ; donne-le-moi
|
||||
(imperative-negative "ne+proclitic+verb+pas") ; ne le donne pas
|
||||
(infinitive enclitic))) ; PARTIAL: clitic-climbing
|
||||
; onto infinitive under modal
|
||||
(clitic-imperative-shift ((me moi) (te toi))) ; final me/te -> moi/toi (donne-moi)
|
||||
(clitic-particles (y en)) ; locative y, partitive/genitive en
|
||||
|
||||
;; ── verb / aspect system ───────────────────────────────────────────────
|
||||
(finite-agreement "person+number (written; many homophones)")
|
||||
(tenses (présent imparfait passé-simple futur conditionnel
|
||||
subjonctif-présent subjonctif-imparfait impératif))
|
||||
(compound-past "passé-composé = aux(present) + participe passé")
|
||||
(perfect-aux "être/avoir (LEXICAL selection)") ; << French-specific
|
||||
(etre-aux-class "intransitive motion/change (aller venir arriver partir
|
||||
entrer sortir monter descendre naître mourir rester
|
||||
tomber retourner passer devenir revenir rentrer) + ALL
|
||||
pronominal verbs")
|
||||
(participle-agreement ((être subject) ; elle est allée / elles venues
|
||||
(avoir preceding-direct-object))) ; je les ai vus
|
||||
(progressive "être en train de + infinitif") ; no dedicated aux
|
||||
(copula "être (single; no ser/estar, no essere/stare)")
|
||||
(passive-aux "être (+ par-agent)")
|
||||
(future inflectional) ; parlera, sera
|
||||
(comparative "plus/moins ADJ que")
|
||||
(superlative "le/la plus ADJ (de …)") ; PARTIAL word-order in build
|
||||
|
||||
;; ── SACRED safety bar (shared with es/pt/it/en) ────────────────────────
|
||||
(negation-faithful yes) ; polarity never dropped/inverted; unplaceable -> FLAG
|
||||
(negation "DISCONTINUOUS: ne (preverbal) … pas/jamais/rien/personne/
|
||||
plus/guère/que (postverbal)") ; << biggest structural delta
|
||||
(negation-ne-elides yes) ; ne -> n' before vowel (n'ai pas vu)
|
||||
(negation-passe-composé "ne + aux + pas + participe") ; n'ai pas vu
|
||||
(negative-concord partial)) ; personne/rien as arguments post-participle
|
||||
@@ -1,70 +0,0 @@
|
||||
;;; lang_profile_it.el — Italian language profile for ELP.
|
||||
;;; Mirrors lang_profile_es / lang_profile_pt; keys the realizer's construction
|
||||
;;; switches. Italian is a Romance sibling, so ~85% of the flags match ES/PT; the
|
||||
;;; essere/avere auxiliary split and phonological article selection are the deltas.
|
||||
|
||||
(lang_profile_it
|
||||
(language "Italian")
|
||||
(iso639 "it")
|
||||
(family "Romance")
|
||||
|
||||
;; ── core typology flags ────────────────────────────────────────────────
|
||||
(pro-drop yes) ; null subjects default; overt pronoun = emphatic
|
||||
(obligatory-subject no)
|
||||
(grammatical-gender yes) ; m/f; full NP agreement (art + adj + participle)
|
||||
(do-support no)
|
||||
(subject-aux-inversion no) ; yes/no Q = declarative order + '?'; no inversion
|
||||
(article-selection "il/lo/l'/i/gli + la/l'/le ; un/uno/un'/una") ; PHONOLOGICAL:
|
||||
; lo/gli/uno before s+cons, z, gn, ps, pn, x, y, i+V;
|
||||
; l'/un' before a vowel (elision, glued to next word)
|
||||
(article-drives-contraction yes) ; article choice feeds the prep+art contraction
|
||||
(adjective-position "postnominal-default + prenominal-class") ; bello/buono/grande
|
||||
; /nuovo/vecchio/primo... prenominal (with apocope)
|
||||
(question-punct plain) ; ? and ! only (no inverted ¿ ¡)
|
||||
|
||||
;; ── MANDATORY prep+article contractions ────────────────────────────────
|
||||
(contractions ((di il del) (di lo dello) (di la della) (di i dei)
|
||||
(di gli degli) (di le delle) (di l' dell')
|
||||
(a il al) (a lo allo) (a la alla) (a i ai) (a gli agli)
|
||||
(a le alle) (a l' all')
|
||||
(da il dal) (da la dalla) (da gli dagli) (da l' dall')
|
||||
(in il nel) (in la nella) (in gli negli) (in l' nell')
|
||||
(su il sul) (su la sulla) (su gli sugli) (su l' sull')))
|
||||
(contraction-mandatory yes) ; *di il -> del is obligatory, never uncontracted
|
||||
(prep-no-contract (per tra fra)) ; per la strada (NOT *perla)
|
||||
|
||||
;; ── clitic system ──────────────────────────────────────────────────────
|
||||
(clitics yes)
|
||||
(clitic-placement ((finite proclitic) ; lo vedo, non me lo dà
|
||||
(imperative-affirmative enclitic) ; dammelo, guardalo
|
||||
(imperative-negative-tu non+infinitive) ; non parlare / non lo fare
|
||||
(infinitive enclitic) ; vederlo, aiutarmi (drop -e)
|
||||
(gerund enclitic))) ; dandolo
|
||||
(clitic-combination ((mi lo "me lo") (ti lo "te lo") (ci lo "ce lo")
|
||||
(vi lo "ve lo") (si lo "se lo")
|
||||
(gli lo "glielo") (le lo "glielo"))) ; glielo = ONE word
|
||||
(clitic-particles (ci ne)) ; locative ci, partitive ne
|
||||
(raddoppiamento (da fa di va sta)) ; monosyllabic imper double clitic: dammelo
|
||||
|
||||
;; ── verb / aspect system ───────────────────────────────────────────────
|
||||
(finite-agreement "person+number (6-way)")
|
||||
(tenses (presente imperfetto passato-remoto futuro condizionale
|
||||
congiuntivo-presente congiuntivo-imperfetto imperativo))
|
||||
(compound-past "passato-prossimo = aux(present) + participle")
|
||||
(perfect-aux "essere/avere (LEXICAL selection)") ; << Italian-specific
|
||||
(essere-aux-class unaccusative) ; motion/change-of-state/copular/pronominal
|
||||
; (andare venire nascere morire diventare piacere
|
||||
; + ALL reflexives) -> essere
|
||||
(participle-agreement ((essere subject) ; è andata / sono arrivati
|
||||
(avere preceding-acc-clitic))) ; li ho visti
|
||||
(progressive-aux "stare + gerundio") ; sto parlando
|
||||
(copula "essere (default) / stare (state: sto bene)")
|
||||
(passive-aux "essere / venire (+ da-agent)")
|
||||
(future inflectional) ; parlerò, sarà
|
||||
(comparative "più/meno ADJ di")
|
||||
|
||||
;; ── SACRED safety bar (shared with es/pt/en) ───────────────────────────
|
||||
(negation-faithful yes) ; polarity never dropped/inverted; unplaceable -> FLAG
|
||||
(negation "non (preverbal) + concord") ; non...niente/nessuno/mai/più
|
||||
(negative-concord yes) ; preverbal negative word (nessuno/niente) suppresses non
|
||||
(neg-adverb-position between-aux-and-participle)) ; non ho MAI visto
|
||||
@@ -1,30 +0,0 @@
|
||||
;;; lang_profile_la.el — Latin language profile for ELP.
|
||||
;;; Keys the realizer's construction switches. Companion to morphology-la.el.
|
||||
|
||||
(lang_profile_la
|
||||
(language "Latin")
|
||||
(iso639 "la")
|
||||
(family "Italic")
|
||||
|
||||
;; -- core typology flags -------------------------------------------------
|
||||
(pro-drop yes) ; person carried by verb ending; subjects dropped
|
||||
(obligatory-subject no)
|
||||
(grammatical-gender yes) ; m/f/n; adjective AGREES in case+gender+number
|
||||
(gender-source lexicon) ; REAL per-noun gender from UniMorph lat
|
||||
(articles none) ; Latin has no articles
|
||||
(case-system yes) ; NOM GEN DAT ACC ABL VOC (+ rare LOC)
|
||||
(cases (nom gen dat acc abl voc))
|
||||
(word-order "SOV (default; free order, case-marked)")
|
||||
(adjective-position "either (case agreement carries the link)")
|
||||
(adjective-agreement "case+gender+number")
|
||||
|
||||
;; -- verb / aspect system ------------------------------------------------
|
||||
(verb-classes (1 2 3 3io 4)) ; four conjugations + i-stem 3rd
|
||||
(tenses (present imperfect future perfect pluperfect futureperfect))
|
||||
(moods (indicative subjunctive imperative infinitive))
|
||||
(voices (active passive))
|
||||
(finite-agreement "person+number (6 slots)")
|
||||
(citation "principal parts: pres-1sg / pres-inf / perf-participle")
|
||||
|
||||
;; -- SACRED safety bar ---------------------------------------------------
|
||||
(negation-faithful yes)) ; polarity never dropped/inverted
|
||||
@@ -1,40 +0,0 @@
|
||||
;;; lang_profile_pt.el — Portuguese language profile for ELP.
|
||||
;;; Keys the realizer's construction switches. Mirrors lang_profile_es.
|
||||
|
||||
(lang_profile_pt
|
||||
(language "Portuguese")
|
||||
(iso639 "pt")
|
||||
(family "Romance")
|
||||
|
||||
;; -- core typology flags -------------------------------------------------
|
||||
(pro-drop yes) ; subjects routinely dropped; agreement carries person
|
||||
(obligatory-subject no)
|
||||
(grammatical-gender yes) ; m/f on every noun; article+adjective AGREE
|
||||
(gender-source lexicon) ; REAL per-noun gender from UniMorph por / kaikki
|
||||
(do-support no)
|
||||
(subject-aux-inversion no)
|
||||
(question-strategy intonation)
|
||||
(article-selection "o/a/os/as um/uma/uns/umas")
|
||||
(adjective-position postnominal)
|
||||
(adjective-agreement "gender+number")
|
||||
|
||||
;; -- MANDATORY CONTRACTIONS (prep + article) -----------------------------
|
||||
(contractions ((de o "do") (de a "da") (em o "no") (em a "na")
|
||||
(a o "ao") (a a "à") (por o "pelo") (por a "pela")))
|
||||
(contraction-mandatory yes)
|
||||
|
||||
;; -- verb / aspect system ------------------------------------------------
|
||||
(verb-classes (ar er ir))
|
||||
(tenses (present preterite imperfect future conditional))
|
||||
(moods (ind sbjv imp))
|
||||
(finite-agreement "person+number (6 slots)")
|
||||
(perfect-aux "ter") ; ter + past participle
|
||||
(copula-split "ser/estar")
|
||||
(personal-infinitive yes) ; distinctive PT inflected infinitive
|
||||
|
||||
;; -- clitics / government ------------------------------------------------
|
||||
(object-clitics yes) ; mesoclisis/enclisis/proclisis by context
|
||||
(verb-prep-government yes)
|
||||
|
||||
;; -- SACRED safety bar ---------------------------------------------------
|
||||
(negation-faithful yes))
|
||||
@@ -1,71 +0,0 @@
|
||||
;;; lang_profile_ro.el — Romanian language profile for ELP.
|
||||
;;; Romanian is the BIG typological delta of the Romance family. The verb/clause
|
||||
;;; engine and the SACRED negation contract mirror the ES/PT/IT core, but the
|
||||
;;; NOMINAL system is genuinely new: a SUFFIXED definite article, preserved CASE,
|
||||
;;; a NEUTER gender, and a VOCATIVE. Those flags mark where the shared engine was
|
||||
;;; extended rather than reused.
|
||||
|
||||
(lang_profile_ro
|
||||
(language "Romanian")
|
||||
(iso639 "ro")
|
||||
(family "Romance (Eastern / Balkan)")
|
||||
|
||||
;; ── core typology flags ────────────────────────────────────────────────
|
||||
(pro-drop yes) ; null subjects default; overt pronoun = emphatic
|
||||
(obligatory-subject no)
|
||||
(grammatical-gender yes) ; m / f / NEUTER (n)
|
||||
(neuter-gender yes) ; << ROMANIAN-SPECIFIC: masc-agreeing SG, fem-agreeing PL
|
||||
; (un tren nou / două trenuri noi)
|
||||
(do-support no)
|
||||
(subject-aux-inversion no) ; yes/no Q = declarative order + '?'
|
||||
(question-punct plain) ; ? and ! only
|
||||
|
||||
;; ── SUFFIXED DEFINITE ARTICLE (the headline engine extension) ───────────
|
||||
(definite-article suffixed) ; << UNIQUE IN ROMANCE: enclitic on the noun
|
||||
(definite-forms ((m/n sg "-ul / -le / -l : om->omul, câine->câinele, codru->codrul")
|
||||
(f sg "-a / -ea / -ua : casă->casa, carte->cartea, stea->steaua")
|
||||
(m pl "-i : oameni->oamenii")
|
||||
(f/n pl "-le : case->casele, trenuri->trenurile")))
|
||||
(article-host ((no-prenom-adj noun) ; omul bun
|
||||
(prenom-adj adjective))) ; bunul om (adj carries the article)
|
||||
(indefinite-article ((m/n "un") (f "o") (pl "niște") (gen/dat-pl "unor")))
|
||||
|
||||
;; ── CASE (preserved; NOM/ACC vs GEN/DAT) ────────────────────────────────
|
||||
(case (nom/acc gen/dat vocative)) ; << ROMANIAN-SPECIFIC
|
||||
(case-syncretism "nom=acc ; gen=dat")
|
||||
(genitive-marking "gen/dat definite: -lui (m/n), -ei/-i (f), -lor (pl)")
|
||||
(genitival-article ((m sg "al") (f sg "a") (m pl "ai") (f/n pl "ale"))) ; o carte a lui
|
||||
(possession "definite-head + gen/dat possessor: casa băiatului")
|
||||
(vocative ((m sg "-ule/-e : omule, băiete") (f sg "-o : Mario, fato")
|
||||
(pl "-lor")))
|
||||
|
||||
;; ── verb / aspect system ────────────────────────────────────────────────
|
||||
(finite-agreement "person+number (6-way)")
|
||||
(tenses (prezent imperfect perfect-simplu conjunctiv-prezent
|
||||
imperativ (periphrastic: perfect-compus viitor conditional)))
|
||||
(compound-past "perfectul compus = a-avea-clitic + INVARIABLE participle")
|
||||
(perfect-aux "a avea (am/ai/a/am/ați/au) — ONE auxiliary for ALL verbs")
|
||||
(perfect-aux-split no) ; << SIMPLER than Italian: no essere/avere selection
|
||||
(participle-agreement none) ; invariable in the perfect compus (agrees only as
|
||||
; an adjective / in the passive)
|
||||
(future "voi/vei/va/vom/veți/vor + infinitive (viitor literar)")
|
||||
(conditional "aș/ai/ar/am/ați/ar + infinitive")
|
||||
(subjunctive "conjunctiv: particle 'să' + subjunctive present")
|
||||
(modal-complement "modal + să + subjunctive (vreau să merg, poți să ajuți)")
|
||||
(copula "a fi")
|
||||
(passive "a fi + participle (participle AGREES like an adjective)")
|
||||
(comparative "mai / mai puțin ADJ decât")
|
||||
|
||||
;; ── clitic system (partial — see honest gaps) ───────────────────────────
|
||||
(clitics yes)
|
||||
(clitic-set ((acc mă te îl o ne vă îi le) (dat îmi îți îi ne vă le)
|
||||
(refl mă te se ne vă se)))
|
||||
(clitic-placement ((finite proclitic) ; îmi place, o văd
|
||||
(perfect-compus elision) ; << m-am, l-am, i-am (PARTIAL)
|
||||
(imperative-affirmative enclitic))) ; dă-mi (PARTIAL)
|
||||
|
||||
;; ── SACRED safety bar (shared with es/pt/it/en) ─────────────────────────
|
||||
(negation-faithful yes) ; polarity never dropped/inverted; unplaceable -> FLAG
|
||||
(negation "nu (single preverbal marker) + concord")
|
||||
(negative-concord yes) ; nu … nimic / nimeni / niciodată / niciun
|
||||
(negative-imperative "nu + INFINITIVE : nu pleca! (KNOWN GAP: uses imperative stem)"))
|
||||
@@ -250,7 +250,6 @@ fn en_irregular_verb(base: String) -> [String] {
|
||||
if str_eq(base, "cut") { let r: [String] = ["cut", "cuts", "cut", "cut", "cutting"]; return r }
|
||||
if str_eq(base, "set") { let r: [String] = ["set", "sets", "set", "set", "setting"]; return r }
|
||||
if str_eq(base, "hit") { let r: [String] = ["hit", "hits", "hit", "hit", "hitting"]; return r }
|
||||
if str_eq(base, "fight") { let r: [String] = ["fight", "fights","fought", "fought", "fighting"]; return r }
|
||||
return empty
|
||||
}
|
||||
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
// multilingual.el - the language layer for the native-el interlocutor.
|
||||
//
|
||||
// Deterministic, NO generative model (ports multilingual.py):
|
||||
// 1. ml_detect(text) -> ISO code (en/es/pt/it) via stopword + diacritic score
|
||||
// 2. ml_tr(key, lang) -> localized fixed phrase (SACRED per-language yes/no/decline)
|
||||
// 3. ml_term(w, lang) -> PT/ES content term -> EN engram equivalent
|
||||
// 4. ml_translate_pred(lemma, lang) -> EN predicate lemma -> target infinitive
|
||||
//
|
||||
// The Python detector count-weights stopwords and diacritics; here diacritics are
|
||||
// scored by PRESENCE (str_contains) rather than codepoint counting, to stay clear
|
||||
// of UTF-8 index hazards in the runtime. Faithful enough to classify typical
|
||||
// queries; documented simplification. Depends on: comprehend (cp_tokenize).
|
||||
|
||||
// ── 1. language detection ─────────────────────────────────────────────────────
|
||||
|
||||
fn ml_stop_en(w: String) -> Bool {
|
||||
if str_eq(w, "the") { return true }
|
||||
if str_eq(w, "does") { return true }
|
||||
if str_eq(w, "do") { return true }
|
||||
if str_eq(w, "did") { return true }
|
||||
if str_eq(w, "what") { return true }
|
||||
if str_eq(w, "who") { return true }
|
||||
if str_eq(w, "is") { return true }
|
||||
if str_eq(w, "are") { return true }
|
||||
if str_eq(w, "how") { return true }
|
||||
if str_eq(w, "you") { return true }
|
||||
if str_eq(w, "your") { return true }
|
||||
if str_eq(w, "of") { return true }
|
||||
if str_eq(w, "to") { return true }
|
||||
if str_eq(w, "and") { return true }
|
||||
if str_eq(w, "for") { return true }
|
||||
if str_eq(w, "explain") { return true }
|
||||
if str_eq(w, "answer") { return true }
|
||||
if str_eq(w, "memory") { return true }
|
||||
if str_eq(w, "with") { return true }
|
||||
if str_eq(w, "not") { return true }
|
||||
if str_eq(w, "store") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
fn ml_stop_es(w: String) -> Bool {
|
||||
if str_eq(w, "que") { return true }
|
||||
if str_eq(w, "qué") { return true }
|
||||
if str_eq(w, "una") { return true }
|
||||
if str_eq(w, "usted") { return true }
|
||||
if str_eq(w, "su") { return true }
|
||||
if str_eq(w, "cómo") { return true }
|
||||
if str_eq(w, "como") { return true }
|
||||
if str_eq(w, "cuál") { return true }
|
||||
if str_eq(w, "quién") { return true }
|
||||
if str_eq(w, "está") { return true }
|
||||
if str_eq(w, "es") { return true }
|
||||
if str_eq(w, "los") { return true }
|
||||
if str_eq(w, "las") { return true }
|
||||
if str_eq(w, "del") { return true }
|
||||
if str_eq(w, "al") { return true }
|
||||
if str_eq(w, "explica") { return true }
|
||||
if str_eq(w, "explique") { return true }
|
||||
if str_eq(w, "forma") { return true }
|
||||
if str_eq(w, "con") { return true }
|
||||
if str_eq(w, "memoria") { return true }
|
||||
if str_eq(w, "responde") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
fn ml_stop_pt(w: String) -> Bool {
|
||||
if str_eq(w, "que") { return true }
|
||||
if str_eq(w, "uma") { return true }
|
||||
if str_eq(w, "você") { return true }
|
||||
if str_eq(w, "sua") { return true }
|
||||
if str_eq(w, "seu") { return true }
|
||||
if str_eq(w, "como") { return true }
|
||||
if str_eq(w, "memória") { return true }
|
||||
if str_eq(w, "isso") { return true }
|
||||
if str_eq(w, "os") { return true }
|
||||
if str_eq(w, "as") { return true }
|
||||
if str_eq(w, "da") { return true }
|
||||
if str_eq(w, "do") { return true }
|
||||
if str_eq(w, "na") { return true }
|
||||
if str_eq(w, "no") { return true }
|
||||
if str_eq(w, "explica") { return true }
|
||||
if str_eq(w, "forma") { return true }
|
||||
if str_eq(w, "é") { return true }
|
||||
if str_eq(w, "está") { return true }
|
||||
if str_eq(w, "com") { return true }
|
||||
if str_eq(w, "responda") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
fn ml_stop_it(w: String) -> Bool {
|
||||
if str_eq(w, "che") { return true }
|
||||
if str_eq(w, "una") { return true }
|
||||
if str_eq(w, "come") { return true }
|
||||
if str_eq(w, "della") { return true }
|
||||
if str_eq(w, "gli") { return true }
|
||||
if str_eq(w, "è") { return true }
|
||||
if str_eq(w, "sono") { return true }
|
||||
if str_eq(w, "questo") { return true }
|
||||
if str_eq(w, "nel") { return true }
|
||||
if str_eq(w, "di") { return true }
|
||||
if str_eq(w, "il") { return true }
|
||||
if str_eq(w, "cosa") { return true }
|
||||
if str_eq(w, "per") { return true }
|
||||
if str_eq(w, "memoria") { return true }
|
||||
if str_eq(w, "spiega") { return true }
|
||||
if str_eq(w, "rispondi") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// diacritic PRESENCE score (weight 3 each; hard overrides weight 8).
|
||||
fn ml_dia_score(low: String, lang: String) -> Int {
|
||||
let s: Int = 0
|
||||
if str_eq(lang, "pt") {
|
||||
if str_contains(low, "ã") { let s = s + 3 }
|
||||
if str_contains(low, "õ") { let s = s + 3 }
|
||||
if str_contains(low, "ç") { let s = s + 3 }
|
||||
if str_contains(low, "ê") { let s = s + 3 }
|
||||
if str_contains(low, "á") { let s = s + 3 }
|
||||
// hard PT markers (ã/õ almost never appear outside PT)
|
||||
if str_contains(low, "ã") { let s = s + 8 }
|
||||
if str_contains(low, "õ") { let s = s + 8 }
|
||||
}
|
||||
if str_eq(lang, "es") {
|
||||
if str_contains(low, "ñ") { let s = s + 3 }
|
||||
if str_contains(low, "¿") { let s = s + 3 }
|
||||
if str_contains(low, "¡") { let s = s + 3 }
|
||||
if str_contains(low, "á") { let s = s + 3 }
|
||||
if str_contains(low, "é") { let s = s + 3 }
|
||||
// hard ES markers
|
||||
if str_contains(low, "ñ") { let s = s + 8 }
|
||||
if str_contains(low, "¿") { let s = s + 8 }
|
||||
if str_contains(low, "¡") { let s = s + 8 }
|
||||
}
|
||||
if str_eq(lang, "it") {
|
||||
if str_contains(low, "è") { let s = s + 3 }
|
||||
if str_contains(low, "ì") { let s = s + 3 }
|
||||
if str_contains(low, "ò") { let s = s + 3 }
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
fn ml_stop_score(toks: [String], lang: String) -> Int {
|
||||
let n: Int = native_list_len(toks)
|
||||
let s: Int = 0
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let w: String = native_list_get(toks, i)
|
||||
if str_eq(lang, "en") { if ml_stop_en(w) { let s = s + 2 } }
|
||||
if str_eq(lang, "es") { if ml_stop_es(w) { let s = s + 2 } }
|
||||
if str_eq(lang, "pt") { if ml_stop_pt(w) { let s = s + 2 } }
|
||||
if str_eq(lang, "it") { if ml_stop_it(w) { let s = s + 2 } }
|
||||
let i = i + 1
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
fn ml_detect(text: String) -> String {
|
||||
if str_eq(text, "") { return "en" }
|
||||
let low: String = str_to_lower(text)
|
||||
let toks: [String] = cp_tokenize(text)
|
||||
// NOTE: el's overloaded `+` mis-compiles two chained function-call Int operands
|
||||
// as string concat (documented in comprehend_gate.el). Bind each call to an Int
|
||||
// var and add vars one at a time so the addition stays integer.
|
||||
let en: Int = ml_stop_score(toks, "en")
|
||||
let es_s: Int = ml_stop_score(toks, "es")
|
||||
let es_d: Int = ml_dia_score(low, "es")
|
||||
let es: Int = es_s + es_d
|
||||
let pt_s: Int = ml_stop_score(toks, "pt")
|
||||
let pt_d: Int = ml_dia_score(low, "pt")
|
||||
let pt: Int = pt_s + pt_d
|
||||
let it_s: Int = ml_stop_score(toks, "it")
|
||||
let it_d: Int = ml_dia_score(low, "it")
|
||||
let it: Int = it_s + it_d
|
||||
|
||||
let best: String = "en"
|
||||
let bs: Int = en
|
||||
if es > bs { let best = "es"; let bs = es }
|
||||
if pt > bs { let best = "pt"; let bs = pt }
|
||||
if it > bs { let best = "it"; let bs = it }
|
||||
// weak signal -> honest fallback to English
|
||||
if bs < 3 { return "en" }
|
||||
return best
|
||||
}
|
||||
|
||||
// ── 2. localized fixed phrases (SACRED per-language decline/yes/no) ────────────
|
||||
|
||||
fn ml_tr(key: String, lang: String) -> String {
|
||||
if str_eq(key, "no_memory") {
|
||||
if str_eq(lang, "pt") { return "Não tenho isso na minha memória." }
|
||||
if str_eq(lang, "es") { return "No tengo eso en mi memoria." }
|
||||
if str_eq(lang, "it") { return "Non ho quello nella mia memoria." }
|
||||
return "I don't have that in my memory."
|
||||
}
|
||||
if str_eq(key, "parse_fail") {
|
||||
if str_eq(lang, "pt") { return "Não consegui interpretar isso." }
|
||||
if str_eq(lang, "es") { return "No pude interpretar eso." }
|
||||
if str_eq(lang, "it") { return "Non sono riuscito a interpretarlo." }
|
||||
return "I didn't parse that."
|
||||
}
|
||||
if str_eq(key, "yes") {
|
||||
if str_eq(lang, "pt") { return "Sim" }
|
||||
if str_eq(lang, "es") { return "Sí" }
|
||||
if str_eq(lang, "it") { return "Sì" }
|
||||
return "Yes"
|
||||
}
|
||||
if str_eq(key, "no") {
|
||||
if str_eq(lang, "pt") { return "Não" }
|
||||
if str_eq(lang, "es") { return "No" }
|
||||
if str_eq(lang, "it") { return "No" }
|
||||
return "No"
|
||||
}
|
||||
if str_eq(key, "identity") {
|
||||
if str_eq(lang, "pt") { return "Sou o Neuron, o engrama com quem você está falando." }
|
||||
if str_eq(lang, "es") { return "Soy Neuron, el engrama con el que estás hablando." }
|
||||
if str_eq(lang, "it") { return "Sono Neuron, l'engramma con cui stai parlando." }
|
||||
return "I'm Neuron, the engram you're speaking with."
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── 3. retrieval term lexicon (PT/ES content term -> EN engram equivalent) ─────
|
||||
|
||||
fn ml_term(w: String, lang: String) -> String {
|
||||
if str_eq(lang, "en") { return w }
|
||||
if str_eq(w, "saliência") { return "salience" }
|
||||
if str_eq(w, "saliencia") { return "salience" }
|
||||
if str_eq(w, "memória") { return "memory" }
|
||||
if str_eq(w, "memoria") { return "memory" }
|
||||
if str_eq(w, "geometria") { return "geometry" }
|
||||
if str_eq(w, "geometrias") { return "geometry" }
|
||||
if str_eq(w, "geometrías") { return "geometry" }
|
||||
if str_eq(w, "forma") { return "form" }
|
||||
if str_eq(w, "consolidação") { return "consolidation" }
|
||||
if str_eq(w, "consolidación") { return "consolidation" }
|
||||
if str_eq(w, "aprendizagem") { return "learning" }
|
||||
if str_eq(w, "aprendizaje") { return "learning" }
|
||||
if str_eq(w, "nó") { return "node" }
|
||||
if str_eq(w, "nodo") { return "node" }
|
||||
if str_eq(w, "armazenamento") { return "storage" }
|
||||
if str_eq(w, "almacenamiento") { return "storage" }
|
||||
if str_eq(w, "estrutura") { return "structure" }
|
||||
if str_eq(w, "estructura") { return "structure" }
|
||||
return w
|
||||
}
|
||||
|
||||
// ── 4. predicate translation (EN lemma -> target infinitive; pass-through) ─────
|
||||
|
||||
fn ml_translate_pred(lemma: String, lang: String) -> String {
|
||||
if str_eq(lang, "en") { return lemma }
|
||||
if str_eq(lang, "es") {
|
||||
if str_eq(lemma, "store") { return "almacenar" }
|
||||
if str_eq(lemma, "use") { return "usar" }
|
||||
if str_eq(lemma, "have") { return "tener" }
|
||||
if str_eq(lemma, "be") { return "ser" }
|
||||
if str_eq(lemma, "give") { return "dar" }
|
||||
if str_eq(lemma, "make") { return "hacer" }
|
||||
if str_eq(lemma, "learn") { return "aprender" }
|
||||
if str_eq(lemma, "form") { return "formar" }
|
||||
return lemma
|
||||
}
|
||||
if str_eq(lang, "pt") {
|
||||
if str_eq(lemma, "store") { return "armazenar" }
|
||||
if str_eq(lemma, "use") { return "usar" }
|
||||
if str_eq(lemma, "have") { return "ter" }
|
||||
if str_eq(lemma, "be") { return "ser" }
|
||||
if str_eq(lemma, "give") { return "dar" }
|
||||
if str_eq(lemma, "make") { return "fazer" }
|
||||
if str_eq(lemma, "learn") { return "aprender" }
|
||||
if str_eq(lemma, "form") { return "formar" }
|
||||
return lemma
|
||||
}
|
||||
if str_eq(lang, "it") {
|
||||
if str_eq(lemma, "store") { return "memorizzare" }
|
||||
if str_eq(lemma, "use") { return "usare" }
|
||||
if str_eq(lemma, "have") { return "avere" }
|
||||
if str_eq(lemma, "be") { return "essere" }
|
||||
return lemma
|
||||
}
|
||||
return lemma
|
||||
}
|
||||
@@ -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,140 +0,0 @@
|
||||
// propositions.el - the READ primitive over the engram's OWN memories, native el.
|
||||
//
|
||||
// Free memory text -> structured PROPOSITIONS (triples):
|
||||
// (subject, predicate, object, modifiers, polarity, tense, source, confidence)
|
||||
//
|
||||
// This is comprehension turned inward: the Python reference (propositions.py) ran
|
||||
// spaCy's dependency parser over each memory sentence and walked the arcs. Here
|
||||
// the spaCy role is filled by the el-native parser (comprehend.el / parse_spec):
|
||||
// each sentence is parsed to a meaning-spec, and the spec's roles ARE the triple.
|
||||
// Nothing generates text. NEGATION IS SACRED: polarity flows straight from the
|
||||
// spec's polarity field and is never dropped or inverted.
|
||||
//
|
||||
// Depends on: comprehend (parse_spec / parse_spec_lang), grammar (slots_get).
|
||||
|
||||
// ── sentence segmentation ─────────────────────────────────────────────────────
|
||||
// Split on sentence-final punctuation (. ! ?) and hard newlines. Markdown/long
|
||||
// memories are handled shallowly (the reference caps + ranks by query overlap;
|
||||
// that ranking belongs to the dialogue layer, not here).
|
||||
|
||||
fn prop_is_boundary(c: String) -> Bool {
|
||||
if str_eq(c, ".") { return true }
|
||||
if str_eq(c, "!") { return true }
|
||||
if str_eq(c, "?") { return true }
|
||||
if str_eq(c, "\n") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
fn prop_split_sentences(text: String) -> [String] {
|
||||
let out: [String] = native_list_empty()
|
||||
let n: Int = str_len(text)
|
||||
let start: Int = 0
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let c: String = str_slice(text, i, i + 1)
|
||||
if prop_is_boundary(c) {
|
||||
let seg: String = str_slice(text, start, i + 1)
|
||||
let trimmed: String = cp_trim_punct(seg)
|
||||
if !str_eq(trimmed, "") {
|
||||
let out = native_list_append(out, seg)
|
||||
}
|
||||
let start = i + 1
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
if start < n {
|
||||
let seg: String = str_slice(text, start, n)
|
||||
let trimmed: String = cp_trim_punct(seg)
|
||||
if !str_eq(trimmed, "") {
|
||||
let out = native_list_append(out, seg)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ── spec -> proposition record ────────────────────────────────────────────────
|
||||
// A proposition is a slot map (same [String] shape as the spec) with the READ
|
||||
// contract keys. Modifiers fold the spec's location + iobj adjuncts.
|
||||
|
||||
fn prop_confidence(subject: String, predicate: String, object: String) -> String {
|
||||
if str_eq(predicate, "") { return "0.0" }
|
||||
if str_eq(subject, "") { return "0.4" }
|
||||
if str_eq(object, "") { return "0.7" }
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
fn prop_modifiers(spec: [String]) -> String {
|
||||
let loc: String = slots_get(spec, "location")
|
||||
let iobj: String = slots_get(spec, "iobj")
|
||||
let parts: [String] = native_list_empty()
|
||||
if !str_eq(loc, "") { let parts = native_list_append(parts, loc) }
|
||||
if !str_eq(iobj, "") { let parts = native_list_append(parts, "to " + iobj) }
|
||||
return str_join(parts, "; ")
|
||||
}
|
||||
|
||||
fn prop_from_spec(spec: [String], source_id: String) -> [String] {
|
||||
let subject: String = slots_get(spec, "agent")
|
||||
let predicate: String = slots_get(spec, "predicate")
|
||||
let object: String = slots_get(spec, "patient")
|
||||
let polarity: String = slots_get(spec, "polarity")
|
||||
let tense: String = slots_get(spec, "tense")
|
||||
let mods: String = prop_modifiers(spec)
|
||||
let conf: String = prop_confidence(subject, predicate, object)
|
||||
|
||||
let p: [String] = native_list_empty()
|
||||
let p = native_list_append(p, "subject"); let p = native_list_append(p, subject)
|
||||
let p = native_list_append(p, "predicate"); let p = native_list_append(p, predicate)
|
||||
let p = native_list_append(p, "object"); let p = native_list_append(p, object)
|
||||
let p = native_list_append(p, "modifiers"); let p = native_list_append(p, mods)
|
||||
let p = native_list_append(p, "polarity"); let p = native_list_append(p, polarity)
|
||||
let p = native_list_append(p, "tense"); let p = native_list_append(p, tense)
|
||||
let p = native_list_append(p, "source"); let p = native_list_append(p, source_id)
|
||||
let p = native_list_append(p, "confidence"); let p = native_list_append(p, conf)
|
||||
return p
|
||||
}
|
||||
|
||||
// Extract one proposition from a single sentence (given language).
|
||||
fn prop_extract_one_lang(sentence: String, lang: String, source_id: String) -> [String] {
|
||||
let spec: [String] = parse_spec_lang(sentence, lang)
|
||||
return prop_from_spec(spec, source_id)
|
||||
}
|
||||
|
||||
fn prop_extract_one(sentence: String, source_id: String) -> [String] {
|
||||
return prop_extract_one_lang(sentence, "en", source_id)
|
||||
}
|
||||
|
||||
// Render a proposition as a compact trace line (repr parity with propositions.py).
|
||||
fn prop_repr(p: [String]) -> String {
|
||||
let neg: String = ""
|
||||
if str_eq(slots_get(p, "polarity"), "neg") { let neg = "NOT " }
|
||||
let mods: String = slots_get(p, "modifiers")
|
||||
let modstr: String = ""
|
||||
if !str_eq(mods, "") { let modstr = " [" + mods + "]" }
|
||||
let s: String = "(" + slots_get(p, "subject") + " -" + neg + slots_get(p, "predicate")
|
||||
let s = s + "-> " + slots_get(p, "object") + modstr
|
||||
let s = s + " conf=" + slots_get(p, "confidence") + ")"
|
||||
return s
|
||||
}
|
||||
|
||||
// Extract all propositions from a memory's text (one per sentence). Returns a
|
||||
// flat [String] whose entries are the prop_repr trace lines, in reading order.
|
||||
fn prop_extract_lang(text: String, lang: String, source_id: String) -> [String] {
|
||||
let sents: [String] = prop_split_sentences(text)
|
||||
let m: Int = native_list_len(sents)
|
||||
let out: [String] = native_list_empty()
|
||||
let i: Int = 0
|
||||
while i < m {
|
||||
let sent: String = native_list_get(sents, i)
|
||||
let p: [String] = prop_extract_one_lang(sent, lang, source_id)
|
||||
// drop empty parses (no predicate recovered): honest partial, not noise.
|
||||
if !str_eq(slots_get(p, "predicate"), "") {
|
||||
let out = native_list_append(out, prop_repr(p))
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fn prop_extract(text: String, source_id: String) -> [String] {
|
||||
return prop_extract_lang(text, "en", source_id)
|
||||
}
|
||||
@@ -34,13 +34,6 @@ fn agent_person(agent: String) -> String {
|
||||
if str_eq(agent, "we") { return "first" }
|
||||
if str_eq(agent, "us") { return "first" }
|
||||
if str_eq(agent, "you") { return "second" }
|
||||
// Romance target-language subject pronouns (translate.el sets these).
|
||||
if str_eq(agent, "yo") { return "first" }
|
||||
if str_eq(agent, "eu") { return "first" }
|
||||
if str_eq(agent, "nosotros") { return "first" }
|
||||
if str_eq(agent, "nós") { return "first" }
|
||||
if str_eq(agent, "tú") { return "second" }
|
||||
if str_eq(agent, "tu") { return "second" }
|
||||
return "third"
|
||||
}
|
||||
|
||||
@@ -57,19 +50,6 @@ fn agent_number(agent: String) -> String {
|
||||
if str_eq(agent, "us") { return "plural" }
|
||||
if str_eq(agent, "they") { return "plural" }
|
||||
if str_eq(agent, "them") { return "plural" }
|
||||
// Romance target-language subject pronouns.
|
||||
if str_eq(agent, "yo") { return "singular" }
|
||||
if str_eq(agent, "eu") { return "singular" }
|
||||
if str_eq(agent, "tú") { return "singular" }
|
||||
if str_eq(agent, "tu") { return "singular" }
|
||||
if str_eq(agent, "él") { return "singular" }
|
||||
if str_eq(agent, "ella") { return "singular" }
|
||||
if str_eq(agent, "ele") { return "singular" }
|
||||
if str_eq(agent, "ela") { return "singular" }
|
||||
if str_eq(agent, "nosotros") { return "plural" }
|
||||
if str_eq(agent, "nós") { return "plural" }
|
||||
if str_eq(agent, "ellos") { return "plural" }
|
||||
if str_eq(agent, "eles") { return "plural" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
@@ -268,56 +248,6 @@ fn add_punct(s: String, intent: String) -> String {
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Polarity-aware negation (SACRED field honored on the generation side) ─────
|
||||
//
|
||||
// Negation must never be dropped between comprehension and realization. The
|
||||
// meaning-spec carries an explicit "polarity" field ("aff"|"neg") and optional
|
||||
// "neg_word" (standalone negative adverb, e.g. "never"). English uses
|
||||
// do-support ("did not see") or preverbal adverb ("never fought"); copular "be"
|
||||
// takes post-verbal "not"; other languages get a preverbal negator particle.
|
||||
|
||||
fn realize_negator(code: String) -> String {
|
||||
if str_eq(code, "es") { return "no" }
|
||||
if str_eq(code, "pt") { return "não" }
|
||||
if str_eq(code, "ca") { return "no" }
|
||||
if str_eq(code, "it") { return "non" }
|
||||
if str_eq(code, "fr") { return "ne" }
|
||||
if str_eq(code, "de") { return "nicht" }
|
||||
if str_eq(code, "ro") { return "nu" }
|
||||
return "not"
|
||||
}
|
||||
|
||||
fn realize_assert_neg_en(predicate: String, tense: String, person: String, number: String, agent: String, patient: String, iobj: String, location: String, neg_word: String, profile: [String]) -> String {
|
||||
let parts: [String] = native_list_empty()
|
||||
let parts = native_list_append(parts, agent)
|
||||
if !str_eq(neg_word, "") {
|
||||
// adverbial negation: "I never fought the ocean."
|
||||
let verb_surf: String = morph_conjugate(predicate, tense, person, number, profile)
|
||||
let parts = native_list_append(parts, neg_word)
|
||||
let parts = native_list_append(parts, verb_surf)
|
||||
} else {
|
||||
if str_eq(predicate, "be") {
|
||||
// copular: "she was not a monster"
|
||||
let be_form: String = morph_conjugate("be", tense, person, number, profile)
|
||||
let parts = native_list_append(parts, be_form)
|
||||
let parts = native_list_append(parts, "not")
|
||||
} else {
|
||||
// do-support: "she did not see the man"
|
||||
let do_form: String = morph_conjugate("do", tense, person, number, profile)
|
||||
let parts = native_list_append(parts, do_form)
|
||||
let parts = native_list_append(parts, "not")
|
||||
let parts = native_list_append(parts, predicate)
|
||||
}
|
||||
}
|
||||
if !str_eq(patient, "") { let parts = native_list_append(parts, patient) }
|
||||
if !str_eq(iobj, "") {
|
||||
let parts = native_list_append(parts, "to")
|
||||
let parts = native_list_append(parts, iobj)
|
||||
}
|
||||
if !str_eq(location, "") { let parts = native_list_append(parts, location) }
|
||||
return str_join(parts, " ")
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
fn realize_lang(form: [String], profile: [String]) -> String {
|
||||
@@ -354,54 +284,6 @@ fn realize_lang(form: [String], profile: [String]) -> String {
|
||||
}
|
||||
|
||||
// ── Assertion (declarative) ───────────────────────────────────────────────
|
||||
let polarity: String = slots_get(form, "polarity")
|
||||
let neg_word: String = slots_get(form, "neg_word")
|
||||
let iobj: String = slots_get(form, "iobj")
|
||||
let code: String = lang_get(profile, "code")
|
||||
|
||||
// Subordinate clause tail (SACRED completeness — the clause is carried, never
|
||||
// dropped): "<conj> <subordinate surface>", e.g. "because he was a monster".
|
||||
let subord_conj: String = slots_get(form, "subord_conj")
|
||||
let subord_text: String = slots_get(form, "subord_text")
|
||||
let subord_tail: String = ""
|
||||
if !str_eq(subord_conj, "") {
|
||||
if !str_eq(subord_text, "") {
|
||||
let subord_tail = subord_conj + " " + subord_text
|
||||
} else {
|
||||
let subord_tail = subord_conj
|
||||
}
|
||||
}
|
||||
|
||||
// Negative polarity: SACRED — never dropped.
|
||||
if str_eq(polarity, "neg") {
|
||||
if str_eq(code, "en") {
|
||||
let sentence: String = realize_assert_neg_en(predicate, tense, person, number, agent, patient, iobj, location, neg_word, profile)
|
||||
return add_punct(capitalize_first(sentence), "assert")
|
||||
}
|
||||
// Generic non-English: affirmative core with a preverbal negator particle.
|
||||
// SACRED: when a standalone negative adverb was carried (e.g. "nunca",
|
||||
// localized upstream from "never"), surface it rather than the generic
|
||||
// negator — the specific negation must never be flattened away.
|
||||
let neg_particle: String = realize_negator(code)
|
||||
if !str_eq(neg_word, "") { let neg_particle = neg_word }
|
||||
let vp_pair: [String] = realize_vp_lang(predicate, tense, aspect, person, number, profile)
|
||||
let verb_surf: String = native_list_get(vp_pair, 0)
|
||||
let aux_surf: String = native_list_get(vp_pair, 1)
|
||||
let vp_str: String = neg_particle + " " + gram_build_vp(verb_surf, aux_surf, profile)
|
||||
let core: String = gram_order_constituents(agent, vp_str, patient, profile)
|
||||
let parts: [String] = native_list_empty()
|
||||
let parts = native_list_append(parts, core)
|
||||
if !str_eq(iobj, "") {
|
||||
let parts = native_list_append(parts, "to")
|
||||
let parts = native_list_append(parts, iobj)
|
||||
}
|
||||
if !str_eq(location, "") { let parts = native_list_append(parts, location) }
|
||||
if !str_eq(subord_tail, "") { let parts = native_list_append(parts, subord_tail) }
|
||||
let sentence: String = str_join(parts, " ")
|
||||
return add_punct(capitalize_first(sentence), "assert")
|
||||
}
|
||||
|
||||
// Affirmative.
|
||||
let vp_pair: [String] = realize_vp_lang(predicate, tense, aspect, person, number, profile)
|
||||
let verb_surf: String = native_list_get(vp_pair, 0)
|
||||
let aux_surf: String = native_list_get(vp_pair, 1)
|
||||
@@ -411,16 +293,9 @@ fn realize_lang(form: [String], profile: [String]) -> String {
|
||||
|
||||
let parts: [String] = native_list_empty()
|
||||
let parts = native_list_append(parts, core)
|
||||
if !str_eq(iobj, "") {
|
||||
let parts = native_list_append(parts, "to")
|
||||
let parts = native_list_append(parts, iobj)
|
||||
}
|
||||
if !str_eq(location, "") {
|
||||
let parts = native_list_append(parts, location)
|
||||
}
|
||||
if !str_eq(subord_tail, "") {
|
||||
let parts = native_list_append(parts, subord_tail)
|
||||
}
|
||||
let sentence: String = str_join(parts, " ")
|
||||
return add_punct(capitalize_first(sentence), "assert")
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
extern fn agent_person(agent: String) -> String
|
||||
extern fn agent_number(agent: String) -> String
|
||||
extern fn realize_np(referent: String, number: String) -> String
|
||||
extern fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String]
|
||||
extern fn realize_question_lang(predicate: String, tense: String, aspect: String, person: String, number: String, agent: String, patient: String, location: String, profile: [String]) -> String
|
||||
extern fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: Any) -> Any
|
||||
extern fn realize_question_lang(predicate: String, tense: String, aspect: String, person: String, number: String, agent: String, patient: String, location: String, profile: Any) -> String
|
||||
extern fn capitalize_first(s: String) -> String
|
||||
extern fn add_punct(s: String, intent: String) -> String
|
||||
extern fn realize_lang(form: [String], profile: [String]) -> String
|
||||
extern fn realize(form: [String]) -> String
|
||||
extern fn realize_lang(form: Any, profile: Any) -> String
|
||||
extern fn realize(form: Any) -> String
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
// self_region.el — the engram's REAL self/identity region, pulled at query time
|
||||
// (native el). This replaces the hardcoded identity anchors and the canned
|
||||
// "I'm Neuron, the engram you're speaking with." template: the identity LANDING
|
||||
// signal and the identity READOUT both come from the engram's own Self/identity
|
||||
// nodes, read through the in-process engram el API.
|
||||
//
|
||||
// Port of self_region.py. The Python module precomputed MiniLM landing vectors;
|
||||
// here the engram's own store IS the geometry — we pull the self nodes by
|
||||
// single-term lexical search (the engram search is a single-term matcher, so we
|
||||
// pool several probes) and rank them by self-signal. No text is generated; the
|
||||
// readout is the self nodes' OWN prose, verbatim (SACRED negation survives by
|
||||
// construction — we never paraphrase, so a negated self-statement stays negated).
|
||||
//
|
||||
// ENGRAM el API NOTE: engram_search_json / engram_get_node_json / engram_node_full
|
||||
// / engram_connect are C runtime builtins. Their argument order is the C order
|
||||
// (engram_connect(from, to, weight, relation)), NOT the runtime/engram.el wrapper
|
||||
// order — we call the builtins directly and never concatenate that wrapper.
|
||||
//
|
||||
// Depends on: comprehend (str helpers via runtime), propositions (prop_split_sentences),
|
||||
// multilingual (ml_tr), the engram builtins, the json builtins.
|
||||
|
||||
// ── single-term self probes (pooled, because engram search is single-term) ────
|
||||
fn sr_terms() -> [String] {
|
||||
let t: [String] = native_list_empty()
|
||||
let t = native_list_append(t, "self")
|
||||
let t = native_list_append(t, "identity")
|
||||
let t = native_list_append(t, "Neuron")
|
||||
let t = native_list_append(t, "consciousness")
|
||||
let t = native_list_append(t, "values")
|
||||
let t = native_list_append(t, "continuous")
|
||||
return t
|
||||
}
|
||||
|
||||
// The canonical self-root: content begins "# self" or label is "# self"/"self".
|
||||
fn sr_is_root(content: String, label: String) -> Bool {
|
||||
let lc: String = str_to_lower(content)
|
||||
let ll: String = str_to_lower(str_trim(label))
|
||||
if str_starts_with(lc, "# self") { return true }
|
||||
if str_eq(ll, "# self") { return true }
|
||||
if str_eq(ll, "self") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// How strongly a node belongs to the self/identity region (integer points, to
|
||||
// avoid el's float-in-`+` pitfalls). Mirrors _self_score in self_region.py.
|
||||
fn sr_score(node_json: String) -> Int {
|
||||
let content: String = json_get_string(node_json, "content")
|
||||
let label: String = json_get_string(node_json, "label")
|
||||
let tags: String = str_to_lower(json_get_string(node_json, "tags"))
|
||||
let low: String = str_to_lower(content)
|
||||
let s: Int = 0
|
||||
// identity tags
|
||||
if str_contains(tags, "self") { let s = s + 2 }
|
||||
if str_contains(tags, "identity") { let s = s + 2 }
|
||||
if str_contains(tags, "self-model") { let s = s + 2 }
|
||||
if str_contains(tags, "consciousness") { let s = s + 2 }
|
||||
if str_contains(tags, "memory-philosophy") { let s = s + 2 }
|
||||
// the named self-traversal root
|
||||
if sr_is_root(content, label) { let s = s + 12 }
|
||||
if str_contains(low, "who i am") { let s = s + 3 }
|
||||
if str_contains(low, "i am neuron") { let s = s + 3 }
|
||||
// softer identity keywords
|
||||
if str_contains(low, "my values") { let s = s + 1 }
|
||||
if str_contains(low, "my purpose") { let s = s + 1 }
|
||||
if str_contains(low, "identity") { let s = s + 1 }
|
||||
return s
|
||||
}
|
||||
|
||||
// list-contains helper (dedup self-node ids across the pooled probes).
|
||||
fn sr_ids_has(ids: [String], id: String) -> Bool {
|
||||
let n: Int = native_list_len(ids)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
if str_eq(native_list_get(ids, i), id) { return true }
|
||||
let i = i + 1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Pull the self nodes: pool every probe's hits, dedupe by id, keep only nodes
|
||||
// with genuine self-signal (score >= 1). Returns the node-json strings.
|
||||
fn sr_pull() -> [String] {
|
||||
let terms: [String] = sr_terms()
|
||||
let nt: Int = native_list_len(terms)
|
||||
let seen: [String] = native_list_empty()
|
||||
let out: [String] = native_list_empty()
|
||||
let ti: Int = 0
|
||||
while ti < nt {
|
||||
let term: String = native_list_get(terms, ti)
|
||||
let hits: String = engram_search_json(term, 30)
|
||||
let hn: Int = json_array_len(hits)
|
||||
let hi: Int = 0
|
||||
while hi < hn {
|
||||
let node: String = json_array_get(hits, hi)
|
||||
let id: String = json_get_string(node, "id")
|
||||
if !str_eq(id, "") {
|
||||
if !sr_ids_has(seen, id) {
|
||||
let seen = native_list_append(seen, id)
|
||||
if sr_score(node) >= 1 {
|
||||
let out = native_list_append(out, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
let hi = hi + 1
|
||||
}
|
||||
let ti = ti + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Return the single highest-signal self node (the readout seed), or "" if the
|
||||
// self region is thin/empty. We keep it O(n) — pick the max-score node, with the
|
||||
// canonical root strongly favored by sr_score's +12.
|
||||
fn sr_best_node() -> String {
|
||||
let nodes: [String] = sr_pull()
|
||||
let n: Int = native_list_len(nodes)
|
||||
let best: String = ""
|
||||
let best_s: Int = 0
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let node: String = native_list_get(nodes, i)
|
||||
let s: Int = sr_score(node)
|
||||
if s > best_s {
|
||||
let best_s = s
|
||||
let best = node
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
fn sr_available() -> Bool {
|
||||
if str_eq(sr_best_node(), "") { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
// Read out the identity from the REAL self node: lead with the first first-person
|
||||
// self-statement ("I am Neuron …"), then one more grounded self line if present.
|
||||
// Verbatim from the node's own prose — no template, negation SACRED. Falls back
|
||||
// to the localized identity phrase ONLY if the live pull is empty (logged shape).
|
||||
fn sr_readout(lang: String) -> String {
|
||||
let node: String = sr_best_node()
|
||||
if str_eq(node, "") {
|
||||
// honest fallback — the self region is unreachable/thin.
|
||||
return ml_tr("identity", lang)
|
||||
}
|
||||
let content: String = json_get_string(node, "content")
|
||||
let sents: [String] = prop_split_sentences(content)
|
||||
let ns: Int = native_list_len(sents)
|
||||
let lead: String = ""
|
||||
let second: String = ""
|
||||
let i: Int = 0
|
||||
while i < ns {
|
||||
let raw: String = str_trim(native_list_get(sents, i))
|
||||
// strip a leading markdown heading marker
|
||||
let s: String = raw
|
||||
if str_starts_with(s, "# ") { let s = str_trim(str_slice(s, 2, str_len(s))) }
|
||||
let low: String = str_to_lower(s)
|
||||
let is_fp: Bool = false
|
||||
if str_starts_with(s, "I ") { let is_fp = true }
|
||||
if str_starts_with(s, "I'm") { let is_fp = true }
|
||||
if str_contains(low, "i am neuron") { let is_fp = true }
|
||||
if is_fp {
|
||||
if str_eq(lead, "") {
|
||||
let lead = s
|
||||
} else {
|
||||
if str_eq(second, "") { let second = s }
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
if str_eq(lead, "") {
|
||||
// no first-person line — read out the first non-empty sentence verbatim.
|
||||
if ns > 0 { let lead = str_trim(native_list_get(sents, 0)) }
|
||||
}
|
||||
if str_eq(lead, "") { return ml_tr("identity", lang) }
|
||||
let out: String = lead
|
||||
if !str_eq(second, "") { let out = out + " " + second }
|
||||
return out
|
||||
}
|
||||
+15
-15
@@ -1,18 +1,18 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn sem_frame(intent: String, subject: String, obj: String, modifiers: String) -> [String]
|
||||
extern fn sem_frame_lang(intent: String, subject: String, obj: String, modifiers: String, lang_code: String) -> [String]
|
||||
extern fn sem_frame_simple(intent: String, subject: String) -> [String]
|
||||
extern fn sem_frame_obj(intent: String, subject: String, obj: String) -> [String]
|
||||
extern fn sem_intent(frame: [String]) -> String
|
||||
extern fn sem_subject(frame: [String]) -> String
|
||||
extern fn sem_object(frame: [String]) -> String
|
||||
extern fn sem_modifiers(frame: [String]) -> String
|
||||
extern fn sem_lang(frame: [String]) -> String
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
extern fn sem_frame(intent: String, subject: String, obj: String, modifiers: String) -> Any
|
||||
extern fn sem_frame_lang(intent: String, subject: String, obj: String, modifiers: String, lang_code: String) -> Any
|
||||
extern fn sem_frame_simple(intent: String, subject: String) -> Any
|
||||
extern fn sem_frame_obj(intent: String, subject: String, obj: String) -> Any
|
||||
extern fn sem_intent(frame: Any) -> String
|
||||
extern fn sem_subject(frame: Any) -> String
|
||||
extern fn sem_object(frame: Any) -> String
|
||||
extern fn sem_modifiers(frame: Any) -> String
|
||||
extern fn sem_lang(frame: Any) -> String
|
||||
extern fn sem_first_modifier(mods: String) -> String
|
||||
extern fn sem_intent_to_realize(intent: String) -> String
|
||||
extern fn sem_to_spec(frame: [String]) -> [String]
|
||||
extern fn sem_to_spec_full(frame: [String], verb: String, tense: String, aspect: String) -> [String]
|
||||
extern fn sem_to_spec(frame: Any) -> Any
|
||||
extern fn sem_to_spec_full(frame: Any, verb: String, tense: String, aspect: String) -> Any
|
||||
extern fn sem_realize_greet(subject: String) -> String
|
||||
extern fn sem_realize(frame: [String]) -> String
|
||||
extern fn sem_realize_full(frame: [String], verb: String, tense: String, aspect: String) -> String
|
||||
extern fn sem_realize_lang(frame: [String], lang_code: String) -> String
|
||||
extern fn sem_realize(frame: Any) -> String
|
||||
extern fn sem_realize_full(frame: Any, verb: String, tense: String, aspect: String) -> String
|
||||
extern fn sem_realize_lang(frame: Any, lang_code: String) -> String
|
||||
|
||||
@@ -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,153 +0,0 @@
|
||||
// surface-profile.el - Surface profile data and accessors.
|
||||
//
|
||||
// THE NATIVE EFFERENT SEAM: surface = a pluggable PROFILE, using the exact same
|
||||
// slot-map mechanism as language-profile.el. A language profile tells the
|
||||
// realizer HOW to shape a natural-language surface (word order, morphology); a
|
||||
// SURFACE profile tells the realizer WHICH surface to project meaning onto
|
||||
// (markdown, docx, html, plain, or a non-text medium like symbolic music).
|
||||
//
|
||||
// The generalization is exact: realize_lang(form, profile) already renders a
|
||||
// SemForm parameterized by a [String] profile read via lang_get. Surface is one
|
||||
// more axis of that same profile vector. One frame (sem_frame), one plan step
|
||||
// (sem_to_spec), one render (realize) — the surface is DATA, not a code path,
|
||||
// precisely as language is data. Adding a surface means adding a profile, no
|
||||
// engine change. This is the multimodal projector, native: geometry -> any
|
||||
// surface, the efferent twin of ingest.
|
||||
//
|
||||
// Surface slot keys:
|
||||
// surface - "markdown" | "docx" | "html" | "plain" | "midi" | "image"
|
||||
// modality - "text" | "audio" | "image" | "video"
|
||||
// media_type - MIME type of the emitted surface
|
||||
// head_open - string prepended to a heading (e.g. "## " for markdown)
|
||||
// head_close - string appended to a heading (e.g. "" for markdown, "</h2>" for html)
|
||||
// emph_open - string opening emphasis (e.g. "*")
|
||||
// emph_close - string closing emphasis (e.g. "*")
|
||||
// item_mark - list-item marker (e.g. "- ")
|
||||
// para_sep - paragraph separator (e.g. "\n\n")
|
||||
//
|
||||
// For a TEXT modality the render composes these markers around the surface that
|
||||
// the EXISTING realizer produces (realize_lang / sem_realize). For a non-text
|
||||
// modality (audio/image) the profile declares modality + media_type and the
|
||||
// render dispatches to the medium projector, which reads the SAME frame's
|
||||
// geometry (its intent/affect/structure) and projects it onto sound or pixels —
|
||||
// deterministic-from-meaning, nothing invented. That dispatch point is where a
|
||||
// music profile or image profile conforms, native, no parallel layer.
|
||||
|
||||
// -- Constructor -------------------------------------------------------------
|
||||
|
||||
fn surface_profile(surface: String, modality: String, media_type: String, head_open: String, head_close: String, emph_open: String, emph_close: String, item_mark: String, para_sep: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "surface")
|
||||
let r = native_list_append(r, surface)
|
||||
let r = native_list_append(r, "modality")
|
||||
let r = native_list_append(r, modality)
|
||||
let r = native_list_append(r, "media_type")
|
||||
let r = native_list_append(r, media_type)
|
||||
let r = native_list_append(r, "head_open")
|
||||
let r = native_list_append(r, head_open)
|
||||
let r = native_list_append(r, "head_close")
|
||||
let r = native_list_append(r, head_close)
|
||||
let r = native_list_append(r, "emph_open")
|
||||
let r = native_list_append(r, emph_open)
|
||||
let r = native_list_append(r, "emph_close")
|
||||
let r = native_list_append(r, emph_close)
|
||||
let r = native_list_append(r, "item_mark")
|
||||
let r = native_list_append(r, item_mark)
|
||||
let r = native_list_append(r, "para_sep")
|
||||
let r = native_list_append(r, para_sep)
|
||||
return r
|
||||
}
|
||||
|
||||
// -- Accessor (same convention as lang_get; standalone so this is a leaf) -----
|
||||
|
||||
fn surface_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 surface_is_text(profile: [String]) -> Bool {
|
||||
return str_eq(surface_get(profile, "modality"), "text")
|
||||
}
|
||||
|
||||
// -- Built-in TEXT surface profiles ------------------------------------------
|
||||
|
||||
// Markdown: headings with "## ", emphasis with "*", "- " list items.
|
||||
fn surface_profile_markdown() -> [String] {
|
||||
return surface_profile("markdown", "text", "text/markdown", "## ", "", "*", "*", "- ", "\n\n")
|
||||
}
|
||||
|
||||
// Plain text: no markup at all — headings become bare uppercase-free lines.
|
||||
fn surface_profile_plain() -> [String] {
|
||||
return surface_profile("plain", "text", "text/plain", "", "", "", "", " - ", "\n\n")
|
||||
}
|
||||
|
||||
// HTML: block-level heading/emphasis tags.
|
||||
fn surface_profile_html() -> [String] {
|
||||
return surface_profile("html", "text", "text/html", "<h2>", "</h2>", "<em>", "</em>", "<li>", "\n")
|
||||
}
|
||||
|
||||
// docx: WordprocessingML is structural, not inline-markup; the head/emph slots
|
||||
// carry the run/style intent that the OOXML emitter maps to <w:pStyle>. Declared
|
||||
// here so docx is a first-class surface on the same seam.
|
||||
fn surface_profile_docx() -> [String] {
|
||||
return surface_profile("docx", "text", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "Heading2:", "", "b:", "", "bullet:", "\n")
|
||||
}
|
||||
|
||||
// -- Built-in NON-TEXT surface profiles (the multimodal seam) ----------------
|
||||
|
||||
// Symbolic music (MIDI): modality=audio. The render dispatches to the music
|
||||
// projector, which reads the SAME frame's intent/affect and projects it to
|
||||
// pitch/rhythm — deterministic-from-meaning. head/emph slots are empty because
|
||||
// the medium is not textual; media_type names the surface. A music profile
|
||||
// (scale/mode/instrument) is layered onto this by the audio agent, native.
|
||||
fn surface_profile_midi() -> [String] {
|
||||
return surface_profile("midi", "audio", "audio/midi", "", "", "", "", "", "")
|
||||
}
|
||||
|
||||
// Synthesized audio (WAV): modality=audio, peer to midi. The richer audio
|
||||
// surface — the render SUPERPOSES ingested tonal primitives (sine at f0*n per an
|
||||
// ingested instrument signature) into PCM, own-core, exactly as midi writes an
|
||||
// SMF via struct. A music profile (scale/mode/instrument/adsr) layers onto this
|
||||
// as its own [String] slot-map read by the same getter. Same frame -> midi OR
|
||||
// audio, interchangeable; this is the audio agent's native conforming point.
|
||||
fn surface_profile_audio() -> [String] {
|
||||
return surface_profile("audio", "audio", "audio/wav", "", "", "", "", "", "")
|
||||
}
|
||||
|
||||
// Image (raster): modality=image. Documented seam — the render dispatches to the
|
||||
// image projector, the efferent twin of image ingest, reading the same frame.
|
||||
fn surface_profile_image() -> [String] {
|
||||
return surface_profile("image", "image", "image/png", "", "", "", "", "", "")
|
||||
}
|
||||
|
||||
// -- Composition helpers: wrap realized TEXT with the surface's markers -------
|
||||
//
|
||||
// These take text the EXISTING realizer already produced and shape it for the
|
||||
// surface. They add NO content — pure surface typography over faithful text,
|
||||
// exactly as the language profile adds no content, only linguistic form.
|
||||
|
||||
fn surface_heading(profile: [String], text: String) -> String {
|
||||
let o: String = surface_get(profile, "head_open")
|
||||
let c: String = surface_get(profile, "head_close")
|
||||
return o + text + c
|
||||
}
|
||||
|
||||
fn surface_emph(profile: [String], text: String) -> String {
|
||||
let o: String = surface_get(profile, "emph_open")
|
||||
let c: String = surface_get(profile, "emph_close")
|
||||
return o + text + c
|
||||
}
|
||||
|
||||
// A section: a heading + a paragraph separator + the (already realized) body.
|
||||
fn surface_section(profile: [String], heading: String, body: String) -> String {
|
||||
let sep: String = surface_get(profile, "para_sep")
|
||||
return surface_heading(profile, heading) + sep + body
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
// translate.el - ELP geometry-native translation faculty (concept-pivot).
|
||||
//
|
||||
// ARCHITECTURE (corrected — Will, 2026-08-14): translation is NOT a bilingual
|
||||
// string map and needs NO external multilingual encoder. It routes through the
|
||||
// engram's concept geometry:
|
||||
//
|
||||
// comprehend(source) → CONCEPT-FRAME (language-invariant, in the manifold) → realize(target)
|
||||
//
|
||||
// A word in any language is resolved to the CONCEPT it denotes via that
|
||||
// language's own lexicon/morphology (a monolingual step — the engram's
|
||||
// nearest-region ranker only ever disambiguates senses WITHIN one language, so
|
||||
// an English-trained embedder is fine and never compares "ocean" to "océano" as
|
||||
// strings). The concept-node's location in the manifold IS the meaning; it is
|
||||
// the shared pivot. "océano" and "ocean" need not be near each other as surface
|
||||
// tokens — they resolve to the SAME concept node.
|
||||
//
|
||||
// This file supplies each target language's CONCEPT→SURFACE lexicon (its own
|
||||
// labeling of the shared concept nodes) — the mirror image of comprehend.el's
|
||||
// SURFACE→CONCEPT resolvers (cp_pron_concept, cp_analyze_verb/cp_irr2, …). The
|
||||
// frame produced by parse_spec() is the interlingua: one parse realizes into N
|
||||
// targets. Concept coverage below is the "Slowness" poem's inventory; a concept
|
||||
// with no target label passes through and is flagged oov (honest bound).
|
||||
//
|
||||
// SACRED: polarity is a concept and is never routed to a content lemma. The
|
||||
// negative-adverb concept ("never") realizes to a target negator ("nunca"/"mai"),
|
||||
// never to a content word.
|
||||
//
|
||||
// Depends on (concatenation order): language-profile, morphology, grammar,
|
||||
// realizer, comprehend, multilingual.
|
||||
|
||||
// ── VERB concept → target lemma (each language's own labeling of the concept) ──
|
||||
// The input is the language-invariant verb concept (English lemma = concept id,
|
||||
// exactly as comprehend.el emits it). NOT a translation of a Spanish string.
|
||||
fn lemma_for_concept(concept: String, lang: String) -> String {
|
||||
if str_eq(lang, "en") { return concept }
|
||||
if str_eq(lang, "es") {
|
||||
if str_eq(concept, "fight") { return "luchar" }
|
||||
if str_eq(concept, "touch") { return "tocar" }
|
||||
if str_eq(concept, "wait") { return "esperar" }
|
||||
if str_eq(concept, "see") { return "ver" }
|
||||
if str_eq(concept, "break") { return "romper" }
|
||||
if str_eq(concept, "stay") { return "quedar" }
|
||||
if str_eq(concept, "call") { return "llamar" }
|
||||
if str_eq(concept, "run") { return "correr" }
|
||||
if str_eq(concept, "chase") { return "perseguir" }
|
||||
if str_eq(concept, "take") { return "tomar" }
|
||||
if str_eq(concept, "carry") { return "llevar" }
|
||||
return ml_translate_pred(concept, "es")
|
||||
}
|
||||
if str_eq(lang, "pt") {
|
||||
if str_eq(concept, "fight") { return "lutar" }
|
||||
if str_eq(concept, "touch") { return "tocar" }
|
||||
if str_eq(concept, "wait") { return "esperar" }
|
||||
if str_eq(concept, "see") { return "ver" }
|
||||
if str_eq(concept, "break") { return "quebrar" }
|
||||
if str_eq(concept, "stay") { return "ficar" }
|
||||
if str_eq(concept, "call") { return "chamar" }
|
||||
if str_eq(concept, "run") { return "correr" }
|
||||
if str_eq(concept, "chase") { return "perseguir" }
|
||||
if str_eq(concept, "take") { return "tomar" }
|
||||
if str_eq(concept, "carry") { return "levar" }
|
||||
return ml_translate_pred(concept, "pt")
|
||||
}
|
||||
if str_eq(lang, "it") {
|
||||
if str_eq(concept, "fight") { return "lottare" }
|
||||
if str_eq(concept, "touch") { return "toccare" }
|
||||
if str_eq(concept, "wait") { return "aspettare" }
|
||||
if str_eq(concept, "see") { return "vedere" }
|
||||
if str_eq(concept, "break") { return "rompere" }
|
||||
if str_eq(concept, "stay") { return "restare" }
|
||||
return ml_translate_pred(concept, "it")
|
||||
}
|
||||
return concept
|
||||
}
|
||||
|
||||
// ── NOUN concept → [target lemma, gender] (target language's concept lexicon) ──
|
||||
fn noun_for_concept(concept: String, lang: String) -> [String] {
|
||||
let out: [String] = native_list_empty()
|
||||
if str_eq(lang, "es") {
|
||||
if str_eq(concept, "ocean") { let out = native_list_append(out, "océano"); let out = native_list_append(out, "m"); return out }
|
||||
if str_eq(concept, "root") { let out = native_list_append(out, "raíz"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "roots") { let out = native_list_append(out, "raíces"); let out = native_list_append(out, "fp"); return out }
|
||||
if str_eq(concept, "breaking") { let out = native_list_append(out, "ruptura"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "shoreline") { let out = native_list_append(out, "orilla"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "patience") { let out = native_list_append(out, "paciencia"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "wave") { let out = native_list_append(out, "ola"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "truth") { let out = native_list_append(out, "verdad"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "silence") { let out = native_list_append(out, "silencio"); let out = native_list_append(out, "m"); return out }
|
||||
return out
|
||||
}
|
||||
if str_eq(lang, "pt") {
|
||||
if str_eq(concept, "ocean") { let out = native_list_append(out, "oceano"); let out = native_list_append(out, "m"); return out }
|
||||
if str_eq(concept, "root") { let out = native_list_append(out, "raiz"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "roots") { let out = native_list_append(out, "raízes"); let out = native_list_append(out, "fp"); return out }
|
||||
if str_eq(concept, "breaking") { let out = native_list_append(out, "ruptura"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "shoreline") { let out = native_list_append(out, "costa"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "patience") { let out = native_list_append(out, "paciência"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "wave") { let out = native_list_append(out, "onda"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "truth") { let out = native_list_append(out, "verdade"); let out = native_list_append(out, "f"); return out }
|
||||
if str_eq(concept, "silence") { let out = native_list_append(out, "silêncio"); let out = native_list_append(out, "m"); return out }
|
||||
return out
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// definite article for a gender+number tag / lang. "f"|"m" singular, "fp"|"mp" plural.
|
||||
fn article_for(gtag: String, lang: String) -> String {
|
||||
if str_eq(lang, "es") {
|
||||
if str_eq(gtag, "fp") { return "las" }
|
||||
if str_eq(gtag, "mp") { return "los" }
|
||||
if str_eq(gtag, "f") { return "la" }
|
||||
return "el"
|
||||
}
|
||||
if str_eq(lang, "pt") {
|
||||
if str_eq(gtag, "fp") { return "as" }
|
||||
if str_eq(gtag, "mp") { return "os" }
|
||||
if str_eq(gtag, "f") { return "a" }
|
||||
return "o"
|
||||
}
|
||||
if str_eq(lang, "it") { if str_eq(gtag, "f") { return "la" } return "il" }
|
||||
return "the"
|
||||
}
|
||||
|
||||
// SURFACE→CONCEPT for an English object NP: strip determiner, return bare head
|
||||
// (which, for content nouns, is already the concept id).
|
||||
fn np_concept_head(np: String) -> String {
|
||||
let s: String = str_to_lower(np)
|
||||
let dets: [String] = native_list_empty()
|
||||
let dets = native_list_append(dets, "the ")
|
||||
let dets = native_list_append(dets, "a ")
|
||||
let dets = native_list_append(dets, "an ")
|
||||
let dets = native_list_append(dets, "my ")
|
||||
let dets = native_list_append(dets, "your ")
|
||||
let dets = native_list_append(dets, "his ")
|
||||
let dets = native_list_append(dets, "her ")
|
||||
let dets = native_list_append(dets, "its ")
|
||||
let dets = native_list_append(dets, "our ")
|
||||
let dets = native_list_append(dets, "their ")
|
||||
let dets = native_list_append(dets, "every ")
|
||||
let i: Int = 0
|
||||
let n: Int = native_list_len(dets)
|
||||
while i < n {
|
||||
let d: String = native_list_get(dets, i)
|
||||
let dl: Int = str_len(d)
|
||||
if str_len(s) > dl {
|
||||
if str_eq(str_slice(s, 0, dl), d) { return str_slice(s, dl, str_len(s)) }
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// CONCEPT→SURFACE: realize an object-NP concept in the target language with its
|
||||
// definite article. Unknown concept => pass the English head through (oov).
|
||||
fn np_for_concept(np: String, lang: String) -> String {
|
||||
if str_eq(np, "") { return "" }
|
||||
let head: String = np_concept_head(np)
|
||||
let pair: [String] = noun_for_concept(head, lang)
|
||||
if native_list_len(pair) < 2 { return head }
|
||||
let lemma: String = native_list_get(pair, 0)
|
||||
let gtag: String = native_list_get(pair, 1)
|
||||
return article_for(gtag, lang) + " " + lemma
|
||||
}
|
||||
|
||||
// SURFACE→CONCEPT for a subject pronoun, then CONCEPT→SURFACE in the target —
|
||||
// reusing comprehend.el's NATIVE concept-pivot (cp_pron_concept /
|
||||
// cp_rom_pron_surface). This is the template the whole faculty follows.
|
||||
fn pron_for_target(agent: String, lang: String) -> String {
|
||||
let concept: String = cp_pron_concept(str_to_lower(agent))
|
||||
if str_eq(concept, "") { return agent }
|
||||
if str_eq(lang, "en") { return cp_pron_surface(concept) }
|
||||
return cp_rom_pron_surface(concept, lang)
|
||||
}
|
||||
|
||||
// The negative-adverb concept realized as the target's preverbal negator (SACRED).
|
||||
fn negator_for_concept(neg_word: String, lang: String) -> String {
|
||||
let w: String = str_to_lower(neg_word)
|
||||
if str_eq(w, "never") {
|
||||
if str_eq(lang, "es") { return "nunca" }
|
||||
if str_eq(lang, "pt") { return "nunca" }
|
||||
if str_eq(lang, "it") { return "mai" }
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Some irregular English pasts that comprehend's cp_irr2 does not yet lemmatize
|
||||
// (source-side SURFACE→CONCEPT gap). Kept minimal; belongs long-term in cp_irr2.
|
||||
fn concept_of_verb(w: String) -> String {
|
||||
if str_eq(w, "broke") { return "break" }
|
||||
if str_eq(w, "broken") { return "break" }
|
||||
if str_eq(w, "took") { return "take" }
|
||||
if str_eq(w, "ran") { return "run" }
|
||||
return w
|
||||
}
|
||||
|
||||
// ── the faculty: EN text → concept-frame → target surface ─────────────────────
|
||||
fn translate_spec(text: String, tgt: String) -> [String] {
|
||||
// 1. comprehend(source) → concept-frame (English lemmas = concept ids +
|
||||
// SACRED polarity/neg_word). This frame lives in the concept geometry.
|
||||
let spec: [String] = parse_spec(text)
|
||||
let predc: String = concept_of_verb(slots_get(spec, "predicate"))
|
||||
let patc: String = slots_get(spec, "patient")
|
||||
let agentc: String = slots_get(spec, "agent")
|
||||
let negw: String = slots_get(spec, "neg_word")
|
||||
|
||||
// 2. realize(target): resolve each concept to the target language's surface.
|
||||
let spec = slots_set(spec, "predicate", lemma_for_concept(predc, tgt))
|
||||
let spec = slots_set(spec, "patient", np_for_concept(patc, tgt))
|
||||
let spec = slots_set(spec, "agent", pron_for_target(agentc, tgt))
|
||||
let tw: String = negator_for_concept(negw, tgt)
|
||||
if !str_eq(tw, "") { let spec = slots_set(spec, "neg_word", tw) }
|
||||
let spec = slots_set(spec, "lang", tgt)
|
||||
return spec
|
||||
}
|
||||
|
||||
fn translate_line(text: String, tgt: String) -> String {
|
||||
return realize(translate_spec(text, tgt))
|
||||
}
|
||||
|
||||
// Concept-frame fingerprint (for concept-preservation fidelity — geometry-native,
|
||||
// NOT a string cosine): the source-language-invariant concept tuple.
|
||||
fn concept_frame(text: String) -> String {
|
||||
let spec: [String] = parse_spec(text)
|
||||
let predc: String = concept_of_verb(slots_get(spec, "predicate"))
|
||||
return "pred=" + predc + " patient=" + np_concept_head(slots_get(spec, "patient")) + " pol=" + slots_get(spec, "polarity")
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
-144861
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
-130676
File diff suppressed because it is too large
Load Diff
-193894
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user