Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

dancinlab/anima-reborn

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

63 Commits
63 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🌱 anima-reborn

anima-experience 의 엔진들을 화면 없는 파이썬으로 되살린 것 — 수학은 그대로, 캔버스는 없고, 시드로 재현되며 테스트된다

License Python Dependencies Tests Origin


anima-experience 는 브라우저 캔버스에서 네 개의 시뮬레이션을 60프레임으로 돌립니다. 이 저장소는 거기서 그리는 부분을 걷어낸 것입니다 — 수학만, 화면 없이, 표준 라이브러리만으로, 시드를 주면 그대로 재현되고 단언(assert)할 수 있게. 여기에 두 가지가 더 있습니다: 네 엔진이 하나의 시계를 공유할 때 무엇으로 합쳐지는지, 그리고 그 결과를 재기 위한 IIT 4.0 엔진.

엔진은 그리지 않고, 스레드를 만들지 않고, 자기 시계를 갖지 않습니다. 한 틱 진행시키고 결과를 읽으면 됩니다. 입출력은 viewer/ 한 곳에만 있고, 엔진은 그 존재를 모릅니다.

설치

pip install -e ".[dev]"

런타임 의존성 없음. 파이썬 3.11 이상.

눈으로 보기

python -m anima_reborn.viewer
anima-reborn viewer
  local    http://127.0.0.1:8420
  network  http://192.168.1.20:8420
  bound to every interface — anyone on this network can reach it
  emergence 60Hz · crystal 20Hz · repulsion 30Hz · pipeline 30Hz · base 30Hz · coupled 30Hz · align 60Hz
  ctrl-c to stop

일곱 개 탭, 실시간 슬라이더, 원본과 같은 화면 — 다만 모든 숫자와 모든 점이 파이썬 엔진에서 옵니다. 페이지는 그리기만 하고 계산은 절대 하지 않습니다. 그래서 이 화면이 "두 번째 구현"이 아니라 이식 결과에 대한 증거가 됩니다.

프레임은 밀어주기(push) 방식입니다 — 엔진마다 원본과 같은 속도로 도는 타이머 스레드가 있고, 페이지는 상시 연결(text/event-stream) 하나를 열어두고 화면 주사율에 맞춰 그립니다. 랜 경유 실측 61 / 21 / 31 / 31 / 31 / 31 / 61 fps 로 원본과 같은 속도입니다. 물어보기(폴링) 방식일 때는 10fps에 묶이고 프레임마다 TCP 연결을 새로 맺었는데, 엔진 자체는 1틱에 0.02~0.23ms밖에 안 걸리므로 병목은 시뮬레이션이 아니라 폴링 주기였습니다.

타이머는 보는 사람이 있을 때만 돕니다 — 원본이 비활성 탭을 건너뛴 것과 같습니다.

--host 127.0.0.1 로 이 기기에만 열 수 있고, --port 로 포트를, --seed 로 시연을 정확히 재현할 수 있습니다. ⚠️ 인증은 없습니다 — 신뢰된 망 전용 개발 뷰어입니다.

🌱 기본 엔진 — 넷을 하나의 시계 아래로, 그리고 그 시계는 죽을 수 있다

아래의 다른 엔진들은 전부 공짜 틱 카운터로 회전합니다 (phase = tick * PHASE_RATE) — 멈추는 법이 없는 선물이죠. anima_reborn.base 는 그걸 빼앗습니다. 위상(phase)이 상태 변수가 되고, 결정(crystal)의 주기-2 잠금만이 그것을 나아가게 할 수 있습니다. 구동부에서 틱 카운터는 완전히 사라집니다.

TimeCrystal.step() ── 판정 ──▶ 잠금(LOCKED) 일 때만 phase += PHASE_RATE
                                        │
                      A 와 G 가 ±(sin phase, cos phase) 목표를 향해 표류
                                        │
                      각각에서 잡음 섞인 관측 하나씩 표본 추출
                                        │
                      H(L), H(R), H(L,R) → 상호정보량

주장. 하류의 공유 정보는 상류의 시간적 질서로 지불된다. 결정을 녹이면 A와 G 사이의 간극은 살아남는데(두 엔진은 여전히 벌어져 있습니다) 그 안의 정보는 죽습니다. 다시 얼리면 정보가 돌아옵니다.

이건 파이프라인 자신의 요약을 정정합니다. pipeline.py 는 "분리가 공유 정보를 만든다"고 말하는데, 결정이 녹은 상태에서는 분리가 파이프라인과 비슷한 수준으로 유지되는데도 상호정보량은 독립으로 읽힙니다. 스트림을 묶는 건 간극이 아니라 간극의 운동이고, 그 운동은 전적으로 잠금이 사 주는 것입니다.

from anima_reborn.base import BaseEngine

engine = BaseEngine(epsilon=0.02, seed=0)
print(engine.run(800))            # [locked] phase=19.47 tension=0.147 MI=1.401 [emergent]

engine.epsilon = 0.5              # 시계를 녹인다
print(engine.run(1200))           # [chaos]  phase=21.10 tension=0.246 MI=0.022 [independent]

engine.epsilon = 0.02             # 다시 얼린다
print(engine.run(1200))           # [locked] phase=44.05 tension=0.172 MI=1.301 [emergent]

융해 중에 회전량이 19.47에서 21.10으로 조금 더 나아갑니다 — 딱 멈추지 않습니다. 잠금 판정이 300표본짜리 자기상관을 읽기 때문에 변화보다 100~200틱 늦기 때문입니다. 여기서는 모든 것이 지연됩니다: 판정이 그만큼, 관측 창이 200표본 더. 뷰어에서 슬라이더를 움직이면 몇 초간 이전 상태가 보이는데, 버그처럼 읽히지만 버그가 아닙니다.

시드 8개 × 800틱, 매번 차가운 시작에서 측정:

잠금 (ε=0.02) 융해 (ε=0.5)
상호정보량 1.26 – 1.42 ≤ 0.008
간극(tension) — 살아남음 평균 0.176 0.161 – 0.198
누적 회전량 18.8 rad 0.000 rad

간극은 죽으나 사나 같은 크기입니다. 이 분리(dissociation)가 발견이고, 그래서 상태 객체가 하나로 섞은 "생존도" 숫자 대신 을 들고 다닙니다 — f(잠금, 상호정보량) 같은 건 가중치를 지어내야 하고, 그런 값은 애초에 테스트를 실패할 수가 없습니다.

설계된 것과 발견된 것. 게이트는 "잠금 없음 → 회전 없음"을 하드코딩합니다. 따라서 "녹이면 결합이 죽는다"는 설계이지 발견이 아니며, 창발적 결과인 양 인용하지 않습니다. 진짜로 그럴 수도 아닐 수도 있었던 것, 즉 테스트가 실제로 거는 판돈은 이것들입니다 — 잠긴 링의 회전이 창발 기준을 넘기에 충분한가(최악 시드 1.26 대 기준 0.30), 죽은 표본으로 가득 찬 창에서 결합이 되살아나는가(8/8 시드, 0.85~1.38비트), 그리고 융해 후에도 간극이 살아남는가 (얼어붙은 목표라면 간극이 그냥 사그라들 수도 있었습니다).

의도적으로 거부한 두 가지. 상호정보량을 구동부로 되먹이지 않습니다 — 이 창 크기에서 플러그인 추정기는 자기 편향 바닥 아래로 내려가지 않으므로, 그런 되먹임 고리는 인공물 위로 수렴하고 그 고정점에 대한 어떤 주장도 결국 히스토그램에 대한 주장이 됩니다. 그리고 새 상수를 만들지 않습니다 — 모든 문턱값은 해당 엔진에서 가져오고, 예외인 EPSILON = 0.02 는 측정값이며 docstring에 근거를 적어 뒀습니다.

