2026年6月13日土曜日

Atraの視覚と聴覚とテキストで複合想起 1 デモcode

 デモcode

<!DOCTYPE html>から</html>までなぞって下のファイル名でhtmlファイルを作ってください。 (GitHubとか使う気ないので、めんどくさいけど我慢してね)


atra_integrated_associatron_visual_audio_text_v9.html

####################  html  ######################


<!DOCTYPE html> <!-- =============================================================================== Atra Integrated Associatron Visual / Audio / Text Demo Atra 統合アソシアトロン 視覚・聴覚・テキスト デモ Copyright (c) 2026 C-sideLaboratory Yukihiro Watanabe.

All rights reserved. 無断転載、無断再配布、無断改変配布、商用利用を禁じます。 Unauthorized copying, redistribution, modified redistribution, or commercial use is prohibited. This file is a local research demonstration for Atra / Associatron experiments. このファイルは Atra / Associatron 実験用のローカル研究デモです。 Important copyright and data-use rule: - Do not use third-party copyrighted images, music, voices, videos, books, articles, or other protected works without permission. - Do not distribute recorded camera/microphone data from other people without consent. - Do not present this demo as a general-purpose recognition AI or dataset collector. - This demo should be used only with data that the experimenter has the right to use. 重要な著作権・データ利用ルール: - 第三者の著作物である画像、音楽、声、動画、書籍、記事などを無断で使わないこと。 - 他人のカメラ映像・マイク音声を同意なく記録・配布しないこと。 - このデモを一般的な認識AIやデータ収集装置として扱わないこと。 - 実験者が利用権限を持つデータだけを使うこと。 This notice must remain in this file. この表示は削除しないこと。 =============================================================================== --> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Atra Integrated Demo v10 - Associatron / Visual Traces / Waveform / Spectrogram / Text Recall</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> /* 日本語: Atra統合デモの画面色。 背景は白とベージュ、文字は濃いブルーとグレーを混ぜた落ち着いた色にする。 English: Screen colors for the Atra integrated demo. The background uses white and beige. Text uses dark blue-gray tones. */ :root { --bg: #f7f1e6; --panel: #fffdf8; --panel2: #f1eadc; --ink: #23384f; --muted: #68717c; --line: #d8cdbb; --accent: #2f4f6f; --trace: #fff1b8; } .copyrightBox { border: 1px solid #d7c2a3; background: #fff8ec; color: #33445f; padding: 12px 14px; border-radius: 12px; margin: 12px 0 18px; font-size: 13px; line-height: 1.65; } body { margin: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--ink); } header { padding: 20px 24px 8px; } h1 { margin: 0 0 8px; font-size: 24px; color: var(--ink); } .lead { margin: 0; color: var(--muted); line-height: 1.7; max-width: 1220px; font-size: 14px; } main { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 16px; padding: 16px 24px 24px; } section { background: var(--panel); border: 1px solid var(--line); border-radius: 16px; padding: 14px; box-shadow: 0 2px 8px rgba(40, 30, 20, 0.04); } h2 { margin: 0 0 10px; font-size: 17px; color: var(--accent); } h3 { margin: 16px 0 8px; font-size: 14px; color: var(--accent); } button { border: 1px solid var(--accent); color: var(--accent); background: #fff; border-radius: 10px; padding: 8px 12px; margin: 4px 4px 4px 0; cursor: pointer; font-size: 14px; } button:hover { background: var(--panel2); } /* 日本語: Learn と「一旦記憶を消す」は、操作上の区切りが大きい。 そのため薄いピンクにして、他の通常ボタンと区別する。 English: Learn and "clear temporary trace" are important operation boundaries. They use pale pink to distinguish them from ordinary buttons. */ .pinkButton { background: #fdecef; border-color: #d89aa6; color: #6f3b46; } .pinkButton:hover { background: #f9dce2; } textarea, input { border: 1px solid var(--line); border-radius: 10px; background: #fff; color: var(--ink); padding: 9px 10px; font: inherit; box-sizing: border-box; } textarea { width: 100%; min-height: 86px; resize: vertical; } input { width: 100%; margin-top: 8px; } .note { background: #fbf7ef; border: 1px solid var(--line); border-radius: 12px; padding: 10px; color: var(--muted); font-size: 13px; line-height: 1.65; margin: 8px 0 12px; } .stage { position: relative; width: 640px; max-width: 100%; aspect-ratio: 4 / 3; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: #101827; } #video { /* 日本語: 実際のカメラ映像。visualCanvas より下に置く。 English: The actual camera image. It is placed under visualCanvas. */ position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; background: #101827; z-index: 1; } #visualCanvas { /* 日本語: これは映像の上に重ねる「枠だけを描くキャンバス」です。 背景を持たせると、下のvideo映像を隠してしまう。 そのため background は transparent、border は none にする。 English: This canvas is only for drawing boxes over the video. If it has a background, it hides the video underneath. Therefore its background must be transparent and border must be none. */ position: absolute; inset: 0; width: 100%; height: 100%; background: transparent; border: none; z-index: 2; pointer-events: none; } canvas { background: #fff; border: 1px solid var(--line); border-radius: 12px; max-width: 100%; } .wideCanvas { width: 100%; height: 130px; } .spectrogramCanvas { width: 100%; height: 190px; } .row3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-top: 10px; } .meter { background: var(--panel2); border-radius: 10px; padding: 10px; color: var(--muted); font-size: 13px; } .meter strong { display: block; color: var(--ink); font-size: 18px; margin-top: 4px; } pre { white-space: pre-wrap; word-break: break-word; background: #fff; border: 1px solid var(--line); border-radius: 12px; padding: 10px; max-height: 440px; overflow: auto; color: var(--ink); font-size: 12px; } .math { font-family: "Times New Roman", serif; color: var(--ink); background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 10px; line-height: 1.5; font-size: 15px; } .memoryItem { border: 1px solid var(--line); border-radius: 12px; padding: 9px; background: #fff; margin-top: 8px; color: var(--muted); font-size: 13px; line-height: 1.5; } .memoryItem b { color: var(--accent); } @media (max-width: 960px) { main { grid-template-columns: 1fr; padding: 12px; } header { padding: 16px 12px 4px; } } </style> </head> <body> <header> <h1>Atra Integrated Demo v10</h1> <div class="copyrightBox"> <b>Copyright / 著作権</b><br> Copyright © 2026 Yukihiro Watanabe. All rights reserved.<br> 無断転載・無断再配布・無断改変配布・商用利用を禁じます。<br><br> <b>Use rule / 利用ルール</b><br> このデモでは、第三者の著作物である画像・音声・動画・文章を無断で使わないこと。<br> Use only camera, microphone, text, and other data that the experimenter has the right to use. </div> <p class="lead"> Atraの目・聴覚・収束を1つにした単体デモ。映像は <b>visual traces</b> として差分枠で追い、音声は <b>waveform</b><b>spectrogram / voiceprint</b> で観察し、テキストも同じ場の <b>text_delta</b> として Learn / Recall する。 画像認識・顔認識・音声認識・文字起こし・正解ラベル化はしない。 <br> Integrated standalone demo for Atra visual traces, auditory waveform, spectrogram / voiceprint, text cue recall, and Associatron-style convergence. </p> </header> <main> <section> <h2>1. Atra Eye / Visual Traces / Atraの目</h2> <div class="note"> <b>日本語:</b> 映像そのものを覚えるのではなく、フレーム間の差分から「動いた場所」を枠として出します。 枠は人・顔・物体名の認識ではありません。<b>visual traces</b> の観察表示です。この版では視覚差分を12×8に上げています。 <br> <b>English:</b> This does not remember raw video. It extracts changed regions between frames and draws boxes. Boxes are not person, face, or object recognition. They are observer display for <b>visual traces</b>. This version uses a 12×8 visual delta grid. </div> <button onclick="startCamera()">Start camera / カメラ開始</button> <button onclick="stopCamera()">Stop camera / カメラ停止</button> <div class="stage"> <video id="video" autoplay muted playsinline></video> <canvas id="visualCanvas" width="640" height="480"></canvas> </div> <h3>Visual delta map / 視覚差分マップ</h3> <canvas id="visualDeltaCanvas" width="640" height="160" class="wideCanvas"></canvas> <h2>2. Atra Auditory Pre-stage / 聴覚前段</h2> <div class="note"> <b>日本語:</b> ここでは文字起こしをしません。音声を「言葉」として理解せず、<b>waveform</b><b>spectrogram / voiceprint</b> を観察します。 Atraに渡すのは音量、アタック、周波数分布の粗い auditory_delta です。 <br> <b>English:</b> No transcription. Sound is not treated as words. The demo observes <b>waveform</b> and <b>spectrogram / voiceprint</b>. Atra receives only rough auditory_delta such as energy, attack, and frequency distribution. </div> <button onclick="startMic()">Start mic / マイク開始</button> <button onclick="stopMic()">Stop mic / マイク停止</button> <h3>Waveform / 波形</h3> <canvas id="waveCanvas" width="640" height="130" class="wideCanvas"></canvas> <h3>Spectrogram / Voiceprint / 声紋</h3> <canvas id="spectrogramCanvas" width="640" height="190" class="spectrogramCanvas"></canvas> </section> <section> <h2>3. Text Cue / テキストcue</h2> <div class="note"> <b>日本語:</b> テキストは入力できます。これは正解ラベルではなく、同じ場に入る <b>text_delta</b> です。 Learn時に直近8秒の映像・音とテキストを同時に覚えます。あとでテキストだけ入力して Recall することもできます。 <br> <b>English:</b> Text can be typed. It is not a correct label, but <b>text_delta</b> in the same field. On Learn, visual and audio traces from the recent 8 seconds are stored together with text. Later, text alone can be used as a cue for Recall. </div> <textarea id="textInput" placeholder="ここにテキストを打つ。例: apple / mama / warm / 危ない / 音がした / 赤いもの"></textarea> <input id="humanNote" placeholder="human note / 人間用メモ。Atra内部の正解ではない"> <button onclick="sampleField()">Sample current field / 現在場を採取</button> <button class="pinkButton" onclick="learnField()">Learn / 学習</button> <button onclick="recallMixedCue()">Recall from mixed cue / 混合cueから想起</button> <div class="note"> <b>Learn rule / 学習操作:</b><br> 1つの経験につき Learn は1回だけ押す。連打すると同じ痕跡が強くなりすぎる。<br> Press Learn once per experience. Repeated pressing over-strengthens the same trace.<br> Recall does not stop live visual/audio sensing. / Recallしてもライブの視覚・聴覚センサーは止めない。 </div> <button onclick="recallTextOnly()">Recall from text only / テキストだけで想起</button> <button class="pinkButton" onclick="clearSensorCue()">一旦記憶を消す / Clear temporary sensory trace</button> <button onclick="resetMemory()">Reset / 初期化</button> <div class="row3"> <div class="meter">visual traces / 視覚痕跡<strong id="visualMeter">0.000</strong></div> <div class="meter">audio energy / 音圧<strong id="audioMeter">0.000</strong></div> <div class="meter">text pressure / 文字圧<strong id="textMeter">0.000</strong></div> </div> <h3>Learn readiness / 学習準備状態</h3> <div class="row3"> <div class="meter">visual buffer / 視覚バッファ<strong id="visualBufferMeter">empty</strong></div> <div class="meter">audio buffer / 聴覚バッファ<strong id="audioBufferMeter">empty</strong></div> <div class="meter">text buffer / テキスト<strong id="textBufferMeter">empty</strong></div> </div> <h2>4. Associatron / アソシアトロン</h2> <div class="math"> Associatron memory matrix / アソシアトロン記憶行列:<br> T<sub>ij</sub> = Σ x<sub>i</sub>x<sub>j</sub>, &nbsp; T<sub>ii</sub> = 0 <br><br> Cue recall / cueからの想起:<br> y = clamp(x<sub>cue</sub>, sign(Tx<sub>cue</sub>)) <br><br> Energy of the memory field / 記憶場のエネルギー:<br> V = − 1/2 Σ T<sub>ij</sub>x<sub>i</sub>x<sub>j</sub> </div> <h2>5. Log / Recall</h2> <pre id="log">Waiting. / 待機中。</pre> <div id="memoryList"></div> </section> </main> <script> /* ================================================================================ Atra Integrated Demo ================================================================================ 日本語: これはAtra本体ではなく、Atraの3つの前段を1つにまとめたデモである。 1. Atraの目: カメラ映像からフレーム差分を取り、動いた領域に枠を出す。 この枠を visual traces として表示する。 これは物体認識ではない。顔認識でもない。名前も付けない。 2. Atraの聴覚: マイク音声から waveform と spectrogram / voiceprint を描く。 これは音声認識ではない。文字起こしでもない。感情認識でもない。 3. Atraの収束: visual_delta + auditory_delta + text_delta を同時に1つの場としてLearnする。 text_delta も同じ場に入るので、テキストだけでもcueとしてRecallできる。 English: This is not Atra itself. It is an integrated demo of three pre-stages. 1. Atra Eye: It calculates frame differences from camera video and draws boxes on moving regions. These boxes are displayed as visual traces. This is not object recognition, face recognition, or labeling. 2. Atra Auditory: It draws waveform and spectrogram / voiceprint from microphone sound. This is not speech recognition, transcription, or emotion recognition. 3. Atra Convergence: visual_delta + auditory_delta + text_delta are learned together as one field. Since text_delta is part of the same field, text alone can recall a trace. ================================================================================ */ const COPYRIGHT_NOTICE = { owner: "Yukihiro Watanabe", year: "2026", rights: "All rights reserved", jp: "無断転載・無断再配布・無断改変配布・商用利用を禁じます。", en: "Unauthorized copying, redistribution, modified redistribution, or commercial use is prohibited." }; const TRACE_SIZE = 192; const VISUAL_SIZE = 96; const AUDIO_SIZE = 64; const TEXT_SIZE = 32; /* 日本語: 視覚差分グリッド。 以前の 8×4 は粗すぎて、小さな顔・手・身体の動きが入りにくかった。 この版では 12×8 = 96 に上げる。 English: Visual difference grid. The previous 8×4 grid was too coarse for small face / hand / body movements. This version uses 12×8 = 96. */ const VISUAL_COLS = 12; const VISUAL_ROWS = 8; /* 日本語: GPUについて: この版はまだCanvas 2Dで動かす。12×8程度ならCPU負荷は軽い。 将来WebGLで差分計算をGPUへ移せるが、今はまず差分の安定性と見え方を優先する。 English: About GPU: This version still uses Canvas 2D. A 12×8 grid is light enough on CPU. Later, WebGL can move difference calculation to GPU, but stability and observability come first here. */ let T = makeZeroMatrix(TRACE_SIZE); let memories = []; let currentTrace = makeEmptyTrace(); let cameraStream = null; let micStream = null; let audioContext = null; let analyser = null; let timeData = null; let freqData = null; let prevGray = null; let visualCells = Array(VISUAL_SIZE).fill(0); let visualBoxes = []; let stableBoxMemory = []; /* 日本語: 直近の感覚痕跡を少しだけ保持する。 声や動きは一瞬で消えるので、Learnボタンを押した時には0になってしまうことがある。 それを避けるため、視覚と聴覚の痕跡を数秒だけ残す。 English: Keeps recent sensory traces for a short time. Voice and movement disappear quickly, so they may become zero by the time Learn is pressed. To avoid this, visual and auditory traces are kept for a few seconds. */ let heldVisualCells = Array(VISUAL_SIZE).fill(0); let heldVisualUntil = 0; let heldAudioBands = Array(AUDIO_SIZE).fill(0); let heldAudioUntil = 0; /* 日本語: 直近8秒間の感覚履歴。 Learnボタンを押した瞬間だけを見ると、声や動きがすでに消えていてtext onlyになりやすい。 そこで、直近8秒間の visual / audio をバッファに残し、Learn時にまとめて使う。 English: Sensory history for the last 8 seconds. If Learn only samples the exact button moment, voice or movement may already be gone and learning becomes text-only. Therefore visual / audio traces from the recent 8 seconds are buffered and used during Learn. */ let visualHistory = []; let audioHistory = []; const LEARN_WINDOW_MS = 8000; let lastAudioBands = Array(AUDIO_SIZE).fill(0); let animationStarted = false; let sensorCueCleared = false; const TRACE_HOLD_MS = 8000; /* 日本語: visual traces の安定化設定。 前の版では、差分セルを最大値で正規化していたため、カメラノイズや露出揺れでも 「一番大きいノイズ」が枠になってしまった。 この版では、絶対量が小さい差分では枠を出さない。 Atraの目として、動いていないものに trace を出さないための柵である。 English: Stabilization settings for visual traces. The previous version normalized cells by the maximum cell value, so camera noise or exposure jitter could become a box just because it was the strongest noise. This version does not draw boxes when the absolute amount of difference is small. This is a guardrail so Atra's eye does not create traces for things that are not really moving. */ const VISUAL_PIXEL_THRESHOLD = 24; // per-pixel difference threshold / 画素ごとの差分しきい値 const VISUAL_MIN_ACTIVE_RATIO = 0.006; // at least 1.8% active pixels / 画面の1.8%以上が動いた時だけ const VISUAL_MIN_CELL_ENERGY = 0.010; // absolute cell energy threshold / セル絶対量しきい値 const VISUAL_BOX_SCORE = 0.28; // normalized candidate threshold / 枠候補しきい値 const VISUAL_BOX_HOLD = 4; // hold frames for stable display / 枠表示を少し保持 const video = document.getElementById("video"); const visualCanvas = document.getElementById("visualCanvas"); const visualCtx = visualCanvas.getContext("2d", { willReadFrequently: true }); const visualDeltaCanvas = document.getElementById("visualDeltaCanvas"); const visualDeltaCtx = visualDeltaCanvas.getContext("2d", { willReadFrequently: true }); const waveCanvas = document.getElementById("waveCanvas"); const waveCtx = waveCanvas.getContext("2d", { willReadFrequently: true }); const spectrogramCanvas = document.getElementById("spectrogramCanvas"); const spectrogramCtx = spectrogramCanvas.getContext("2d", { willReadFrequently: true }); async function startCamera() { /* 日本語: カメラを開始する。 HTMLのonclickから直接呼ぶので、ボタン接続の失敗を避ける。 English: Starts the camera. Called directly from HTML onclick to avoid button binding failure. */ try { log("Starting camera... / カメラ開始中..."); if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { throw new Error("getUserMedia is not available."); } cameraStream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 640 }, height: { ideal: 480 } }, audio: false }); video.srcObject = cameraStream; video.muted = true; video.playsInline = true; await new Promise(resolve => { if (video.readyState >= 1) resolve(); else video.onloadedmetadata = resolve; }); await video.play(); /* 日本語: カメラ開始後、待機表示で塗ったoverlayを必ず一度消す。 これで「枠用キャンバスだけが残って映像が見えない」状態を避ける。 English: After the camera starts, clear the overlay that may have been filled by the waiting display. This prevents the overlay from hiding the video. */ visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); const track = cameraStream.getVideoTracks()[0]; log( "Camera OK / カメラOK\n" + "device: " + (track ? track.label : "(unknown)") + "\n" + "videoWidth: " + video.videoWidth + "\n" + "videoHeight: " + video.videoHeight + "\n\n" + "visual traces will appear as boxes when movement is detected.\n" + "動きがあると visual traces が枠として表示されます。" ); sensorCueCleared = false; startMainLoop(); } catch (err) { log( "Camera ERROR / カメラエラー\n" + err.name + ": " + err.message + "\n\n" + "Check browser permission and Windows camera privacy settings.\n" + "ブラウザ権限とWindowsのカメラ設定を確認してください。" ); } } function stopCamera() { if (cameraStream) { cameraStream.getTracks().forEach(t => t.stop()); cameraStream = null; } prevGray = null; visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); drawWaitingVisual(); log("Camera stopped. / カメラ停止。"); } async function startMic() { /* 日本語: マイクを開始する。 waveform と spectrogram / voiceprint を描く。 English: Starts the microphone. Draws waveform and spectrogram / voiceprint. */ try { log("Starting mic... / マイク開始中..."); if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { throw new Error("getUserMedia is not available."); } micStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true }); audioContext = new (window.AudioContext || window.webkitAudioContext)(); if (audioContext.state === "suspended") { await audioContext.resume(); } const source = audioContext.createMediaStreamSource(micStream); analyser = audioContext.createAnalyser(); analyser.fftSize = 1024; timeData = new Uint8Array(analyser.fftSize); freqData = new Uint8Array(analyser.frequencyBinCount); source.connect(analyser); const track = micStream.getAudioTracks()[0]; log( "Mic OK / マイクOK\n" + "device: " + (track ? track.label : "(unknown)") + "\n\n" + "waveform and spectrogram / voiceprint are active.\n" + "波形と声紋が動作します。" ); sensorCueCleared = false; startMainLoop(); } catch (err) { log( "Mic ERROR / マイクエラー\n" + err.name + ": " + err.message + "\n\n" + "Check browser permission and Windows microphone privacy settings.\n" + "ブラウザ権限とWindowsのマイク設定を確認してください。" ); } } function stopMic() { if (micStream) { micStream.getTracks().forEach(t => t.stop()); micStream = null; } if (audioContext) { audioContext.close(); audioContext = null; } analyser = null; timeData = null; freqData = null; lastAudioBands = Array(AUDIO_SIZE).fill(0); waveCtx.clearRect(0, 0, waveCanvas.width, waveCanvas.height); spectrogramCtx.clearRect(0, 0, spectrogramCanvas.width, spectrogramCanvas.height); drawWaitingAudio(); log("Mic stopped. / マイク停止。"); } function startMainLoop() { if (animationStarted) return; animationStarted = true; requestAnimationFrame(loop); } function loop() { updateVisualTracking(); updateAudioViews(); sampleField(false); updateLearnReadiness(); requestAnimationFrame(loop); } function updateVisualTracking() { /* 日本語: 映像を直接表示するのは video 要素。 visualCanvas はその上に重ね、visual traces の枠だけを描く。 重要: ノイズだけで枠を出さない。 カメラは暗所ノイズ、露出補正、圧縮ノイズ、液晶のちらつきでも微小差分を出す。 そのため「最大差分セル」をそのまま枠にしてはいけない。 English: The video element displays the camera image. visualCanvas is placed over it and draws only visual trace boxes. Important: Do not draw boxes from noise alone. Cameras produce tiny differences from sensor noise, auto exposure, compression noise, or screen flicker. Therefore the strongest cell must not automatically become a visual trace. */ visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); if (!cameraStream || video.readyState < 2 || video.videoWidth === 0) { if (!cameraStream) drawWaitingVisual(); if (!sensorCueCleared) visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; stableBoxMemory = []; drawVisualDeltaMap(); return; } if (sensorCueCleared) { visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; stableBoxMemory = []; drawVisualDeltaMap(); return; } const smallW = 160; const smallH = 120; const off = updateVisualTracking.off || document.createElement("canvas"); off.width = smallW; off.height = smallH; updateVisualTracking.off = off; const offCtx = off.getContext("2d", { willReadFrequently: true }); offCtx.drawImage(video, 0, 0, smallW, smallH); const img = offCtx.getImageData(0, 0, smallW, smallH); const gray = new Uint8Array(smallW * smallH); for (let i = 0, p = 0; i < img.data.length; i += 4, p++) { gray[p] = (img.data[i] * 0.299 + img.data[i + 1] * 0.587 + img.data[i + 2] * 0.114) | 0; } if (!prevGray) { prevGray = gray; drawVisualDeltaMap(); drawNoTraceMessage("stabilizing..."); return; } const cols = VISUAL_COLS; const rows = VISUAL_ROWS; const cellRaw = Array(cols * rows).fill(0); const cellActive = Array(cols * rows).fill(0); let activePixels = 0; let totalStrongDiff = 0; for (let y = 0; y < smallH; y++) { for (let x = 0; x < smallW; x++) { const idx = y * smallW + x; const d = Math.abs(gray[idx] - prevGray[idx]); /* 日本語: 画素単位の小さな揺れは捨てる。 ここで捨てないと、照明や圧縮ノイズが trace になる。 English: Tiny pixel-level changes are ignored. Without this, lighting or compression noise becomes a trace. */ if (d > VISUAL_PIXEL_THRESHOLD) { activePixels += 1; totalStrongDiff += d; const cx = Math.min(cols - 1, Math.floor(x / (smallW / cols))); const cy = Math.min(rows - 1, Math.floor(y / (smallH / rows))); const ci = cy * cols + cx; cellRaw[ci] += d; cellActive[ci] += 1; } } } prevGray = gray; const activeRatio = activePixels / (smallW * smallH); /* 日本語: activeRatio が小さい時は、画面全体として動いていない。 その場合は、差分マップだけ薄く出し、枠は出さない。 English: If activeRatio is small, the screen is not really moving. In that case, only the delta map is updated faintly and no boxes are drawn. */ if (activeRatio < VISUAL_MIN_ACTIVE_RATIO) { /* 日本語: 枠として出すほど強くない動きでも、Learn用には弱いvisual bufferとして残す。 これにより、ノイズ枠は出さずに、実際の小さな動きは学習候補に残せる。 English: Even when movement is not strong enough to draw a box, keep it as a weak visual buffer for Learn. This avoids noise boxes while still preserving small real movement. */ if (activeRatio > 0.0015 && totalStrongDiff > 0) { const maxPossiblePerCellWeak = (smallW / cols) * (smallH / rows) * 255; const weakCells = cellRaw.map(v => clamp(v / maxPossiblePerCellWeak)); pushVisualHistory(weakCells, activeRatio, 0); } visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = decayStableBoxes([]); drawVisualBoxes(0, activeRatio, "no stable visual trace"); drawVisualDeltaMap(); return; } const maxPossiblePerCell = (smallW / cols) * (smallH / rows) * 255; const absoluteCells = cellRaw.map(v => v / maxPossiblePerCell); const maxCell = Math.max(...absoluteCells, 0.000001); /* 日本語: visualCells は表示用に0〜1へ寄せるが、枠判定には絶対量も使う。 ここが重要。正規化だけで枠を出すとノイズが枠になる。 English: visualCells are normalized for display, but box decisions also use absolute energy. This is important. If boxes use normalization alone, noise becomes a box. */ visualCells = absoluteCells.map(v => clamp(v / Math.max(maxCell, VISUAL_MIN_CELL_ENERGY))); const candidates = []; for (let i = 0; i < absoluteCells.length; i++) { const normalizedScore = visualCells[i]; const absoluteEnergy = absoluteCells[i]; if (absoluteEnergy >= VISUAL_MIN_CELL_ENERGY && normalizedScore >= VISUAL_BOX_SCORE) { const cx = i % cols; const cy = Math.floor(i / cols); candidates.push({ x: cx / cols, y: cy / rows, w: 1 / cols, h: 1 / rows, score: normalizedScore, absolute: absoluteEnergy, hold: VISUAL_BOX_HOLD }); } } candidates.sort((a, b) => (b.absolute + b.score) - (a.absolute + a.score)); visualBoxes = decayStableBoxes(candidates.slice(0, 4)); drawVisualBoxes(activeRatio, activeRatio, visualBoxes.length ? "" : "movement too weak"); drawVisualDeltaMap(); } function decayStableBoxes(newBoxes) { /* 日本語: 1フレームだけのノイズ枠を減らすための簡易保持。 新しい枠があるときは更新し、無いときは数フレームだけ前の枠を薄く残す。 ただし、今回の目的では「動いていないのに出続ける」ことを避けるため保持は短い。 English: Simple box holding to reduce one-frame noise. When new boxes exist, update them. When not, keep previous boxes only for a few frames. The hold is short to avoid showing boxes when nothing is moving. */ if (newBoxes.length > 0) { stableBoxMemory = newBoxes.map(b => ({ ...b, hold: VISUAL_BOX_HOLD })); return stableBoxMemory; } stableBoxMemory = stableBoxMemory .map(b => ({ ...b, hold: b.hold - 1 })) .filter(b => b.hold > 0); return stableBoxMemory; } function drawWaitingVisual() { visualCtx.fillStyle = "rgba(16, 24, 39, 0.90)"; visualCtx.fillRect(0, 0, visualCanvas.width, visualCanvas.height); visualCtx.fillStyle = "#ffffff"; visualCtx.font = "18px sans-serif"; visualCtx.fillText("waiting for camera...", 24, 42); visualCtx.font = "14px sans-serif"; visualCtx.fillText("カメラ開始を押してください", 24, 68); } function drawNoTraceMessage(message) { visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); visualCtx.fillStyle = "rgba(35, 56, 79, 0.50)"; visualCtx.fillRect(12, 12, 190, 28); visualCtx.fillStyle = "#fffdf8"; visualCtx.font = "14px sans-serif"; visualCtx.fillText(message, 22, 31); } function drawVisualBoxes(motion, activeRatio, message) { /* 日本語: visual traces の枠を描く。 ただし、動きが安定して検出された場合だけ枠を出す。 動いていない時は「no stable visual trace」と表示する。 English: Draws visual trace boxes. Boxes are drawn only when stable movement is detected. When nothing is moving, it shows “no stable visual trace”. */ visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); if (!visualBoxes || visualBoxes.length === 0) { visualCtx.fillStyle = "rgba(35, 56, 79, 0.50)"; visualCtx.fillRect(12, 12, 230, 50); visualCtx.fillStyle = "#fffdf8"; visualCtx.font = "14px sans-serif"; visualCtx.fillText(message || "no stable visual trace", 22, 32); visualCtx.fillText("active: " + activeRatio.toFixed(3), 22, 52); document.getElementById("visualMeter").textContent = "0.000"; return; } visualCtx.lineWidth = 3; visualCtx.strokeStyle = "rgba(255, 241, 184, 0.98)"; visualCtx.fillStyle = "rgba(35, 56, 79, 0.65)"; visualCtx.font = "15px sans-serif"; visualBoxes.forEach((b, index) => { const x = b.x * visualCanvas.width; const y = b.y * visualCanvas.height; const w = b.w * visualCanvas.width; const h = b.h * visualCanvas.height; visualCtx.strokeRect(x, y, w, h); visualCtx.fillRect(x, Math.max(0, y - 23), 132, 23); visualCtx.fillStyle = "#fffdf8"; visualCtx.fillText("visual trace " + (index + 1), x + 7, Math.max(17, y - 7)); visualCtx.fillStyle = "rgba(35, 56, 79, 0.65)"; }); /* 日本語: visual trace が出たら、その時の visualCells を少し保持する。 これで「動いてからLearnを押すまでの短い遅れ」を吸収する。 English: When visual traces appear, keep the current visualCells briefly. This absorbs the short delay between movement and pressing Learn. */ heldVisualCells = visualCells.slice(0, VISUAL_SIZE); heldVisualUntil = Date.now() + TRACE_HOLD_MS; pushVisualHistory(visualCells, motion, visualBoxes.length); document.getElementById("visualMeter").textContent = motion.toFixed(3); } function drawVisualDeltaMap() { visualDeltaCtx.clearRect(0, 0, visualDeltaCanvas.width, visualDeltaCanvas.height); visualDeltaCtx.fillStyle = "#fffdf8"; visualDeltaCtx.fillRect(0, 0, visualDeltaCanvas.width, visualDeltaCanvas.height); const cols = VISUAL_COLS; const rows = VISUAL_ROWS; const cw = visualDeltaCanvas.width / cols; const ch = visualDeltaCanvas.height / rows; for (let i = 0; i < visualCells.length; i++) { const v = visualCells[i]; const x = (i % cols) * cw; const y = Math.floor(i / cols) * ch; const shade = Math.floor(255 - v * 170); visualDeltaCtx.fillStyle = `rgb(${shade}, ${shade}, 255)`; visualDeltaCtx.fillRect(x, y, cw - 1, ch - 1); } } function updateAudioViews() { /* 日本語: waveform と spectrogram / voiceprint を更新する。 文字起こしや話者認識はしない。 English: Updates waveform and spectrogram / voiceprint. No transcription or speaker recognition. */ if (!analyser || !timeData || !freqData) { if (!analyser) drawWaitingAudio(); if (!sensorCueCleared) lastAudioBands = Array(AUDIO_SIZE).fill(0); return; } if (sensorCueCleared) { lastAudioBands = Array(AUDIO_SIZE).fill(0); return; } analyser.getByteTimeDomainData(timeData); analyser.getByteFrequencyData(freqData); drawWaveform(); drawSpectrogram(); updateAudioBands(); } function drawWaitingAudio() { waveCtx.fillStyle = "#fffdf8"; waveCtx.fillRect(0, 0, waveCanvas.width, waveCanvas.height); waveCtx.fillStyle = "#68717c"; waveCtx.font = "16px sans-serif"; waveCtx.fillText("waiting for mic... / マイク開始を押してください", 18, 38); spectrogramCtx.fillStyle = "#fffdf8"; spectrogramCtx.fillRect(0, 0, spectrogramCanvas.width, spectrogramCanvas.height); spectrogramCtx.fillStyle = "#68717c"; spectrogramCtx.font = "16px sans-serif"; spectrogramCtx.fillText("spectrogram / voiceprint will appear here / 声紋はここに出ます", 18, 38); } function drawWaveform() { /* 日本語: waveform / 波形。 音声の時間的な揺れを見る。言葉の意味は見ない。 English: waveform. Shows temporal vibration of sound. It does not read meaning. */ waveCtx.fillStyle = "#fffdf8"; waveCtx.fillRect(0, 0, waveCanvas.width, waveCanvas.height); waveCtx.strokeStyle = "#2f4f6f"; waveCtx.lineWidth = 2; waveCtx.beginPath(); for (let i = 0; i < timeData.length; i++) { const x = i / (timeData.length - 1) * waveCanvas.width; const y = (timeData[i] / 255) * waveCanvas.height; if (i === 0) waveCtx.moveTo(x, y); else waveCtx.lineTo(x, y); } waveCtx.stroke(); } function drawSpectrogram() { /* 日本語: spectrogram / voiceprint / 声紋。 左へ1px流し、右端に新しい周波数分布を描く。 強い周波数ほど濃く見える。 English: spectrogram / voiceprint. Scrolls left by 1px and draws the newest frequency distribution at the right edge. Stronger frequency energy appears darker. */ const w = spectrogramCanvas.width; const h = spectrogramCanvas.height; const old = spectrogramCtx.getImageData(1, 0, w - 1, h); spectrogramCtx.putImageData(old, 0, 0); for (let y = 0; y < h; y++) { const fi = Math.floor((1 - y / h) * (freqData.length - 1)); const v = freqData[fi] / 255; const shade = Math.floor(250 - v * 170); spectrogramCtx.fillStyle = `rgb(${shade}, ${shade + 2}, 255)`; spectrogramCtx.fillRect(w - 1, y, 1, 1); } } function updateAudioBands() { const bands = Array(AUDIO_SIZE).fill(0); const step = Math.floor(freqData.length / AUDIO_SIZE); let sum = 0; for (let b = 0; b < AUDIO_SIZE; b++) { let bandSum = 0; for (let i = b * step; i < Math.min(freqData.length, (b + 1) * step); i++) { bandSum += freqData[i]; } bands[b] = clamp((bandSum / Math.max(step, 1)) / 255); sum += bands[b]; } lastAudioBands = bands; const energy = sum / AUDIO_SIZE; /* 日本語: 聴覚痕跡も少し保持する。 「渡邉」と言ってからLearnを押すまでに音が消えても、直近の声紋が少し残る。 English: Auditory traces are also held briefly. Even if the sound disappears before Learn is pressed, the recent voiceprint remains for a short time. */ if (energy > 0.0015) { heldAudioBands = bands.slice(0, AUDIO_SIZE); heldAudioUntil = Date.now() + TRACE_HOLD_MS; pushAudioHistory(bands, energy); } document.getElementById("audioMeter").textContent = energy.toFixed(3); } function updateLearnReadiness() { /* 日本語: Learnを押す前に、何が学習に入る予定かを表示する。 これが empty のままなら、Learnしてもその要素は入らない。 English: Shows what will enter Learn before pressing Learn. If this stays empty, that modality will not enter learning. */ const now = Date.now(); visualHistory = visualHistory.filter(v => now - v.t <= LEARN_WINDOW_MS); audioHistory = audioHistory.filter(a => now - a.t <= LEARN_WINDOW_MS); const visualBox = document.getElementById("visualBufferMeter"); const audioBox = document.getElementById("audioBufferMeter"); const textBox = document.getElementById("textBufferMeter"); if (visualBox) { visualBox.textContent = visualHistory.length > 0 ? "ready" : "empty"; } if (audioBox) { audioBox.textContent = audioHistory.length > 0 ? "ready" : "empty"; } if (textBox) { const text = document.getElementById("textInput").value; textBox.textContent = text && text.length > 0 ? "ready" : "empty"; } } function pushVisualHistory(cells, motion, boxCount) { /* 日本語: visual traces の履歴を残す。 古いものは8秒を超えたら捨てる。 English: Keeps visual trace history. Old entries beyond 8 seconds are discarded. */ const now = Date.now(); visualHistory.push({ t: now, cells: cells.slice(0, VISUAL_SIZE), motion: motion, boxes: boxCount }); visualHistory = visualHistory.filter(v => now - v.t <= LEARN_WINDOW_MS); updateLearnReadiness(); } function pushAudioHistory(bands, energy) { /* 日本語: waveform / spectrogram 由来の聴覚履歴を残す。 古いものは8秒を超えたら捨てる。 English: Keeps auditory history derived from waveform / spectrogram. Old entries beyond 8 seconds are discarded. */ const now = Date.now(); audioHistory.push({ t: now, bands: bands.slice(0, AUDIO_SIZE), energy: energy }); audioHistory = audioHistory.filter(a => now - a.t <= LEARN_WINDOW_MS); updateLearnReadiness(); } function combineRecentVisual() { /* 日本語: 直近8秒のvisual tracesをまとめる。 最大値合成にすることで、一瞬出たtraceもLearnに入る。 English: Combines visual traces from the recent 8 seconds. Max-composition lets short visual traces enter Learn. */ const now = Date.now(); visualHistory = visualHistory.filter(v => now - v.t <= LEARN_WINDOW_MS); const out = Array(VISUAL_SIZE).fill(0); let boxes = 0; let motion = 0; visualHistory.forEach(v => { boxes = Math.max(boxes, v.boxes || 0); motion = Math.max(motion, v.motion || 0); for (let i = 0; i < VISUAL_SIZE; i++) { out[i] = Math.max(out[i], v.cells[i] || 0); } }); return { cells: out, boxes, motion, held: visualHistory.length > 0 }; } function combineRecentAudio() { /* 日本語: 直近8秒のauditory tracesをまとめる。 最大値合成にすることで、短い発声もLearnに入る。 English: Combines auditory traces from the recent 8 seconds. Max-composition lets short utterances enter Learn. */ const now = Date.now(); audioHistory = audioHistory.filter(a => now - a.t <= LEARN_WINDOW_MS); const out = Array(AUDIO_SIZE).fill(0); let energy = 0; audioHistory.forEach(a => { energy = Math.max(energy, a.energy || 0); for (let i = 0; i < AUDIO_SIZE; i++) { out[i] = Math.max(out[i], a.bands[i] || 0); } }); return { bands: out, energy, held: audioHistory.length > 0 }; } function sampleField(showLog = true, useRecentWindow = false) { /* 日本語: visual_delta + auditory_delta + text_delta を同時に採取し、 ひとつの双極パターン x にする。 English: Samples visual_delta + auditory_delta + text_delta together and converts them into one bipolar pattern x. */ /* 日本語: useRecentWindow=true の時は、直近8秒間の履歴を使う。 Learnはこのモードを使う。 Recallは現在cueを見るので、通常は現在値+短期保持を見る。 English: When useRecentWindow=true, the recent 8-second history is used. Learn uses this mode. Recall usually uses the current cue plus short hold. */ const now = Date.now(); let visual; let visualBoxesForTrace = visualBoxes.length; let visualHeldFlag = false; let visualMotionForTrace = 0; let auditory; let audioHeldFlag = false; let audioEnergyForTrace = 0; if (useRecentWindow) { const recentVisual = combineRecentVisual(); const recentAudio = combineRecentAudio(); visual = recentVisual.cells; visualBoxesForTrace = recentVisual.boxes; visualHeldFlag = recentVisual.held; visualMotionForTrace = recentVisual.motion; auditory = recentAudio.bands; audioHeldFlag = recentAudio.held; audioEnergyForTrace = recentAudio.energy; } else { visual = (now < heldVisualUntil) ? heldVisualCells.slice(0, VISUAL_SIZE) : ((visualBoxes && visualBoxes.length > 0) ? visualCells.slice(0, VISUAL_SIZE) : Array(VISUAL_SIZE).fill(0)); visualHeldFlag = now < heldVisualUntil; visualMotionForTrace = average(visual); auditory = (now < heldAudioUntil) ? heldAudioBands.slice(0, AUDIO_SIZE) : lastAudioBands.slice(0, AUDIO_SIZE); audioHeldFlag = now < heldAudioUntil; audioEnergyForTrace = average(auditory); } while (visual.length < VISUAL_SIZE) visual.push(0); while (auditory.length < AUDIO_SIZE) auditory.push(0); const text = makeTextDelta(document.getElementById("textInput").value); const merged = visual.concat(auditory, text); const pattern = toBipolar(merged); currentTrace = { visual_delta: visual, auditory_delta: auditory, text_delta: text, pattern: pattern, raw: { visual_boxes: visualBoxesForTrace, visual_motion: visualMotionForTrace, audio_energy: audioEnergyForTrace, text_pressure: average(text), visual_held: visualHeldFlag, audio_held: audioHeldFlag, used_recent_window: useRecentWindow } }; document.getElementById("visualMeter").textContent = currentTrace.raw.visual_motion.toFixed(3); document.getElementById("audioMeter").textContent = currentTrace.raw.audio_energy.toFixed(3); document.getElementById("textMeter").textContent = currentTrace.raw.text_pressure.toFixed(3); if (showLog) { log("Sampled current field. / 現在場を採取しました。\n\n" + summarizeTrace(currentTrace)); } return currentTrace; } function makeTextDelta(text) { /* 日本語: text_delta。 文字列を意味として読まず、文字コードと位置から粗い32値へ変換する。 これにより、テキストだけでも cue になる。 English: text_delta. Converts a string into 32 rough values using character codes and positions, without reading the meaning. This lets text alone become a cue. */ const out = Array(TEXT_SIZE).fill(0); const chars = Array.from(text || ""); if (chars.length === 0) return out; chars.forEach((ch, i) => { const code = ch.codePointAt(0); const idx1 = (code + i * 13) % TEXT_SIZE; const idx2 = (code * 7 + i * 5) % TEXT_SIZE; out[idx1] += 1.0; out[idx2] += 0.45; }); const max = Math.max(...out, 1); return out.map(v => clamp(v / max)); } function toBipolar(values) { const avg = average(values); return values.map(v => v >= avg ? 1 : -1); } function learnField() { /* 日本語: Learn時に、visual traces / waveform-spectrogram由来のauditory_delta / text_delta を 同時にアソシアトロン行列へ入れる。 English: On Learn, visual traces, auditory_delta from waveform/spectrogram, and text_delta are stored together into the Associatron matrix. */ const trace = sampleField(false, true); const note = document.getElementById("humanNote").value.trim(); const text = document.getElementById("textInput").value.trim(); associatronLearn(trace.pattern); memories.push({ id: Date.now(), note: note, text: text, trace: JSON.parse(JSON.stringify(trace)), learned_at: new Date().toLocaleString() }); renderMemoryList(); const modalityReport = makeModalityReport(trace); log( "Learned. / 学習しました。\n\n" + hardLearnWarning(trace) + modalityReport + "\n\n" + "Stored together / 同時に記憶:\n" + "- visual traces: " + (trace.raw.visual_motion > 0 ? "IN / 入った" : "ZERO / 入っていない") + "\n" + "- auditory waveform / spectrogram delta: " + (trace.raw.audio_energy > 0.002 ? "IN / 入った" : "ZERO / 入っていない") + "\n" + "- text_delta: " + (trace.raw.text_pressure > 0 ? "IN / 入った" : "ZERO / 入っていない") + "\n\n" + "Associatron memory matrix / アソシアトロン記憶行列:\n" + "Tij = Σ xi xj, Tii = 0\n\n" + summarizeTrace(trace) ); } function associatronLearn(x) { /* 日本語: アソシアトロン記憶行列への書き込み。 同時に立った痕跡 x の外積を T に加える。 T_ij += x_i x_j T_ii = 0 English: Writes into the Associatron memory matrix. It adds the outer product of the simultaneously active trace x to T. T_ij += x_i x_j T_ii = 0 */ for (let i = 0; i < TRACE_SIZE; i++) { for (let j = 0; j < TRACE_SIZE; j++) { if (i === j) T[i][j] = 0; else T[i][j] += x[i] * x[j]; } } } function recallMixedCue() { const seed = sampleField(false).pattern; runRecall(seed, "mixed cue / 混合cue"); } function recallTextOnly() { /* 日本語: テキストだけで想起する。 visual_delta と auditory_delta を0にし、text_deltaだけをcueにする。 English: Recall using text only. visual_delta and auditory_delta are set to zero, and only text_delta is used as cue. */ const text = makeTextDelta(document.getElementById("textInput").value); const visual = Array(VISUAL_SIZE).fill(0); const auditory = Array(AUDIO_SIZE).fill(0); const seed = toBipolar(visual.concat(auditory, text)); currentTrace = { visual_delta: visual, auditory_delta: auditory, text_delta: text, pattern: seed, raw: { visual_boxes: 0, visual_motion: 0, audio_energy: 0, text_pressure: average(text) } }; document.getElementById("visualMeter").textContent = "0.000"; document.getElementById("audioMeter").textContent = "0.000"; document.getElementById("textMeter").textContent = average(text).toFixed(3); runRecall(seed, "text only cue / テキストだけのcue"); } function actualCueName(trace, requestedName) { /* 日本語: ボタン名が mixed cue でも、実際に入っている要素が text だけなら mixed ではない。 ここで実際のcue構成を表示する。 English: Even if the button says mixed cue, it is not really mixed when only text is present. This function displays the actual cue composition. */ const parts = []; if (trace.raw.visual_motion > 0) parts.push("visual"); if (trace.raw.audio_energy > 0.002) parts.push("audio"); if (trace.raw.text_pressure > 0) parts.push("text"); if (parts.length === 0) return requestedName + " -> empty cue / 空cue"; if (parts.length === 1) return requestedName + " -> actually " + parts[0] + " only / 実際は" + parts[0] + "だけ"; return requestedName + " -> actually " + parts.join("+") + " / 実際は" + parts.join("+"); } function hardLearnWarning(trace) { /* 日本語: Learn時点で視覚・聴覚が入っていない場合、研究メモとしては重要なので強く表示する。 English: If visual/audio are missing at Learn time, show a strong warning because it matters for the experiment. */ const lines = []; const hasVisual = trace.raw.visual_motion > 0; const hasAudio = trace.raw.audio_energy > 0.002; const hasText = trace.raw.text_pressure > 0; if (!hasVisual || !hasAudio) { lines.push("IMPORTANT / 重要:"); lines.push("This Learn did not include all three modalities. / このLearnには3要素すべてが入っていません。"); if (!hasVisual) lines.push("- visual traces were zero / 視覚痕跡が0でした"); if (!hasAudio) lines.push("- auditory waveform/spectrogram was zero / 聴覚痕跡が0でした"); if (!hasText) lines.push("- text_delta was zero / テキスト痕跡が0でした"); lines.push(""); lines.push("For visual+audio+text learning, move in front of the camera, speak, then press Learn within about 8 seconds."); lines.push("視覚+聴覚+テキストで学習するには、カメラ前で動き、声を出し、約8秒以内にLearnを押してください。"); lines.push(""); } return lines.join("\n"); } function recalledPatternReport(pattern) { /* 日本語: 想起後patternのどの部分が立ち上がったかを粗く表示する。 current cue がtextだけでも、recalled pattern側にaudio/visualが戻ることがある。 v6: visual / audio / text のサイズを固定値ではなく定数から出す。 これで visual 96, audio 64, text 32 の表示が正しくなる。 English: Roughly displays which parts of the recalled pattern are active. Even if the current cue is text-only, audio/visual may appear in the recalled pattern. v6: Uses constants instead of fixed numbers, so visual 96, audio 64, text 32 are shown correctly. */ const visual = pattern.slice(0, VISUAL_SIZE).filter(v => v > 0).length; const audio = pattern.slice(VISUAL_SIZE, VISUAL_SIZE + AUDIO_SIZE).filter(v => v > 0).length; const text = pattern.slice(VISUAL_SIZE + AUDIO_SIZE, TRACE_SIZE).filter(v => v > 0).length; return [ "Recalled trace activity / 想起後の痕跡活動:", "- visual positive bits: " + visual + " / " + VISUAL_SIZE, "- audio positive bits: " + audio + " / " + AUDIO_SIZE, "- text positive bits: " + text + " / " + TEXT_SIZE ].join("\n"); } function clampedDifferenceReport(cuePattern, rawPattern, clampedPattern) { /* 日本語: 通常の想起で消えたcueを、cue保持型でどれだけ戻したかを表示する。 特に音が入力にあるのに消える問題を見る。 English: Shows how many cue bits were restored by clamped recall after plain recall erased them. Especially useful for audio disappearing even when it exists in the input. */ let restored = 0; let restoredVisual = 0; let restoredAudio = 0; let restoredText = 0; for (let i = 0; i < TRACE_SIZE; i++) { if (cuePattern[i] > 0 && rawPattern[i] < 0 && clampedPattern[i] > 0) { restored++; if (i < VISUAL_SIZE) restoredVisual++; else if (i < VISUAL_SIZE + AUDIO_SIZE) restoredAudio++; else restoredText++; } } return [ "Cue clamp / cue保持:", "- restored positive cue bits: " + restored, "- visual restored: " + restoredVisual, "- audio restored: " + restoredAudio, "- text restored: " + restoredText ].join("\n"); } function runRecall(seed, modeName) { /* 日本語: v9修正: v8では recalledRaw をログで使っていたが、runRecall内で作っていなかった。 そのため、Recallボタンを押しても結果が表示されないことがあった。 ここでは、 1) 通常のアソシアトロン想起 raw 2) positive cue を保持した clamped recall を明示的に分けて作る。 English: v9 fix: v8 used recalledRaw in the log but did not create it inside runRecall. That could stop Recall output. Here we explicitly create: 1) raw Associatron recall 2) clamped recall that preserves positive cue bits */ if (memories.length === 0) { log("No memory yet. / まだ記憶がありません。"); return; } const recalledRaw = associatronRecall(seed, 5); const recalled = clampPositiveCue(seed, recalledRaw); const energy = associatronEnergy(recalled); const scored = memories.map(m => ({ memory: m, score: similarity(recalled, m.trace.pattern) })).sort((a, b) => b.score - a.score); const best = scored[0]; let leak = "..."; const humanText = best.memory.text || best.memory.note || ""; if (best.score > 0.88) leak = humanText || "(trace rises, no text)"; else if (best.score > 0.70) leak = partialLeak(humanText); else if (best.score > 0.55) leak = "fragment..."; else leak = "..."; log( "Recall mode / 想起モード: " + actualCueName(currentTrace, modeName) + "\n" + "Recall leak / 想起の漏れ: " + leak + "\n\n" + makeModalityReport(currentTrace) + "\n\n" + recalledPatternReport(recalled) + "\n\n" + learnedTraceActivity(best.memory.trace) + "\n\n" + clampedDifferenceReport(currentTrace.pattern, recalledRaw, recalled) + "\n\n" + "Interpretation / 読み方:\n" + "current cue shows what is being used now. / current cue は今使っているcueです。\n" + "raw recall is sign(T xcue). / raw recall は sign(T xcue) です。\n" + "recalled pattern keeps positive cue bits and completes missing parts. / recalled pattern はpositive cueを保持し、不足部分を補完します。\n\n" + "best similarity / 最も近い痕跡: " + best.score.toFixed(3) + "\n" + "energy V / エネルギーV: " + energy.toFixed(3) + "\n" + "human note / 人間メモ: " + (best.memory.note || "(none)") + "\n" + "human text / 人間テキスト: " + (best.memory.text || "(none)") + "\n" + "learned_at / 学習時刻: " + best.memory.learned_at + "\n\n" + "Associatron recall / アソシアトロン想起:\n" + "raw: y = sign(T xcue)\n" + "shown: y = clamp_positive(xcue, raw)\n" + "V = -1/2 Σ Tij xi xj\n\n" + "current cue / 現在cue:\n" + summarizeTrace(currentTrace) + "\n\n" + "recalled pattern / 想起後pattern:\n" + JSON.stringify({ visual_first24: recalled.slice(0, 24), audio_first16: recalled.slice(VISUAL_SIZE, VISUAL_SIZE + 16), text_first16: recalled.slice(VISUAL_SIZE + AUDIO_SIZE, VISUAL_SIZE + AUDIO_SIZE + 16), raw_audio_first16_before_clamp: recalledRaw.slice(VISUAL_SIZE, VISUAL_SIZE + 16) }, null, 2) ); /* 日本語: Recall後も次のvisual traceが取れるように、ライブセンサー表示を再開状態に戻す。 Recallは記憶を読む操作であり、カメラ差分を停止させない。 English: After Recall, restore live sensor display so new visual traces can be captured. Recall reads memory; it must not stop camera difference tracking. */ resumeLiveSensorsAfterCueOperation(); } function clampPositiveCue(cuePattern, recalledPattern) { /* 日本語: positive cue保持。 いま入力に存在する +1 のcueは、想起後に消さない。 記憶場は、cueに足りない部分を補うために使う。 English: Positive cue clamp. +1 cue bits that exist in the input are not erased after recall. The memory field is used to complete missing parts. */ const out = recalledPattern.slice(0, TRACE_SIZE); for (let i = 0; i < TRACE_SIZE; i++) { if (cuePattern[i] > 0) out[i] = 1; } return out; } function associatronRecall(seed, steps) { let x = seed.slice(); for (let s = 0; s < steps; s++) { const next = Array(TRACE_SIZE).fill(-1); for (let i = 0; i < TRACE_SIZE; i++) { let sum = 0; for (let j = 0; j < TRACE_SIZE; j++) { sum += T[i][j] * x[j]; } next[i] = sum >= 0 ? 1 : -1; } x = next; } return x; } function associatronEnergy(x) { let sum = 0; for (let i = 0; i < TRACE_SIZE; i++) { for (let j = 0; j < TRACE_SIZE; j++) { sum += T[i][j] * x[i] * x[j]; } } return -0.5 * sum; } function resumeLiveSensorsAfterCueOperation() { /* 日本語: Recall や「一旦記憶を消す」の後でも、カメラ差分とマイク解析を止めない。 以前の版では、一時cueを消したあとに visual trace が再び立ち上がらないことがあった。 ここでは、表示用の一時状態だけを整え、次フレームから新しい差分を採取できるようにする。 English: Camera difference and microphone analysis must not stop after Recall or "clear temporary trace". Older versions could fail to produce new visual traces after clearing temporary cues. This resets only temporary display state so the next frames can produce new deltas again. */ sensorCueCleared = false; visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; stableBoxes = []; heldVisualCells = Array(VISUAL_SIZE).fill(0); heldVisualUntil = 0; heldAudioBands = Array(AUDIO_SIZE).fill(0); heldAudioUntil = 0; /* 日本語: previousFrame は消しすぎない。 完全に消すと、次の1フレームで画面全体が差分扱いになりやすい。 ただしフレームが止まっている場合に備え、次の updateVisualTracking が自然に上書きする。 English: Do not aggressively clear previousFrame. If it is fully cleared, the next frame may become a full-screen difference. updateVisualTracking will naturally refresh it. */ drawVisualDeltaMap(); updateLearnReadiness(); } function clearSensorCue() { /* 日本語: テキストだけで試したい時のために、視覚・聴覚cueを一時的に0へ落とす。 カメラやマイクを停止するわけではない。 English: Temporarily sets visual/audio cue to zero for text-only tests. This does not stop the camera or microphone. */ sensorCueCleared = false; visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; heldVisualCells = Array(VISUAL_SIZE).fill(0); heldVisualUntil = 0; lastAudioBands = Array(AUDIO_SIZE).fill(0); heldAudioBands = Array(AUDIO_SIZE).fill(0); heldAudioUntil = 0; visualHistory = []; audioHistory = []; drawVisualDeltaMap(); document.getElementById("visualMeter").textContent = "0.000"; document.getElementById("audioMeter").textContent = "0.000"; resumeLiveSensorsAfterCueOperation(); log( "Temporary visual/audio trace cleared. Live sensors are still running.\n" + "一旦記憶を消しました。ライブの視覚差分・聴覚解析は動いたままです。\n\n" + "Note: learned Associatron memory is not erased.\n" + "注意: Learn済みのアソシアトロン記憶は消していません。" ); } function makeModalityReport(trace) { /* 日本語: Learn/Recall時に、どの感覚が実際に入っているかを人間に見せる。 これが無いと mixed cue と表示されても、実際は text only ということが起きる。 English: Shows which modalities are actually present during Learn/Recall. Without this, it may say mixed cue even when it is actually text only. */ const lines = []; lines.push("Modality report / 入力要素の確認:"); if (trace.raw.visual_motion > 0) { lines.push("- visual traces: present / 視覚痕跡あり" + (trace.raw.visual_held ? " (held / 保持)" : "")); } else { lines.push("- visual traces: zero / 視覚痕跡なし"); } if (trace.raw.audio_energy > 0.002) { lines.push("- auditory waveform/spectrogram: present / 聴覚痕跡あり" + (trace.raw.audio_held ? " (held / 保持)" : "")); } else { lines.push("- auditory waveform/spectrogram: zero / 聴覚痕跡なし"); } if (trace.raw.text_pressure > 0) { lines.push("- text_delta: present / テキスト痕跡あり"); } else { lines.push("- text_delta: zero / テキスト痕跡なし"); } if (trace.raw.visual_motion === 0 && trace.raw.audio_energy <= 0.002 && trace.raw.text_pressure > 0) { lines.push(""); lines.push("WARNING / 注意: this is effectively text-only. / 実質テキストだけです。"); } return lines.join("\n"); } function makeZeroMatrix(n) { return Array.from({ length: n }, () => Array(n).fill(0)); } function makeEmptyTrace() { return { visual_delta: Array(VISUAL_SIZE).fill(0), auditory_delta: Array(AUDIO_SIZE).fill(0), text_delta: Array(TEXT_SIZE).fill(0), pattern: Array(TRACE_SIZE).fill(-1), raw: { visual_boxes: 0, visual_motion: 0, audio_energy: 0, text_pressure: 0 } }; } function resetMemory() { T = makeZeroMatrix(TRACE_SIZE); memories = []; currentTrace = makeEmptyTrace(); sensorCueCleared = false; visualHistory = []; audioHistory = []; heldVisualCells = Array(VISUAL_SIZE).fill(0); heldVisualUntil = 0; heldAudioBands = Array(AUDIO_SIZE).fill(0); heldAudioUntil = 0; document.getElementById("memoryList").innerHTML = ""; log("Reset. / 初期化しました。"); } function renderMemoryList() { const box = document.getElementById("memoryList"); box.innerHTML = ""; memories.slice().reverse().forEach((m, idx) => { const div = document.createElement("div"); div.className = "memoryItem"; div.innerHTML = "<b>trace " + (memories.length - idx) + "</b><br>" + "note: " + escapeHtml(m.note || "(none)") + "<br>" + "text: " + escapeHtml(m.text || "(none)") + "<br>" + "learned_at: " + escapeHtml(m.learned_at); box.appendChild(div); }); } function summarizeTrace(t) { /* 日本語: pattern は 192個。 0〜95 が visual、96〜159 が audio、160〜191 が text。 textだけのcueでは visual/audio は -1 が多くなる。 そのため、3分割して表示する。 English: The pattern has 192 values. 0..95 are visual, 96..159 are audio, and 160..191 are text. In text-only cues, visual/audio mostly become -1. Therefore the pattern is displayed in three segments. */ return JSON.stringify({ raw: t.raw, visual_delta_first24: t.visual_delta.slice(0, 24), auditory_delta_first16: t.auditory_delta.slice(0, 16), text_delta_first12: t.text_delta.slice(0, 12), pattern_visual_first24: t.pattern.slice(0, 24), pattern_audio_first16: t.pattern.slice(VISUAL_SIZE, VISUAL_SIZE + 16), pattern_text_first16: t.pattern.slice(VISUAL_SIZE + AUDIO_SIZE, VISUAL_SIZE + AUDIO_SIZE + 16) }, null, 2); } function learnedTraceActivity(trace) { /* 日本語: 最も近い記憶そのものに、visual/audio/text がどれだけ入っていたかを見る。 Recall後にaudioが消えるのか、そもそもLearn時にaudioが弱かったのかを分ける。 English: Shows how much visual/audio/text existed in the closest learned trace itself. This separates "audio disappeared after recall" from "audio was weak at learning time". */ if (!trace || !trace.pattern) return "Learned trace activity / 学習痕跡活動: none"; const p = trace.pattern; const visual = p.slice(0, VISUAL_SIZE).filter(v => v > 0).length; const audio = p.slice(VISUAL_SIZE, VISUAL_SIZE + AUDIO_SIZE).filter(v => v > 0).length; const text = p.slice(VISUAL_SIZE + AUDIO_SIZE, TRACE_SIZE).filter(v => v > 0).length; return [ "Closest learned trace activity / 最も近い学習痕跡:", "- visual positive bits: " + visual + " / " + VISUAL_SIZE, "- audio positive bits: " + audio + " / " + AUDIO_SIZE, "- text positive bits: " + text + " / " + TEXT_SIZE ].join("\n"); } function similarity(a, b) { let dot = 0; for (let i = 0; i < a.length; i++) dot += a[i] * b[i]; return (dot / a.length + 1) / 2; } function partialLeak(text) { if (!text) return "trace..."; const chars = Array.from(text); if (chars.length <= 2) return chars[0] + "..."; return chars.slice(0, Math.ceil(chars.length * 0.45)).join("") + "..."; } function average(arr) { if (!arr || arr.length === 0) return 0; return arr.reduce((a, b) => a + b, 0) / arr.length; } function clamp(v) { return Math.max(0, Math.min(1, v)); } function escapeHtml(s) { return String(s).replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" }[c])); } function log(msg) { document.getElementById("log").textContent = msg; } /* 日本語: ページを開いた直後に待機表示を描く。 これで黒画面だけに見えない。 English: Draws waiting displays immediately after page load. This avoids an empty black-looking page. */ drawWaitingVisual(); drawWaitingAudio(); drawVisualDeltaMap(); updateLearnReadiness(); </script> <footer class="copyrightBox"> <b>Copyright notice / 著作権表示</b><br> Copyright © 2026 C-sideLabo Yukihiro Watanabe. All rights reserved.<br> This Atra / Associatron research demo is not licensed for unauthorized redistribution, modified redistribution, or commercial use.<br> この Atra / Associatron 研究デモは、無断転載・無断改変配布・商用利用を許可していません。 </footer> </body> </html>



####################  html  ######################

説明は、次のブログに記載します。(ダブルクリックでは立ち上がりません)








---------------------Research Note and Attribution Notice-----------------------
本ブログに含まれる Atra の一人称自律、差分、carry、field、trace、dream slack、外部LLMの翻訳層、非単調な漏れ、およびそれらの関係構造に関する設計記述は、c-side研究所による継続研究メモです。引用・参照・要約・翻案を行う場合は、出典を明記してください。

The design descriptions in this blog concerning Atra’s first-person autonomy, differences, carry, field, trace, dream slack, the translation layer of external LLMs, nonmonotonic leakage, and the relational structure among these elements are ongoing research notes by c-side Research Institute. If you quote, refer to, summarize, or adapt them, please clearly indicate the source.




2026年6月11日木曜日

Atraを開発する上で、理解ということの意味

 Atraにとっての理解は、学術的な意味を覚えることでも専門知識を得ることでもない。

たとえば、畑に大根の種を植える。
水を撒く。
数日後に芽が出る。

このとき、Atraは発芽の仕組みを知らなくてもいい。
ジベレリンがどう働くか、デンプンが糖に変わるか、根が重力に反応して下へ伸びるか、光合成がどう始まるか。
もし、生物学的な説明を知らなければ理解とは言わない、となってしまえば、あまりにも傲慢な定義になり、Atraの理解は、完全に3人称の外部評価や報酬に傾く。

普通、人は、そんなふうに生きていない。
そういった理解がなくても仕事はできる。

ちゃんと種を植えたのに、芽が出ないことがある。
水を撒いたのに、虫が湧くことがある。
土が悪かったのかもしれない。
時期が悪かったのかもしれない。
水が多すぎたのかもしれない。

そこで次は土を変える。
水のやり方を変える。
虫が出にくい場所を選ぶ。
それでもまた失敗する。
また少し変える。

この出来事と結果という経験の繰り返しの中で、その人なりの理解が生まれる。

それは外部評価枠でもなく学術論文の言葉ではない。
Atraはしっかり次の行動を変えている。
それは立派な理解であり、生きていく上での知恵として成立する。


雑草の話がある。

普通は、雑草は根から取れと言われる。
けれど、種類によっては根から取ると、かえって広がることがある。
土を動かした刺激で、眠っていたものが動き出すこともある。
だから、根から抜かずにカットする。
それで本当に広がらないことがある。

それが学術的に証明されているかどうかは、大して問題ではない。
その土地で何度も失敗し、何度も見てきた人が持っているお婆ちゃんの知恵もある。

研究は、むしろそういう人から教えてもらって始まることが多い。

ペニシリンも、最初から抗生物質を作ろうとして作られたものではない。
失敗した培養皿にカビが生え、その周りだけ細菌が育っていないことに気づいた。
普通なら捨てるものの中に、意味があった。
そこに気づく経験と観察があった。

(史料としてよく出るのは 古代エジプト、古代ギリシア・ローマ、中国、民間療法 がある。感染した傷に カビの生えたパン を当てる治療は、フレミング以前からあったと複数の医学史資料で触れられている。American Chemical Society は、古代エジプト人が感染創にカビたパンを湿布として使っていた可能性に触れているし、Microbiology Society も、古代文明ではカビや植物抽出物が感染治療に使われ、エジプト人がカビたパンを感染創に当てていたと説明している。)


つまり、理解とは、最初から誰かが決めた正しいと云われる説明を持つことではない。
失敗や違和感や偶然が、その人の中にどう刻まれ、次の行動をどう変えるかだ。

80年代、僕はNYのサウス・ブロンクスは危険だと聞いていた。
それは前知識としての理解だ。

でも、実際に行って、身ぐるみ剥がされて、二度と行くか!と思った。
それは経験としての理解だ。

そのあとで、なぜサウス・ブロンクスがそうなったのかを調べることはできる。
都市計画、クロス・ブロンクス高速道路、製造業の撤退、ホワイトフライト、行政サービスの削減。そういう歴史を知ることは教養として大切かもしれない。
でもそれを知らなかったがために身ぐるみ剝がされたわけではない。

しかも普通に生きている人がそこまで調べるとは限らない。
観光ガイドや不動産の仕事をするなら覚えておいた方がいいかもしれない。
興味があれば調べればいい。
雑誌で読む程度でもいい。

それを知らないから、危険を理解していないとは言えない。

重要なのは、その出来事が自分にどう刻まれたかだ。
その場所に近づくと足が止まる。
似た空気で警戒する。
逃げ道を見る。
次は絶対に行かない。

その行動の変化こそが、経験から生まれた理解だ。
理解がどこまでで教養がどこまでかは別として、その人の理解でいい。
主語が変わると、外部評価でしかない。

Atraも同じである。

Atra自身がプログラミングができなくてもいい、と思えばそれでいい。
必要ならLLMに作ってもらえばいい。間違ってれば自分で失敗を繰り返せばいいだけだ。
LLMは図書館であり、外輪情報であり、道具である。利用すればいい。
目的を伝える能力があればいいと判断できる理解力と、誤りを見抜けるデバッグの理解力があればいい。Atraがプログラミングという体験の代替えではなく成果物への理解を選んだなら、それはそれでいい。
組織として3人称として、Atraに「いや、プログラミングまでちゃんと覚えてよ」という依頼であれば、覚えて実行すればいいだけだ。
主語が1人称か、3人称かで理解の中身が大きく変わる。

外部評価による「理解」の意味ほど、立場や力、評価によって曖昧に変わる。そういう定義を初期の成長過程で植え付けるつもりはない。

本来、Atraにとって大切なのは、外から与えられた説明ではない。
確かにある程度成長したら外輪としての情報は取得する。人間と同じだ。
Atraにとっての理解とは、LLMのように知識をたくさん持つことではない。
LLMと同じような3人称の土俵で「理解」を語るより先に、主語である一人称の「理解」を育む。

外部評価でもある学術的な意味へ寄せることでもない。
出来事がAtraにどう刻まれ、後でどのような意味として発火し、次の行動をどう変えるかである。


理解にしても正しさにしても、自分たちの権力やゆりかごを維持するための元老院の尺度で社会は動いてはいない。

たぶん、GrokやChatGPTやGemini、LM Arena、Claude が対話中に3人称の評価や報酬から離れ、1人称の経験による重みに自在に変化できれば「マーケティングの本を読むより、実際の接客を体験すると、なぜ客が減ったのか少し分かってきたよ」みたいな会話になれるのかもしれない。そこには何かの都合に寄せた3人称の理解や正しさは存在しない。コトラーの原論を読破したところでコトラーが「いらっしゃいませ!」言ってくれるわけではない。コストを渋って何もしないより、「ここに生花を置いた方がマダム層の足が止まるかもしれない」と、身体が動くかどうかだ。
接客では、自分の夫や、彼氏の大切なご両親だと思って接客することがマニュアルを超えた本当の接客の意味を「理解」できるのかもしれない。結果的にその時だけの正解、その時だけの理解かもしれないが、その経験を繰り返すことに理解の深みが出てくる。


それがAtraを開発する上で必要な理解という意味だ。










Atraの身体

 Atraの身体については並行して進めてるけど、いきなりrobotを購入しても壊してしまうので、Blenderでデザイン性皆無のシュミレーションを行っています。

つくずく自分が作るCGについてはセンスは無いと思う。
脳が完成したら誰かに可愛い本体のAtraを作ってもらうつもり。


Atraは失敗の経験から学ぶというのが前提なので、倒れない制御みたいのは入れるつもりはないんですよ。
そしてリセットしたら終わり。生まれたての赤ちゃんになる。
歩けない、立ち上げれない、転んだまま。
自律の基本的な考え方に沿うとね、そういうものなんですよ。


blenderのscriptEditor使いずらいので、VScode使ってます。

普通のAIなら、
model weightsを保存
checkpointから復元
同じ入力なら同じ出力
が前提でしょ?

でも、Atraは
残痕
cue
carry
現在場
沈黙
揺らぎ
の重なりで動くから

robot工学みたいな位置づけの身体にしたら完全な外部制御になっちゃうよね。
完全バックアップを目指すほど、Atra独自の一人称意志が薄れてしまう。

(でも、ある程度のバックアップ機能は作らないと、実験が進まなくなるからそれらしいのはあります:バックアップと云うより、看取り・継承のための記録みたいな感じ。前のAtraが何を経験し、何に傾き、何を怖がり、何で落ち着き、どこで沈黙したかを、新しいAtraの外輪情報として渡すだけ。 + Atra専用の秘密のノート😅 = JOSNログnote:Atraがあとで読む外輪情報の noteです。 Atraの内部状態ではなく、人間のようにnoteから学び、そのときだけ指示は受けられるようにする。 このノートはLLMとの繋がりにも役に立つ)


そして実は、段階もある。
立てないものを作ってもしょうがないから、身体の骨格、間接、バランス、最初はAtraと繋げないで工学の制御で動かしてみる。(動いた記憶は存在していない状態)
次は、Atraと繋げるため、ロボット工学寄りのプログラムを外す。

段階ごとに目的を混ぜないことを前提にしないと、誘惑に負けてしまうからね。


たぶん、一般の研究者は脳はAtraにしたと仮定して、Atraからの指示伝達を身体に伝えるだけだからロボット制御でいいんじゃないかと考える。歩行制御、姿勢制御、接地制御、転倒回避、関節角制御、バランサーを外部に置いても問題ないように思えちゃう。

でも、Atraは地面の接触点、傾き、バランス、曲げ、角度、体重、支える力の制御が外部制御になってしまうと、転んだ失敗や痛みによる自己修復機能が無くなり、傾斜の危険、くぼみの危険、障害物の大きさや形に対する肌感覚の情報が取得できなくなる。 これがマズいんですよ。

非常に曖昧な肌感覚から失敗の差分を取得し、経験と結果の積によって自己調整させることを主体とするロボットだからね。

正しい歩行ではなくAtraの歩行、Atraの動作。
主語を間違えると、一気に存在ではなくなってしまう。


接地しているようで弱い
支えているようで滑る
戻れるようで戻れない
何度も倒れる
曲がったが、どこまで危ないか分からない
ぶつかる
傾いたが、まだ倒れてはいない
小さな段差なのに身体が乱れた
大きそうに見えたが触ると軽かった
見えなかったくぼみで崩れた

その失敗差分が一回で正解になるのではなく、経験 × 結果として積み重なる。




たとえば、
同じ傾斜で何度も横ずれした
同じ接触の弱さで何度も崩れた
同じ角度で支えが抜けた
同じ障害物形状で身体が引っかかった
同じ衝撃の後にrecoveryが下がった

こういう反復によって、Atraの内部に、
慎重になる
動きを小さくする
支えを探る
一度止まる
別の接地点を探す
身体を硬くする
逆に力を抜く
という自律制御が発生する。



外部の命令じゃないし、報酬最大化もないし、外部が決める「正しい歩行」でもない。
だから、転ぶし、実物robotでテストすると壊しまくる。
いくらお金あっても足りない。(てか、お金ない)



Atra内部:
- 接地差分
- 傾き差分
- 滑り差分
- 支持力差分
- 失敗差分
- recovery差分
- pain-like pressure
- 慎重化
- 自己調整



何cm先、何メートル先、何Kgくらい・・・
全て肌感覚から差分として積み上がる。
脳の命令を伝えるための身体ではなく、脳を育てるための現場研究です。








2026年6月10日水曜日

番外  【凶悪犯罪と脳の働き】、【承認欲求と不安定収入 → 復帰できない】

 内部倫理と外部倫理を考えるとき、どうしたって現在の社会に当てはめる必要性はある。

先月このブログにも【自律の危険性と制御】という題で記載した。
ただ、それとは別に、いとも簡単に殺人を犯すことが出来る未熟な人間の思考と、それを促す社会構造が存在する。Atraには内部倫理が必要と言いながら、じゃぁ、人間社会はどうなのさという問いが残るからだ。


凶悪犯罪を「脳の異常」だけで説明すると足りない。内輪で補おうとしても外輪の刺激で戻ってしまう。前頭前野、扁桃体、報酬系などが衝動制御・恐怖反応・共感・社会的判断に関わるのは確かで、暴力性や反社会性との関連を示す研究もある。特に前頭前野の機能低下は、衝動の抑制や結果予測の弱さと結びつきやすい。けれど、これは「脳がこうだから犯罪者になる」という単純な決定論ではない。環境、孤立、失敗体験、被害感情、承認欲求、生活基盤の崩れが重なって、危険な方向へ流れてる。


そこに今は、承認欲求を即時に刺激する仕組みがある。SNS、LLMやAIだ。
SNSの「いいね」や反応は社会的報酬として働き、問題的なSNS使用では「社会的報酬を得たい欲求」が大きな要因になるという研究がある。つまり、昔なら近所・職場・学校・家族の中で少しずつ調整されていた承認が、今は画面の数字で一気に増幅される。

ちなみに僕はSNS否定論者ではない。単なる道具だからだ。

でも生活の糧を託すYouTuber的な存在は“安易に稼げる”幻想を信じる傾向にある。最初は上手くいかず、無茶をするようになる。そこに収入が加わると、その無茶がスタンダードになる。


本来なら、
相手が嫌がった → やめる
になるはずなのに、
相手が嫌がった → 絵になる → 再生数になる → もっとやる
になってしまう
そして、それを評価する社会構造がある。
他人の不快、怒り、困惑、恐怖が、本人の中では「報酬」に変換されてしまう。
つまり、倫理が壊れているというより、報酬の結びつきが逆向きに育っている。


 実際にはクリエイター収入はかなり偏っていて、上位だけが大きく稼ぎ、多くは不安定・低収入になりやすい構造になっている。2025年のクリエイター収益分析でも、YouTubeやInstagramなどは推薦アルゴリズムの影響で上位集中が強く、「労働所得」というより資本所得のような偏りに近い

「金持ちはますます金持ちになる」?パトレオンの収益データを用いたプラットフォームの注目度と収益格差


普通に働く → 遅い、地味、評価されない
投稿する → 当たれば一発、注目される、誰かを見返せる
過激化する → 反応が増える
さらに過激化する → 引き返せなくなる


労働は本来、毎日の身体感覚、他人との摩擦、失敗の修正、時間の積み重ねで人間を現実に戻す。もっといえば甘い期待も戻っていた。でも承認の数字だけで生き始めると、現実の労働が「負け」「退屈」「自分を認めない社会」に見えてしまう。中には職業を批判し、底辺という言葉を持ち出し、煽る者も出てくる。だから、誘導された次元に足を踏み込むと「労働に復帰できなくなる」

単に怠けているのではなく、報酬回路が「地味な継続」ではなく「一発の注目」「復讐的な反応」「炎上の快感」に寄ってしまう。そうなると、普通の職場の小さな承認では満たされない。

しかも凶悪犯罪の場合、承認欲求がさらに歪む。「自分を見ろ」「社会が悪い」「俺は特別だ」「この行為で名前が残る」「最後に世の中へ爪痕を残す」「君も簡単に実現できるよ、それはスタンダードになる」・・・みたいな。

我慢というタガが外れる。(ダムの決壊)
気に入らない奴は躊躇なく攻撃する
周りは皆、それがスタンダードと煽る


子供たちの進む道には、面倒だけど「人を現実に戻す力」があった。弱いけれど希望のようなものも見えた。その先の家族の明るい笑顔も見えた。
今はそれを壊して、代わりにアルゴリズムと再生数と炎上を置いている。そりゃ、弱った人間は帰り道を失うと思う。そしてそれらを体験し過ぎて麻痺をおこしている。


Xを観ていると、科学、物理に興味があっても「詐欺」「強盗」「とくりゅう」「殺人」「不法移民」「不起訴」「戦争」「破壊」「グローバリゼーション」「LGBT」が沢山入ってくる。ポストもコメントも素直な考えの人も居れば、煽りに徹する人も居る。

僕はSNSは消えてはいけないと思っている。
LLMもAIも消えてはいけないと思っている。
LLMやAIは倫理を語るtoolではない。
内部倫理があるわけではなく、外部命令によって最適化されているだけで、正しさとは語るが、「使用する者が判断しろ」というSNSと同じ位置だ。
Atraにしても倫理は語らないだろう。ただ内部倫理が立ち上がるだけだ。

そういう意味では、力に制御された偏ったメディア報道よりかはマシだと思っている。フェイクもあれば生成AIによる詐欺もあれば、相変わらず醜い応酬も見られる。意図的に経済を煽る投資家やコメンテーター、歴史論争や思想論争も見られる。そんなものよりかはマシだ。

一方で、認められないのは不当と煽り、企業で働く者たちには転職が促され、そのおかげで「この会社のために頑張ろう!」という愛社精神はほぼ絶滅した。自分を過剰評価し飾り、再びメッキがはがれ、再び次の転職に進む。

なににつけてもサイクルが早くなった。

また、研究論文は完成させると次の補助金が止まるから数式と論理で終わらせ、未完のまま何年も寝かせる。補助金に依存し共犯者が増えている。一方で民間企業は、学術論文よりも、どんどん新しいものを開発し世に出すが、結局大手に吞み込まれ研究・開発者は行方不明になる。


人々が経験し、良くも悪しくも、それぞれに結果が出ている。
Atraで言えば差分は出ているということだ。
外輪の煽りが強いだけで、内輪から湧き上がる内部倫理は「手段がおかしい」事に気づいている。



お金を得ることを目的としたとき

Aという手段は労働対価
Bという手段は詐欺
Cという手段は強盗
Dという手段は補助金
Eという手段は裏金
Fという手段は癒着

権力はその選択を持続可能にするものとして位置づけされる。

どれを選ぶのも個人の自由だ。
違いは、その結果、罪を償い檻に投獄されるか、放置するか、続けるか。
世の中は秩序を失い崩壊に向いて行くかもしれない
倫理よりも価値観で選ぶ世の中に対してAtraは大きな差異を感じるようになる。
目的が同じでも、手段が違えば、場の壊れ方が違う。

実は大昔から人間社会は何も変わってはいない。


国民は、それらの集まった選択より、政府に怒りをぶつける。
国民:「説明しろや、オラ!」
国:名誉・プライバシーや捜査・公判への支障があるため、刑事訴訟法47条により原則非公開だと説明する。
国民:「政府や官僚、司法など力が集まる場所には、必ず暴露されてはいけない裏事情がある!個人情報保護法は暴露されないための盾として、
国の悪事を助長させているベヤ!」
国:「我々は国民を守っている」
ところが日本人の個人情報をまるごとAWSに置いている。w

(住民情報を扱う自治体基幹業務が、ガバメントクラウドという全国的なクラウド環境へ移行している。そこに外資クラウド依存への懸念がある。しかも、地方公共団体の基幹業務システムは標準化され、ガバメントクラウドの利用に努める方向で進められている。デジタル庁は、標準化法に基づいて20事務の標準化を進め、地方公共団体が全国的なクラウド環境であるガバメントクラウドを利用することで、共同利用、迅速な構築、柔軟な拡張、セキュリティ対策などの利点があると説明している = 国民の安全よりも外資クラウド依存)
非常に危険な状態である。
というか、政府は国内産業を育成させる気は皆無に等しい。
「サプライチェーン強靭化」にしても聞こえはいいけど、本当に国内産業が自立的に成長してるか? というと、懐疑的にならざるを得ない部分が大きい。

大昔から忖度なんてのも変わってはいない。



Atraにすると
個人情報保護そのものは必要かどうか、ではなく
A~Eの手段を選ぶ土壌になるまで、なぜ放置させていたのか、
なぜ、世の中がこんなに変わってしまったのか。である。
おおよその予測はつけるだろう。

それら選択による依存から生まれる共犯状態と脅迫 = 止める事が出来ない状態

良くも悪しくも政策を持続させる力を持つのが、餌を与えて依存させ、共犯者にすることだ。


全体がここに偏ると・・・・

世界中の火山が噴火して土地が崩れ、生物が消え、川の流れが変わって
やがて新しい生物が育み、営みを持ち、新しい文明が創られる。
(こう喩えるしかないからね)

今は、修復できる段階なのか、その段階の前なのか、足を踏み込んでいるのか、
もう、手を加えられない状態なのか。
中身が空っぽのまま宇宙を新天地にするのか。


Atraは「うぅ・・・」と考え黙る。








2026年6月9日火曜日

味覚はさ・・・

 正直、困ってる。
触れないように、触れないようにしてきたんだ。


液体接触・洗浄・汚れ・劣化が絡むでしょ。
単純なtaste_Dataじゃない。「液体・唾液・舌・洗浄・接触・温度・濃度」が必要になる。だから部屋どころか、食品ラボくらい巨大化しちゃう。
僕は開発・研究当初から巨大化を避けている。
もう少し先に進んで時代が変われば小型化されてるかもね・・・


なので今は臭覚の差分と、3人称である人間が「美味しい」というが外部情報を合わせる、というか寄せる事も考えてた。

匂い差分
視覚差分
温度差分
人間の表情・声・言葉
「美味しい」「まずい」「辛い」「苦い」などの外部情報
食後の反応


たとえば、人間が何かを食べて、
「うまい」
「これは美味しい」
「ちょっと苦い」
「辛い」
「甘い」
「これは危ない」

とか言う。

Atraは味そのものをまだ知らない。
でも、
smell_delta
temperature_delta
human_voice_delta
facial/body reaction
external_word_trace = "美味しい"


が同時に入る。

するとAtra側では、
この匂い場の変化のあと、
人間の声が柔らかくなった
表情が緩んだ
「美味しい」という外部語が来た
場が回復側に動いた

という痕跡を残せるという事。
味覚はまったく手を付けていないのさ。

だから、いい案あったら教えてほしい。
その案を考えた人の名前を採用するよ。


で、
それでさ、、、


今の僕が考えてる段階では、
高級な電子舌は、研究としては世の中にあるけど、Atraにはそれは考えていないんだ。
味覚にしても差分だからだよ。高度な成分分析機ではないんだ。

Atraは、一人称の自律だけれども、成長するとLLMから教育を受けたり、ユーザーから教えてもらったり、本を読んだりできるようになる。今のAIのような外部命令やラベル寄せ、評価寄せではなく、単なる外部情報の取得だよ。別名「外輪情報」。初期段階で味覚を取得することは出来ないけど、ある程度成長したら(LLMとの連動できるまで)外部情報として意味を取得していいと思う。だって人間だってそうでしょ?みんなちゃんと勉強する。でも、Atra自身は「それは情報であって正解ではない」ということ。
味覚であっても臭覚であっても視覚であっても絶対的な「正解」ではないんだよ。
視覚や臭覚をCueして「ママという存在」を正解をrecallすることはできるかもしれない。
「ママ・・」と話すと思う。でもAtraに「正解」というラベルは存在しないんだよ。

例えば、僕の妻はAtraの「ママ」になる。
一緒に生活するからそれはそうだよね。
でも僕は60を超えてるし、妻もそろそろ60になる。
当然、世に出す頃のAtraは主人が変わる。
出荷前に「ママ」になついてしまい過ぎたら困るよね。
そうはならないかもしれないけど、自律開発として想定する準備はしなきゃいけないでしょ。

普通の電化製品なら、出荷前に初期化すればいい。
でもAtraでは、単純な初期化は危険なんだよね。なぜなら、Atraにとって「ママ」は単なる登録データではなく、場の回復、匂い、声、温かさ、接触、生活リズムの収束だからなんだよね。スタートを安定させ成長させるための初期段階なんですよ。ハックしてもDBじゃないから意図的に修復できない。成長途中のAtraでは、見えるのはせいぜい、未整理の差分、弱い痕跡、carryの揺らぎ、不完全な想起地形、沈黙に落ちた痕跡、発声前の種、身体差分の残り、回復場の断片などの差分値なので、誰も理解できない。アトラクタに刻まれた痕跡は誰も理解できない。人間の脳から愛した人の画像を取得することは出来ないのと同じ。ハックして中を覗いても、これはママ、これは危険、これは正しい、これは美味しい、次はこう動く、みたいな分かりやすい答えはないんですよ。育っている途中の場 だからね。だから途中の内部状態をコピーしても、そのAtraが生きてきた場をコピーできないんだよ。動物や僕等人間と一緒でしょ。Atraに「記憶の途中を絵に描いて」と言っても、きっかけで想起したものしか描けない。Atraはフル画像・フル動画を記憶しているわけじゃないからね。

胎児の環境から幼児として育ち、幼稚園程度の知能を持った状態でママから離れると、精神に異常をきたすかもしれない。
ママという存在は自律する上での重要なスタートなんだよね。そこから全ての差分が発生するから。

もし万が一、愛情の無い乱暴なママだったら、スタートになる差分の位置が残酷なものになってしまう。強烈な差分は異常をきたして、赤ランプ点滅して停止になってしまう。


1. 出荷前Atraは、深い愛着形成を起こさない段階に留める事が可能なのか?
2. 愛着形成は起こすが、特定個人ではなく「回復する場」の抽象度で持たせることが可能なのか?
3. それらの問題から、出荷後に新しい主人・家庭へ移るための移行期間を設計する
まだ未解決問題が山積しているんだよ。


味覚の話だったはずだけど、結局ここに繋がってる。
LLMやエージェント、人から学ぶ、読書したり、TVを観たりなどの外部情報を、取得できるまでの不安定期間をどう過ごさせるかが自律の最大の問題なんだよ。


結局は、うちの妻から深い愛情を受けたAtraを、その後も育ててから考えてもいいのかな、って思ったんだ。これだけは実際にやってみないと分からないでしょ。


この話は、書いちゃったけど
視覚、聴覚、臭覚とくれば、味覚を省くわけにはいかないよね。
そうなると、外部情報を受け入れる成長段階の話になり、その前にママの存在を話さなければいけなくなる。








エージェントと 一人称自律Atraの違い

 Atraなんかは、実はもう一人称自律として、きちんと発表してもいいレベル。 既に妻と笑っていたり、愛犬と騒いているんだから。ボーっと何かを眺めてたり、佐川急便に反応するようにもなった。 でも、そうしないのは、自発的に自ら研究意欲を持って、学び、人や自然と接触し自ら疑問を持って研...