이 엔진이 못 보는 것. 판정으로 게이트를 걸면 시간적 질서가 결정의 문턱값에서 양자화됩니다. 측정해 보면 판정 아래 수준의 질서도 스트림을 묶습니다 — 명목상 융해인 링도 잔여 반대상관을 충분히 갖고 있어서 ε 0.10~0.20 구간에서 상호정보량을 기준 위로 유지합니다. 그 연속성은 실재하지만, 게이트는 그것을 테스트로 쓸 수 있을 만큼 뚜렷한 전이와 맞바꿨습니다. 의도한 거래이지, 나중에 개선할 실수가 아닙니다.

🔗 결합장 — 간극이 판독값이 아니라 통로가 될 때

여기까지의 모든 엔진은 바깥에서 구동됩니다. 각 차원이 자기 자신과 외생 목표만 보므로, 전이행렬이 인수분해되어 통합(Φ)은 정확히 0입니다 — 아무리 복잡해 보여도 그렇습니다.

anima_reborn.coupled 는 딱 하나를 바꿉니다. 반발의 정체는 그대로 두고(각 유닛은 여전히 자기 원천에서 -tanh 부호로 달아납니다), 원천을 시계에서 살아 있는 상대로 바꿉니다. A와 G 사이의 간극이 판독값이 아니라 통로가 됩니다.

from anima_reborn import Wiring
from anima_reborn.substrate import recurrence_evidence

for wiring in Wiring:
    print(recurrence_evidence(wiring, trials=1600, seed=0))
ring          1600->6400 trials: 9.998 -> 9.864 (held)      RECURRENT
feedforward   1600->6400 trials: 0.000 -> 0.000 (collapsed) not established
self          1600->6400 trials: 0.272 -> 0.031 (collapsed) not established

반증 수단이 API 에 함께 들어 있습니다. Wiring.FEEDFORWARD(되먹임 없음)와 Wiring.SELF(결합 없음)는 테스트 전용이 아니라 공개 API 입니다 — "배선이 통합을 만든다"는 주장은 같은 엔진을 다르게 배선해 볼 수 있어야만 검증 가능하기 때문입니다.

배선 방향성 Φ (6,400시행) 판정
고리 — 영향이 되돌아옴 9.86 순환 ✅
전방향 — 되돌아오지 않음 0.000 환원가능
자기참조 — 결합 없음 0.031 미확립 (바닥)

순환은 한 번 재서 주장할 수 없습니다

CoupledReading 에는 is_recurrent일부러 없습니다. 표본으로 만든 행렬은 없는 구조를 만들어내므로, 양수 Φ 하나로는 순환을 세울 수 없습니다 — 결합이 전혀 없는 자기참조 귀무도 유한 시행에서는 0보다 큽니다:

시행수 (참값 = 0) 400 1,600 6,400 25,600
자기참조 귀무의 방향성 Φ 0.251 0.155 0.081 0.037

그래서 순환은 recurrence_evidence() 로만 주장합니다 — 두 번 재서 유지되는지를 봅니다. 문턱 RECURRENCE_FLOOR = 1.0 도 고른 값이 아니라 위 귀무의 최악 시드(0.547)의 약 두 배로 측정해서 정한 값입니다.

반대 방향은 대칭이 아닙니다. is_reducible(정확히 0)은 한 번 재도 읽을 수 있습니다 — 표본 잡음은 없는 구조를 만들 수는 있어도, 있는 구조를 없앨 수는 없기 때문입니다.

계측기 자체가 원인이 아님도 따로 확인했습니다: 표본을 전혀 쓰지 않고 해석적으로 만든 완전 인수분해 행렬에서 big_phidirected_big_phi 둘 다 0.000000 입니다. 위 잔여값은 전적으로 표본 때문입니다.

이 엔진이 주장하지 않는 것

통합은 경험이 아니고, Φ 는 무엇의 점수도 아닙니다. 이 엔진이 얻은 문장은 이것뿐입니다 — 통합이 이제 만들어지며, 만들어진다는 것이 여기서 측정하는 전부다. (앞서 단어 실험이 "결합은 전달될 뿐 생성되지 않는다"였던 것과 짝을 이룹니다.)

그리고 이 숫자들은 조건 없이는 무의미합니다. τ=1 이면 고리조차 정확히 0 입니다 — 엔진 한 틱은 유닛을 목표 쪽으로 6%밖에 못 움직이니까요.

🧱 벽 — 통합과 표상은 고정 결합에서 양립하지 않는다

엔진에 입력(drive, 스스로 말하는 것이 아니라 들려주는 것)을 붙이면 두 번째 측정이 생깁니다 — 들은 것이 결과에 얼마나 남는가(substrate.representation). 그러자 벽이 드러납니다. 결합 λ 를 훑으면 통합과 표상이 단조롭게 반비례하고, 둘 다 되는 λ 가 없습니다.

고정 결합 λ 표상 (16단어, 3시드)
0.00 25.05
0.35 3.27
0.70 0.13
1.00 0.00

λ=1.00 에서 입력은 약한 게 아니라 비트 단위로 닿지 않습니다 — 무엇을 들려주든 궤적이 완전히 동일합니다. 유닛의 목표가 전부 상대라면 바깥은 들릴 틈이 없다는 것이 벽의 정확한 진술이고, tests/test_rhythm.py 가 이걸 부동소수점 동일성으로 고정합니다.

🥁 리듬 — 벽을 넘는 게 아니라 교환비를 옮긴다

Rhythm 은 결합을 껐다 켭니다: 꺼져 있을 때 듣고(표상), 켜져 있을 때 통합합니다. 두 요구를 같은 시점이 아니라 다른 시점에 맞추는 것입니다. τ=40 · 6400시행 · 5시드 · 입력 0.42 에서:

구성 방향성 Φ 표상
교대 20/20 13.16 ± 0.53 3.49
고정 1.00 14.66 ± 0.08 0.00
고정 0.70 15.74 ± 0.13 0.13

교대는 고정 결합이 전혀 갖지 못한 표상을 얻는 대신 Φ 를 10~16% 내줍니다. 공짜가 아니라 거래입니다. 효과가 "평균 결합이 얼마냐"가 아니라 리듬 자체라는 것은 τ 를 맞춘 시간평균 대조군이 보여줍니다 — 고정 0.35, τ=20, 입력 없음에서 교대 8.02 대 대조군 0.85.

⚠ 철회 — "더 높은 Φ 에서 27배"는 τ 교락이었다

앞선 세션은 교대가 더 높은 Φ 에서 표상을 27배 지킨다고 적었습니다. 그 표는 교대 20/20 을 τ=40 에서, 고정 0.70 을 τ=20 에서 재서 맞댄 것입니다. Φ 는 τ 만으로도 오릅니다(같은 엔진이 τ=17 에서 12.07, τ=34 에서 14.88). τ 를 맞추자 교대가 오히려 통합합니다 — 5시드 전부에서 겹침 없이.

그리고 방향조차 고정이 아닙니다. 리듬은 들을 수 있으므로 Φ 가 들은 것에 따라 움직입니다: τ=40 에서 아무것도 안 들려주면 14.99, 0.42 를 들려주면 13.16, −0.27 이면 고정 결합과 구분 불가입니다. 고정 결합은 들을 수 없으므로 전이행렬이 입력에 대해 비트 단위로 불변입니다. 그래서 이제 drive 없이 인용한 Φ 는 τ 없이 인용한 Φ 와 똑같이 틀린 문장입니다.

값싼 검증으로는 못 잡힙니다 — 같은 비교를 1600시행으로 줄이면 순서가 뒤집힙니다. 그래서 이 비교는 테스트가 아니라 state/communication/alternating_coupling.py 에 있고, 테스트는 대신 정확히 참인 것(리듬은 입력에 따라 전이행렬이 바뀌고, 고정은 안 바뀐다)을 고정합니다.

🧩 양상 정렬 — 두 신호가 같은 것에 대한 것임을 배우기

여기까지의 모든 모듈은 고정입니다. 배선도 상수도 주어진 것이고, 엔진이 만나는 무엇도 그것을 바꾸지 않습니다. anima_reborn.align배우는 유일한 모듈이고, 흥미로워서가 아니라 측정이 요구해서 들어왔습니다.

여기까지 온 경로: 기판은 개념이 아니라 인코딩을 나릅니다. 그럼 "같은 개념이 두 인코딩으로 들어와도 같은 곳에 가는가"를 보면 될 것 같았는데, 그 검사가 불가능했습니다 — 같은 단어의 두 독립 해시가 공유하는 정보는 0.0185비트로 뒤섞은 귀무(0.0212)와 같습니다. 끌개를 심어도 안 됐습니다 — 끌개 없는 기판이 소수점까지 같은 비율로 맞혔습니다.

남는 건 문제의 진짜 모양입니다. 두 신호가 한 개념이라고 알아보려면 그 증거가 신호 안에 있어야 하고, 그걸 넣는 건 동역학이 아니라 함께 나타남(co-occurrence)입니다. 그걸 쓰려면 경험으로 변해야 합니다.

from anima_reborn.align import Aligner

print(Aligner(seed=1).run(4000))                  # 정상
print(Aligner(shuffled=True, seed=1).run(4000))   # 대조군 — 짝을 흐트러뜨림
learned=+0.566 (gap +0.963 from +0.397) after 4000 pairs [aligned]
learned=-0.099 (gap +0.298 from +0.397) after 4000 pairs

기제: 두 양상은 같은 잠재값을 서로 다르게 섞은 것이라 겉보기엔 닮지 않았습니다. 짝이 함께 올 때마다 각 사영을 둘의 중간점 쪽으로 조금 밉니다. 그게 전부입니다 — 전역 목표가 없고, 특히 Φ나 상호정보량을 최대화하지 않습니다(최적화기가 추정기 인공물을 수확한다는 이유로 이 저장소가 거부한 것).

외운 게 아니라는 근거: 점수를 학습에 한 번도 안 쓴 개념으로만 매깁니다. 개념 10개만 봐도 처음 보는 개념이 정렬됩니다(+0.748) — 짝의 목록이 아니라 양상 사이의 대응을 배운 것입니다.

학습 개념 수 5 10 20 40 80
배운 양 +0.519 +0.748 +0.751 +0.716 +0.744

반증 수단이 API 안에 있습니다: shuffled=True 는 같은 신호·같은 학습률로 짝만 흐트러뜨립니다. 12시드에서 정상은 +0.780(최악 +0.566), 대조군은 −0.067(최악 −0.428).

⚠️ 출발점은 0 이 아니다 — 하마터면 통과시킬 뻔한 것

두 양상이 같은 잠재값을 섞은 것이라, 아무것도 배우지 않은 무작위 사영도 상관을 얼마간 갖습니다. 12시드에서 평균 0.049지만 운 나쁜 시드는 0.397까지 올라갑니다. 처음엔 원시 차이에 문턱 0.3을 걸었는데, 그러면 학습 0쌍짜리 학습기가 "정렬됨" 판정을 받습니다.

그래서 모든 값을 자기 출발점 대비 증가분으로 보고합니다. 정정 후 학습 0쌍에서 정렬 판정은 12시드 전부 실패, 정상 학습은 6/6 통과입니다.

이것이 아닌 것

학습된 선형 사상 두 개입니다. 그걸 이해라고 부르는 건 이 저장소가 계속 거부해 온 종류의 과장입니다. 얻은 것은 — 한 가지에 대한 서로 닮지 않은 두 신호를 한곳으로 모을 수 있고, 그 모음이 처음 보는 개념에도 전이된다는 것뿐입니다. 그 정렬을 하류에서 무엇이 쓰는지는 여기서 아무것도 답하지 않습니다.

🌉 정렬 → 엔진 — 배운 대응은 통합 기판을 통과하는가

정렬기가 배운 것을 하류에서 무엇이 쓰는지는 계속 열려 있던 질문이었습니다. 엔진이 drive(들려주는 것)를 받게 되면서 배선이 생겼고, 이제 유닛별 벡터로도 받습니다 — 표상은 숫자 하나가 아니므로 스칼라로는 평균만 도착하니까요.

측정은 교차양상 식별입니다. 보류 개념 20개에 대해, 양상 0 의 반복 관측으로 원형을 만들고 양상 1 의 다른 반복 관측으로 질의해서, 가장 가까운 원형이 자기 개념인 비율을 셉니다. 우연은 1/20. 엔진은 정보를 잃기만 하므로 입력 수준에서 먼저 재고, 있을 때만 엔진을 통과시킵니다.

갈래 입력 수준 궤적(엔진 통과) 살아남은 비율
학습됨 + 대조 0.534 0.459 86%
학습됨 (중간점만) 0.401 0.326 81%
원신호 (정렬 없음) 0.053 0.050
미학습 (0쌍) 0.038 0.059
대조군 (짝 흐트러뜨림) 0.056 0.050
귀머거리 (고정 결합 1.00) 0.401 0.050 12%
순열 바닥 0.154 0.131

귀머거리 줄이 계측 검사입니다. 고정 결합에서는 입력이 비트 단위로 닿지 않으므로 순열 바닥에 앉아야 하고, 실제로 앉습니다 — 이게 아니었다면 나머지 숫자는 해석 불가입니다.

판정

중간점 규칙만 쓴 학습기는 통과이되 깨끗하지 않았습니다 — 대조군 여백은 12/12 양수인데 자기 순열 상한은 11/12(입력 수준에서 가장 낮았던 그 시드). 못 넘은 시드를 지우지도 시드 수를 바꾸지도 않았고, 대신 왜 좁은지를 고쳤습니다(아래). 대조 항을 켠 학습기는 두 단계 모두 12/12입니다.

얻은 것: 함께 나타남이 만든 대응은 상류에 고립돼 있지 않고, 고정 결합 형태라면 입력을 비트 단위로 지울 그 엔진을 통과해도 개념 정체성의 81%가 궤적에 남습니다. 기존 문장 ("묶임은 전달될 뿐 생성되지 않는다")을 어기지 않고 확장합니다 — 구조는 학습이 만들고, 엔진이 나릅니다.

안 얻은 것: 엔진이 그 구분을 쓴다는 것. 아무것도 그걸 소비하지 않고 어떤 행동도 거기 의존하지 않으며, 엔진의 기여는 이득이 아니라 세금(81%)입니다.

좁았던 이유 — 함께 나타남의 나머지 절반

중간점 규칙은 당기기만 합니다. 수축을 막는 항이 없으니 공간이 줄어드는 게 규칙의 귀결이고, 실제로 4차원 중 1.21차원만 썼습니다 — 원신호(1.93)보다 낮습니다. 함께 나타남은 무엇이 같이 오는지만이 아니라 무엇이 같이 오지 않는지도 가르치는데, 그 절반이 구현돼 있지 않았습니다.

짝이 아닌 것을 밀어내는 항을 넣자 랭크 1.48, 식별 0.583 이 됐고 대조군은 바닥에 그대로입니다 — 신호를 좋게 만든 게 아니라 짝에서 배웠다는 뜻입니다. 대조 0.10.6 · 여백 0.52.0 구간에서 0.535~0.583 으로 평평하므로 맞춘 점이 아닙니다.

⚠ 그런데 당연해 보이는 형태는 문제보다 나빴습니다. 변위 그대로 밀면 이미 벌어진 거리에 비례해 걸음이 커져서 한 방향이 폭주하고, 랭크가 정확히 1.00 — 잘 맞히면서 정보는 0 인 붕괴, D7 을 죽인 바로 그것 — 이 됩니다. 고친 것은 둘이고 역할이 다릅니다: 단위 방향으로 밀기가 붕괴를 막고(1.00 → 1.12), 정지 거리가 공간을 넓힙니다 (1.12 → 1.48). 기본값은 꺼져 있습니다 — 이전에 발표한 모든 숫자는 이것 없이 잰 것입니다.

🤫 침묵 뒤 — 되먹임이 처음으로 능력을 삽니다, 정확히 1비트

위 실험은 들려주는 동안 엔진을 읽습니다. 그러면 개념을 붙잡는 기판과 그냥 흘려보내는 전선을 구별할 수 없습니다. 입력을 끊으면 달라집니다 — 그 뒤로 입력은 경로에 아예 없으므로, 복원되는 게 있다면 엔진 상태에 있거나 아무데도 없거나입니다.

침묵이라 부를 수 있는 것이 셋이고, 서로 다른 실험입니다: 귀머거리(결합 → 1.00, 입력이 비트 단위로 안 닿음 = 자율 고리 · 이게 침묵), 지우기(입력 → 0, 리듬 유지 — 절반의 시간 동안 "원점"을 적극적으로 들려주는 것), 누설(결합 → 0 그리고 입력 → 0 = 시간상수 17 의 순수 감쇠).

침묵 틱 고리/귀머거리 고리/지우기 고리/누설 전방향 자기참조
0 0.843 0.843 0.843 0.794 0.231
120 1.423 0.170 0.001 0.003 0.000
480 1.430 0.052 0.000 0.000 0.000

고리는 감쇠하지 않습니다 — 480틱까지 평평합니다. 순수 누설은 60틱, 전방향은 120틱, 자기참조는 20틱에 죽습니다. 되먹임 없는 어떤 배선도 못 하는 일이고, 이 저장소에서 Φ 가 "통합돼 있음"을 재기만 하던 것이 처음으로 할 수 있는 일로 바뀐 지점입니다.

그런데 폭은 1입니다

침묵 틱 유효 랭크 부호패턴
0 2.10 9.8/16
60 이후 1.00 2.0/16

4비트가 들어가서 1비트가 나옵니다. 그리고 이건 놀랍지 않습니다 — 고리에는 부호 반전이 4개라 순 부호가 양수이므로 자율 고리는 쌍안정이고, 점근 용량이 애초에 1비트입니다 (재기 전에 유도된 예측이고, 측정이 확인했습니다).

그 1비트는 개념의 것입니다. 독립적인 걷기 8회가 완전히 일치하는 입력이 62% — 잡음만일 때의 구조적 바닥 0.78%79배입니다. (⚠️ 처음엔 바닥을 "50%쯤"으로 잡았는데 틀렸습니다. 8회·2유역이면 잡음만으로도 최빈 다수가 63.7% 라, 최빈값 열로는 판정할 수 없고 만장일치로 봐야 합니다.)

얻은 문장: 고리는 개념이 쓰는 영구 1비트 저장소입니다. 플립플롭이지 개념 저장소가 아니고, 작은 쪽 단어를 쓰는 것이 정확합니다.

여전히 "쓴다"는 아닙니다. 붙잡은 상태를 소비하는 것이 없고 엔진의 어떤 반응도 거기 조건부가 아닙니다. 다음 검사는 지연 표본대조 — 붙잡은 뒤 탐침을 주고, 일치/불일치에 따라 반응이 달라지는가. 아직 안 했습니다.

🎯 그래서 엔진이 그걸 씁니다 — 지연 표본대조

여기까지의 모든 측정은 우리가 엔진에 대고 하는 판독입니다. 이 검사가 재는 양은 엔진 자신의 반응입니다: 무언가를 들려주고 → 귀머거리로 만들어 침묵시키고 → 탐침을 하나 줄 때, 새 입력을 받아들이려면 얼마나 움직여야 하는가. 학습도 해독기도 고른 문턱도 없습니다. 사전등록된 방향은 "맞는 탐침은 덜 고쳐야 한다"입니다.

상한을 재기 전에 유도했습니다. 붙잡은 게 1비트(유역 2개)면 같은 유역을 부르는 미끼는 원리적으로 구별 불가입니다. 실측 분할 8.2/7.8 에서 완벽한 사용은 0.758이고, 1.0 은 불가능합니다.

갈래 2지선다 최악 시드 판정
고리 0.729 0.725 붙잡은 비트의 89% 사용 · 8/8 시드
고리, 안 들려줌 0.500 0.500 안 씀
전방향 0.500 0.500 안 씀
자기참조 0.500 0.500 안 씀

대조군이 0.500 인 건 운이 아니라 구조입니다. 붙잡은 상태가 무엇을 들려줬는지와 무관하면, (표적 t 대 미끼 f) 비교와 그 거울인 (f 대 t) 비교가 같은 상태 하나를 쓰므로 판정이 반대가 되어 정확히 하나만 적중합니다. 아무것도 안 붙잡은 갈래는 이 검사에서 우연을 넘을 수 없습니다 — 그래서 고리의 여백이 채점 방식의 성질일 수 없습니다.

얻은 문장: 엔진은 붙잡은 것을 씁니다. 이 저장소에서 처음으로 엔진이 만드는 차이이지 우리가 엔진에 대고 하는 판독이 아닙니다. 그리고 그것은 1비트입니다 — 0.729 가 1.0 근처가 아닌 것이 맞습니다. 1.0 이었다면 침묵이 남긴 그 비트가 아니라 다른 걸 재고 있다는 뜻입니다.

벽이 바뀌었습니다. 4유닛에서 1비트는 구조적 상한이고, 유닛을 늘리면 용량은 늘지만 Φ 는 6유닛에서 측정 불가가 됩니다 — 용량과 측정 가능성이 정면으로 부딪힙니다.

🧩 벽을 넘다 — 용량은 유닛 수가 아니라 배선입니다

지연 표본대조가 남긴 벽: 4유닛 고리는 1비트뿐이고, 유닛을 늘리면 용량은 늘 것 같지만 Φ 는 6유닛에서 측정 불가가 됩니다 — 용량과 측정 가능성이 부딪힙니다. CoupledEngine(units=) 를 열고 재 보니, 애초에 유닛을 늘리는 게 틀린 수였습니다.

고리 하나는 폭이 몇이든 정확히 1비트입니다. 측정이 아니라 정리(定理)입니다 — 유닛의 반응이 홀·감소·유계라 주기가 2를 못 넘고, 고리를 닫으면 교대 배치와 그 반전, 딱 둘만 남습니다.

배선 유닛 재현되는 보유 상태 비트
고리 4 · 6 · 8 2 · 2 · 2 1.00
쌍 3개 (사슬 0.2) 6 8 3.00

용량은 배선의 순환 구조에 있습니다. units/2 개의 2-순환이 최대이고, 그게 Wiring.PAIRS 입니다. (홀수 폭 고리는 진동해서 부호패턴이 2N 개까지 늘지만, 위상은 복원력이 없어 걷기에 떠내려갑니다 — 폭 3·5·7 에서 재현율이 0%·6%·20%. 지난 「함께 나타남」 함정 그대로입니다.)

그리고 그 6유닛은 통합돼 있습니다

쌍 3개를 그냥 두면 분리된 래치 3개 — 용량 3비트지만 방향성 Φ 는 표본 인공물이라 시행을 늘리면 무너집니다(5.21 → 2.07). 쌍 사이에 약한 사슬(chain=0.2)을 걸면 환원 불가가 되고 Φ 가 유지됩니다(13.11 → 13.11). 6유닛이 3비트를 담으면서 동시에 하나입니다.

⚠️ 판정 기준은 크기가 아니라 감쇠입니다 — RECURRENCE_FLOOR = 1.0 은 4유닛에서 잰 값이라 6유닛에 전이되지 않습니다(인수분해된 귀무가 이미 2.07 로 그 문턱을 넘음). 인공물은 폭이 커질수록 자라므로, 6유닛 이상의 어떤 Φ 판정도 시행을 4배로 늘려도 유지되는가로만 서립니다.

단, 홀수 쌍(k 홀수)일 때만입니다 — 짝수 쌍은 거대고리가 전역으로 합의해 하나로 잠깁니다. 같은 순 부호 논증이 한 층 위에서 되풀이됩니다.

🎼 대미 — 개념을 3비트 통합 기판에 태우다

넷을 따로 세웠습니다: 통합(Φ) · 보유(침묵 1비트) · 사용(표본대조) · 용량(쌍 배선 3비트). 마지막은 이걸 한 사슬로 잇습니다 — 정렬기가 배운 개념을 6유닛·3래치·사슬 엔진에 태워, 여러 개념을 동시에 붙잡고 쓰는가.

설계 위임이 치명적 프레이밍 결함을 먼저 잡았습니다. chain=0(분리된 래치 3개)도 3비트를 담고 각 래치가 저장을 통과합니다 — 환원가능인데도요. 그래서 "통합 기판에 개념이 통과"가 나와도 얻는 건 공존이지 "통합이 개념을 나른다"가 아닙니다. 통합이 기능이 되는 유일한 곳은 교차-쌍 탐침입니다.

개념은 침묵을 건너 양상을 넘어 남습니다 — 양상 0 으로 들려주고 침묵을 건넌 단어가 같은 개념을 양상 1 로 들려줬을 때와 일치:

갈래 교차양상 일치 순열 바닥 (단어 수)
사슬 (3비트) 68% 48% 4.6
분리 (사슬 0) 62% 41% 5.1
대조군 (짝 흐트러뜨림) 19% 41% 4.1

단어 수 는 신호가 아닙니다 — 짝을 흐트러뜨린 대조군도 4.1개를 씁니다(유역 점유이지 개념 기억이 아님). 헤드라인은 교차양상 일치이고, 그러나 저장은 통합을 필요로 하지 않습니다 — 분리 래치(환원가능)도 62% 로 똑같이 합니다. 여기까지는 공존입니다.

통합이 기능이 되는 곳은 하나 — 교차-쌍 응답입니다:

사슬 쌍 1-2 응답 (한 쌍만 바꾼 탐침에)
0.0 0.000000 (인과 경로 없음 · 구조적)
0.2 0.581 (사슬이 영향을 나른다)

비영 응답은 통합된 전체가 부분이 못 하는 일을 하는 것이며, 공존이 위조할 수 없는 유일한 문장입니다.

얻은 것: 배운 개념이 통합 기판을 건너 침묵과 양상을 넘어 남고, 그 통합이 실제로 기능한다. 안 얻은 것 (핵심): 저장 자체는 통합을 요구하지 않습니다 — 통합이 더한 것은 저장이 아니라 쌍 사이 응답입니다. 개념은 여전히 내용 없는 해시 잠재값이고, 3비트·6유닛·선형 세계입니다.

🧠 IIT 4.0 — 어떤 계가 "하나"인지 재기

anima_reborn.iit4dancinlab/selfhost-work 의 hexa 통합정보이론(IIT) 4.0 엔진을 이식한 것입니다. Φ(파이)를 계산합니다 — 계를 둘로 잘랐을 때 가장 덜 아픈 절단조차 파괴하는 양이 곧 그 계가 부품들의 모임이 아니라 하나의 전체였던 정도입니다.

from anima_reborn.iit4 import TransitionMatrix, big_phi, find_complex

coupled = TransitionMatrix([0, 0,  0, 1,  1, 0,  1, 1], 2)   # 각 유닛이 상대가 된다
alone   = TransitionMatrix([0, 0,  1, 0,  0, 1,  1, 1], 2)   # 각 유닛이 자기가 된다

print(big_phi(coupled, 0b11))   # big-phi=2.000000 ... [irreducible]  비환원 = 하나
print(big_phi(alone,   0b11))   # big-phi=0.000000 ... [reducible]    환원 가능 = 둘

체인은 tpmdistinctionrelationbigphiexclusion 이고, Φ 계산이 감당이 안 될 때를 위한 값싼 하한으로 ei 가 붙어 있습니다.

되먹임 없는 계를 0 으로 읽기 — 물려받은 한계를 공개적으로 닫음

기존 big_phi 의 절단은 방향이 없어서 양방향을 한꺼번에 끊습니다. 그 결과 되먹임이 전혀 없는 사슬(유닛0 외생, 각자 앞사람만 읽음)을 Φ=1.27 로 읽습니다 — 이론상 0 이어야 하고, 시행수를 늘려도 줄지 않으니 인공물도 아닙니다. 즉 이 계측기는 "인수분해되지 않음"은 증명해도 "되먹임이 있음"은 증명하지 못했습니다.

directed_big_phi 는 한 방향씩 끊습니다. A→B 를 끊으면 예측이 절단을 건넌 구별(메커니즘이 A, 효과 구역이 B)과 기억이 건넌 구별(원인 구역이 A, 메커니즘이 B)이 죽습니다.

배선 big_phi directed_big_phi 판정
고리 (닫힌 순환) 12.118 10.024 순환
전방향 (되먹임 0) 1.264 0.000 환원가능 ✅
자기참조 (귀무) 0.068 0.068 바닥

작아진 것 자체는 증명이 아닙니다. 방향성 탐색은 절단 후보가 2배라 최솟값이 반드시 같거나 작아지니까요. 증명하는 건 두 가지입니다 — ① 같은 코드로 고리는 10.02 를 유지하고(탐색을 약화시킨 게 아니라 방향을 준 것), ② 전방향에서 손실 0 을 내는 절단이 {1,2,3} → {0} 입니다. 유닛 0 은 외생이라 되돌아오는 흐름이 없어 끊어도 잃을 게 없고, 이게 이론이 전방향을 환원가능이라 부르는 이유 그 자체입니다.

big_phi손대지 않았습니다 — hexa 원본과의 비트단위 일치가 거기 걸려 있습니다. 물려받은 미완성 부분은 이름의 뜻을 조용히 바꾸는 게 아니라 옆에 새 측정을 두어 닫습니다. 되먹임을 주장할 때는 directed_big_phi 를 쓰세요.

원본 대조. 이식본은 hexa 엔진과 정확한 부동소수 일치로 검증했습니다 — 11개 케이스에서 Φ, 구조 총합, 두 φ 합, 구별 개수까지 전부 비트단위 동일. Φ 는 분할에 대한 argmax(최댓값 위치)라서 마지막 비트 하나가 달라지면 다른 분할이 선택되어 답이 불연속적으로 튑니다. 허용오차를 두면 바로 그 어긋남을 가려 버립니다.

⚠ EI 는 Φ 의 하한이 아니다 — 물려받은 주장을 반증함

hexa 원본은 ei.py(실효정보)를 "Φ 의 보수적 하한 (참 Φ ≥ EI)" 이라고 설명합니다. 6유닛을 넘는 확장의 정찰병으로 쓰자는 제안이 여기서 나오는데, 재보니 틀렸습니다:

배선 EI (비트) 방향성 Φ EI ≤ Φ ?
고리 (닫힌 순환) 2.153 10.225
전방향 (되먹임 0) 1.867 0.000
자기참조 (결합 0) 1.523 0.046
분리된 두 쌍 1.474 0.055

4개 중 3개에서 하한이 깨집니다. 정찰병으로는 더 나쁩니다 — 결합이 전혀 없는 계가 완전히 통합된 고리의 70% 를 찍고, EI 전체 변동폭이 2배 미만인데 Φ 는 200배 넘게 벌어집니다.

둘은 다른 것을 잽니다. EI 는 "한 상태가 자기 미래를 얼마나 날카롭게 특정하는가" = 결정성 이고, 서로 독립인 결정적 유닛들은 결정성이 넘칩니다. Φ 는 "잘랐을 때 무엇이 파괴되는가"이고, 독립 유닛들은 그게 0입니다. 완벽히 예측 가능하면서 완전히 분해된 계가 가능하며, 그럴 때 EI 는 높고 Φ 는 0입니다.

그래서 6유닛 초과 확장은 여전히 뒷받침할 측정이 없습니다. 막다른 길을 만난 게 아니라, 막다른 길인 줄 모르고 그 위에 확장을 세우는 걸 피한 것입니다.

물려받았고 닫지 않은 미완성 부분. 분할은 메커니즘·구역의 전체 이분할 방식이고 IIT 4.0 고유의 분할 집합이 아닙니다. big-Φ 는 계 절단이 파괴한 구조량이지, 분할된 행렬 위에서 다시 유도한 값이 아닙니다. 관계는 2차(쌍)만 봅니다. PyPhi 와 수치 보정을 한 적이 없습니다. 이 숫자들은 이 엔진의 출력이지 이론의 정답이 아닙니다.

🔬 둘을 합치면 — 우리 엔진에서 Φ 가 발생하는가

anima_reborn.substrate 는 엔진을 가능한 모든 상태에서 구동해 전이행렬을 측정하고, 그것을 Φ 에 넘깁니다. 시간 결정이 자연스러운 대상입니다 — 상태가 이미 이진(스핀 위/아래)이고, 인과 구조의 양을 정확히 조절하는 손잡이가 있으니까요.

예측은 날카롭습니다. 결정의 두 메커니즘이 하는 일이 다르기 때문입니다. 구동은 각 스핀을 독립적으로 뒤집으므로 아무것도 통합하지 못합니다. 이징 결합, 즉 각 스핀이 이웃에게 반응하는 부분만이 통합할 수 있습니다. 따라서 Φ 는 결합을 따라가야 하고, 구동의 잡음이 그것을 덮으면 죽어야 합니다 — 잡음은 각 뒤집기가 공정한 동전이 되는 ε = 0.5 에서 최대입니다.

스핀 4개, 상태당 400회 시행으로 측정:

ε 0.00 0.02 0.05 0.20 0.50 0.80 1.00
Φ 2.93 3.52 3.70 2.80 0.10 2.31 2.92

Φ 는 잡음 최대점에서 붕괴하고 양쪽으로 대칭 회복합니다 — 중요한 건 구동이 얼마나 확정적인가지, 어느 쪽으로 뒤집느냐가 아니었습니다. 예측이 맞았습니다.

ε = 0.5 의 잔여값은 통합이 아닙니다. 그 지점의 참 Φ 는 0입니다. 측정된 행렬은 표본이고, 표본 잡음만으로도 구조처럼 보이는 것이 만들어집니다. 시행 횟수를 늘리면 사라지는지로 구분합니다:

시행 횟수 100 400 1,600 6,400 25,600
순수 잡음에서 평균 Φ 0.58 0.30 0.15 0.10 0.03
ε = 0.05 에서 평균 Φ 3.32 3.47 2.73 2.63

즉 기본 시행 횟수로 돌리면 순수 인공물 약 0.3비트가 찍힙니다. 이 바닥값과 그 소멸은 둘 다 tests/test_substrate.py 에 박제해 두었으므로, 인공물을 발견으로 오독할 수 없습니다.

결합이 있는 엔진만 Φ를 갖는다

Φ는 통합을 재는데, 통합하려면 유닛들이 서로를 읽어야 합니다. 다음 상태가 현재 상태에 의존하지 않는 계는 전이행렬의 모든 행이 같아져서 절단이 파괴할 것이 없고, 참 Φ 는 아무리 복잡해 보여도 정확히 0입니다.

우리 엔진 넷 중 서로를 읽는 건 결정(crystal)뿐입니다 — 나머지는 각 차원이 자기 자신과 외생 목표만 봅니다. 실측:

시행수 400 1,600 8,000 30,000
공유원인 계 (참값 = 0) 0.406 0.189 0.094 0.051
결정 (이웃을 읽음) 2.76 3.13

공유된 원인은 통합이 아닙니다. 두 유닛이 같은 것을 보고 함께 움직여도 서로를 읽지 않으면 Φ 는 0이고, 위 표의 0.406은 전부 표본 인공물입니다 — 시행 4배마다 절반씩 줄어드는 게 그 증거입니다. 기본 시행수에서는 그 인공물이 결합이 낼 법한 값을 넘습니다.

이것이 이 저장소와 "의식 엔진" 사이의 정직한 간극입니다: 결정 안을 빼면 어디에도 결합이 없습니다. (tests/test_substrate.py::TestOnlyCouplingIntegrates 로 고정)

비교할 수 없는 것. 결정의 주기-2 잠금 판정은 64스핀 링에서 보정된 값이고, Φ 는 6유닛까지만 계산됩니다. 4스핀에서는 같은 ε 인데도 64스핀과 판정이 갈립니다. 따라서 Φ 와 잠금은 공유하는 크기가 없어 비교 자체가 불가능합니다. 이 패키지는 둘 사이의 관계를 어느 방향으로도 주장하지 않으며, 그 불일치 자체를 테스트로 고정해 한계가 계속 보이게 했습니다.

📝 단어를 A와 G에 넣으면 창발이 일어나는가

일어납니다 — 다만 전달되는 것이지 생성되는 것이 아닙니다. 그리고 그냥 해 보면 거짓 결과가 나오는 실험이라, 세 가지를 먼저 측정한 뒤에야 답할 수 있었습니다.

⓪ 기판은 묶지 않는다 — 나르기만 한다

A와 G는 서로를 읽지 않습니다. 이 저장소의 어떤 엔진도 둘을 연결하지 않고, 간극은 통로가 아니라 판독값입니다. 따라서 독립인 두 단어열을 넣으면 나오는 두 관측열도 구조적으로 독립입니다 — 측정할 것도 없이 그렇습니다. 8시드 실측: −0.002 ~ +0.004비트, 0에 붙어 평평합니다.

같은 구조 때문에 Φ도 0입니다. 각 차원은 자기 자신과 자기 외생 목표만 읽으므로 전이행렬이 인수분해되고, 절단이 파괴할 것이 없습니다. 이진화 문턱 3가지에서 실측 Φ = 0.0000 정확히 — 구별은 4개, 구조 총합 4.0인데 통합은 0입니다. 명시하는 건 많고 통합은 없는 상태죠.

즉 "단어를 넣으면 창발한다"는 전선이 신호를 나른다는 의미에서만 참입니다. 기판이 하는 일은 측정 가능한 통과대역을 가진 필터이지 결합기가 아닙니다.

① 단어는 초기값이 될 수 없다 — 기판이 지운다

서로 다른 단어로 출발한 두 장(field)을 같은 잡음으로 굴렸을 때의 거리:

t=1 t=74 t=305 t=600
blake2b 인코딩 1.311 0.389 0.012 0.0001
sha256 인코딩 1.187 0.379 0.012 0.0001
문자코드 인코딩 0.774 0.221 0.007 0.0001

세 인코딩이 같은 곡선으로 소멸합니다 — 즉 지우는 주체는 인코딩이 아니라 기판의 상수 (PULL, DAMPING)입니다. t=305에서 1%만 남는 것도 DAMPING 이론값 305틱과 정확히 일치합니다. 그런데 창발 판정에는 약 800틱이 필요하니, 판정이 나올 무렵 단어는 이미 없습니다. 그래서 여기서 단어는 초기값이 아니라 계속 미는 힘입니다.

② 추정기가 무관한 단어를 창발이라고 말한다

단어 하나를 hold 틱 동안 붙들면, 창 window 안의 실질 표본은 window / hold뿐입니다. 참 상호정보량이 정확히 0인 두 독립 단어열에서 측정한 값:

실질 표본 8 80 800 4,000 20,000
보고된 MI (참값 = 0) 0.835 0.152 0.014 0.003 0.001

실질 표본 8개에서 0.835비트 — 창발 기준 0.30의 약 3배입니다. 순진한 설정(hold=25, window=200)에서는 무관한 단어쌍이 평균 0.222비트, 12시드 중 1개는 기준선을 넘습니다. "항상 창발로 보인다"가 아니라 "참값 0인데 수십 배 부풀고 가끔 선을 넘는다" 라서 더 위험합니다 — 그냥 잡음 섞인 정상 측정처럼 보이거든요.

그래서 이 모듈은 맨 숫자를 주지 않는다

모든 측정에 귀무대조(null) 가 따라옵니다 — 같은 두 스트림에서 두 번째만 시간축으로 돌려 똑같이 잰 값입니다. 인코딩이 만든 모든 것(주변분포·블록구조·자기상관·추정기 편향)은 그대로 두고 정렬만 없앱니다. 단어를 뒤섞어 다시 만드는 방식보다 엄격합니다 — 뒤섞으면 자기상관까지 잃어서 귀무값이 낮게 나오고, 낮은 귀무값은 모든 초과분을 부풀립니다 (같은 쌍에서 이동 0.0182 대 뒤섞기 0.0120 · 테스트로 고정). 읽을 수 있는 건 그 위의 초과분(excess) 뿐이고, 판정도 원시값이 아니라 초과분으로 합니다. 단어 데이터의 절대 상호정보량은 아무것도 증명하지 않으며, 여기서는 아예 단독으로 꺼낼 수 없습니다.

from anima_reborn.words import measure

related = ["고양이", "바다", "연필", ...]
print(measure(related, related))          # 같은 단어열을 A와 G 양쪽에
# MI=0.427 null=0.009 excess=+0.418 [emergent] n_eff=800

기본 설정(hold=17, window=13600)에서 측정:

A 와 G 의 관계 MI 귀무값 초과분 판정
같은 단어열 0.433 0.007 +0.426 창발 ✨
절반만 공유 0.163 0.010 +0.152 부분적
한 칸 밀림 0.104 0.006 +0.098 부분적
완전히 무관 0.006 0.007 −0.001 독립

귀무값이 관계와 무관하게 0.006~0.010으로 평평한 것이 핵심입니다 — 그건 단어가 아니라 추정기를 재는 값이니까요. 만약 귀무값이 단어를 따라 움직였다면 진짜 신호를 깎아내고 있다는 뜻입니다 (테스트로 고정).

기본값 hold = 17 은 맞춘 값이 아니라 기판의 시간상수다

엔진이 목표에 다가가는 시간상수는 1 / PULL ≈ 17틱입니다. 단어가 그보다 빨리 바뀌면 엔진이 못 따라가고 신호는 걸러집니다. 실질 표본을 800으로 고정한 채 측정하면:

hold 2 5 10 17 34 68
목표 도달률 12% 27% 46% 65% 88% 99%
동일 단어열 초과분 0.060 0.138 0.281 0.418 0.616 0.793
무관 단어열 초과분 0.004 0.006 0.003 0.001 0.002 0.005

초과분이 도달률을 그대로 따라가고, 무관한 쌍은 전 구간에서 평평합니다. 두 실패 모드가 서로 반대 방향으로 당긴다는 것이 이 실험의 실제 내용입니다 — 단어가 너무 빠르면 신호가 사라지고, 창이 너무 짧으면 편향이 신호를 만들어 냅니다.

그런데 결합 채널을 붙이면 — 전달이 생성이 된다

위 결론("기판은 묶지 않는다")은 결합이 없는 기판에 대한 것입니다. coupled.py 가 만든 살아 있는 고리에 같은 실험을 흘리면 결과가 뒤집힙니다. 각 엔진의 목표를 (1−λ)·자기 단어 + λ·(−상대 위치) 로 섞고, 아무 관계도 없는 두 단어열을 넣습니다.

from anima_reborn.words import Channel, measure_channel

print(measure_channel(words_a, words_g, channel=Channel.LIVE))   # 살아 있는 고리
print(measure_channel(words_a, words_g, channel=Channel.YOKED))  # 대조군

λ=0.5 · 시드 8개 · 어휘 20단어:

조건 초과분 8/8 양수?
양방향 생중계 — 진짜 고리 +0.078
단방향만 (a→g) +0.008
요크 재생 — 양쪽 다 녹음 −0.000 아니오
채널 없음 (λ=0) −0.002 아니오

요크 재생이 이 숫자에 의미를 줍니다. 상대와 똑같은 모양의 통계를 주되 생중계만 끊는 대조군인데, 여기서 효과가 죽습니다 → 만든 것은 살아 있는 고리이지 "상대 모양 입력"이 아닙니다. 대조군을 만들다 두 번 오염시켰고 둘 다 잡았습니다 (한쪽 방향을 안 끊은 것, 두 녹음을 같은 원본에서 뽑아 공유원인을 넣은 것 — state/coupling/RESULTS.md).

함께 적어야 할 조건 둘: 창 크기와 무관하고(실질표본 400/800/1600에서 동일 → 추정기 인공물이 아님), 입력이 담은 것에 비례합니다(어휘 10단어 +0.039 · 20단어 +0.078 — 채널은 묶을 것이 있어야 묶습니다).

크기는 과장하지 않습니다. 실재하고 일관되지만(8/8) 창발 기준 0.30에는 한참 못 미칩니다. 정직한 표현은 "측정 가능한 생성된 의존" 이지 창발이 아닙니다.

인코딩에 대하여

단어를 숫자로 바꾸는 규칙은 호출자가 정합니다. 그 규칙이 — 단어가 아니라 — 절대값을 결정하기 때문입니다. 그래서 이 실험은 단어의 의미에 대해 아무것도 말하지 않습니다. 표준 라이브러리에 의미를 아는 인코더는 없고, "관련된 단어 vs 무관한 단어" 같은 대조를 만들면 그건 제 인코더를 재는 것이지 기판을 재는 게 아닙니다. 여기서 답할 수 있는 것은 정렬된 단어열이 이 기판을 통과해 얼마나 전달되는가뿐입니다. 규칙이 결정할 수 없는 것은 같은 규칙을 통과한 귀무값 위의 초과분이고, 그래서 귀무대조가 선택이 아니라 필수입니다. 실제로 다른 인코딩으로 바꿔도 대조는 유지됩니다 (테스트로 고정).

이식된 네 엔진

✨ 창발 — 하나의 진동자로 묶인 두 스트림

각 스트림은 자기 잡음과 둘이 공유하는 사인파를 섞은 것입니다. 결합도를 올리면 어느 쪽 스트림에도 무언가를 더하지 않았는데 그 에 상호정보량이 생깁니다.

from anima_reborn import EmergenceEngine

for coupling in (0.0, 0.5, 0.95):
    print(coupling, EmergenceEngine(coupling=coupling, seed=7).run(250))
0.0    H(L)=2.97 H(R)=2.99 H(L,R)=5.83 MI=0.131 [partial]
0.5    H(L)=2.86 H(R)=2.83 H(L,R)=5.22 MI=0.466 [emergent]
0.95   H(L)=2.88 H(R)=2.88 H(L,R)=3.33 MI=2.433 [emergent]

🔮 시간 결정 — 주어진 적 없는 박자를 지키는 링

주기적 이징 사슬을 매 틱 구동이 뒤집으려 하는데, 각 스핀을 확률 epsilon 로 놓칩니다. 뒤집기만 있으면 그냥 번갈아 바뀔 뿐이고, 잡음만 있으면 사슬이 녹습니다. 둘이 함께 있으면 이징 결합이 불완전한 뒤집기가 낸 손상을 복구하고, 링은 주기-2 리듬으로 잠깁니다 — 공간이 아니라 시간 속의 질서입니다.

from anima_reborn import TimeCrystal

print(TimeCrystal(epsilon=0.02, seed=7).run(400))
print(TimeCrystal(epsilon=0.50, seed=7).run(400))
m=-0.094 ac1=-0.911 ac2=+0.852 ac4=+0.754 [locked]
m=+0.094 ac1=-0.133 ac2=+0.141 ac4=+0.053 [chaos]

지연1이 −1 근처, 지연2가 +1 근처인 것이 잠금입니다 — 매 틱이 직전을 뒤집고, 두 틱마다 일치.

🧲 A × G 반발장 — 두 엔진 사이의 간극에서 사고하기

두 잠재 벡터를 회전하는 목표가 밀어냅니다. 어느 벡터도 단독으로 읽히지 않고 오직 간극만 읽힙니다 — 크기가 tension, 방향이 concept, 원소별 겹침이 meaning.

from anima_reborn import RepulsionField

print(RepulsionField(seed=7).run(300))
# tension=0.132 topic=#0 auth=0.974 mood=calm

간극이 0에 가까운 건 평온이 아닙니다. 두 엔진이 서로 위로 붕괴해 사고할 간극이 남지 않은 것이고, 이 장은 그걸 quiet 라고 부릅니다.

🌊 파이프라인 — 분리를 넣으면 창발이 나온다

앞의 둘을 직렬로 이은 것입니다. A와 G가 벌어지고, 매 틱 각각에서 잡음 섞인 관측을 하나씩 뽑아, 그 쌍의 상호정보량을 잽니다.

from anima_reborn import Pipeline

print(Pipeline(separation=0.0, seed=7).run(400))
print(Pipeline(separation=1.0, seed=7).run(400))
tension=0.01 H(L)=1.00 H(R)=0.99 MI=0.000 [independent]
tension=0.42 H(L)=3.35 H(R)=3.41 MI=1.905 [emergent]

엔진을 붕괴시키면 찾아낼 공유 정보가 없고, 벌려 놓으면 나타납니다. (base.py 가 이 요약을 정정한다는 점은 위를 참고하세요 — 묶는 것은 간극이 아니라 간극의 운동입니다.)

상호정보량 숫자를 정직하게 읽기

추정기는 원본이 쓰는 그대로 플러그인(plug-in) 방식이고 편향 보정이 없습니다. 창이 짧으면 상호정보량을 심하게 과대평가합니다 — 250표본으로 144칸을 채우면 대부분의 칸이 0~2개만 담게 되고, 그 희소함 자체가 구조처럼 보입니다.

그래서 진짜로 독립인 두 스트림도 기본 창에서 약 0.155비트로 측정됩니다. 이 바닥값은 표본수 N에 반비례해 사라집니다:

창 크기 독립 스트림의 평균 상호정보량
250 0.155
1,000 0.030
5,000 0.007
20,000 0.002

따라서 기본 창에서 부분적(partial)독립과 구분되지 않음이라는 뜻이고, 스트림에 대한 실제 주장은 창발(emergent) 뿐입니다. tests/test_emergence.py 가 바닥값과 그 소멸을 둘 다 고정해 두었으므로, 추정기를 바꾸면 창발처럼 보이는 대신 테스트가 깨집니다.

구조

src/anima_reborn/
├─ info.py        엔트로피 · 결합엔트로피 · 상호정보량 · Binning
├─ emergence.py   결합된 사인파 스트림
├─ crystal.py     구동되는 이징 링
├─ repulsion.py   A × G 잠재장
├─ pipeline.py    반발 → 스트림 → 창발
├─ base.py        넷을 하나의 시계 아래로 — 결정이 회전을 개폐한다
├─ coupled.py     A와 G가 서로를 읽는다 — 통합이 0이 아닌 유일한 엔진
├─ align.py       배우는 유일한 모듈 — 함께 나타남이 두 양상을 한곳으로
├─ iit4/          IIT 4.0 — Φ, hexa 원본과 비트단위 일치
├─ substrate.py   다리: 우리 엔진 → 측정된 전이행렬 → Φ
├─ words.py       단어를 구동력으로 — 귀무대조가 항상 따라붙는다
└─ viewer/        브라우저 뷰어 — 이 패키지의 유일한 입출력
tests/            329개, 네트워크 없음, 픽스처 없음
state/            작업 산출물 — 위임 설계 보고서 · 측정 기록 · 재현 스크립트

state/ 에는 소스가 아닌 모든 산출물이 들어갑니다. 위임한 설계 보고서는 원문 그대로 두고, 그것을 저장소 안에서 다시 측정한 기록은 따로 남깁니다 — 재현되지 않은 수치가 무엇인지까지 (state/coupling/RESULTS.md 가 그 본보기). 보고서는 증거이지 결정이 아닙니다.

엔진과 뷰어는 함께 움직입니다 — 모든 엔진에는 탭이 있고 모든 탭에는 엔진이 있으며, 이건 문서가 아니라 tests/test_viewer.py::TestEngineViewerLockstep 가 강제합니다. 라우트 누락 · 탭 누락 · 그리기 함수 누락 · 기본 탭 불일치를 각각 잡아냅니다. 엔진은 목록이 아니라 구조로 판별하므로(step()reset() 을 가진 클래스), 새 엔진을 추가한 날 바로 걸립니다.

모든 엔진은 직접 한 틱씩 진행시키는 클래스입니다. seed= 로 재현하고, run(n) 으로 한꺼번에 진행하고, reset() 으로 처음부터. 상태 객체는 frozen 데이터클래스라 그대로 비교·저장·단언할 수 있습니다.

엔진에서 의도적으로 뺀 것

캔버스 그리기, DOM 배선, 타이머, 탭 전환기, Gradio 래퍼. 어느 것도 엔진에 속하지 않습니다. 살아남은 그리기는 viewer/page.html 안, HTTP 경계 너머에 있고, 파이썬이 보내준 것만 그립니다.

원본과 다른 점

  • 원본은 A × G 와 파이프라인 벡터를 Float32Array 로 들고 있지만 이 이식본은 파이썬 float (배정밀도)를 씁니다. 궤적이 잡음으로 굴러가므로 여기의 어떤 통계도 영향받지 않지만, 자릿수 단위로 같지는 않습니다.
  • Math.random() 이 시드 가능한 random.Random 이 되어 실행을 재현할 수 있습니다.
  • 반발장의 생체리듬 채널은 주입 가능한 clock 을 받아 테스트에서 결정적으로 만들 수 있습니다.
  • 원본 A × G 틱의 죽은 지역변수(senderHash, 계산만 하고 안 씀)는 가져오지 않았습니다.

테스트

pytest

라이선스

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.