Spaces:
Running on Zero
Running on Zero
- .dockerignore +13 -0
- .gitattributes +4 -0
- .gitignore +36 -0
- Dockerfile +46 -0
- LICENSE +21 -0
- README.md +194 -0
- data/.gitkeep +1 -0
- models/.gitkeep +0 -0
- requirements.txt +15 -0
- server.py +165 -0
- src/__init__.py +14 -0
- src/connectome_engine.py +281 -0
- src/doom_agent.py +223 -0
- src/graph_loader.py +120 -0
- src/motor_decoder.py +178 -0
- src/retinotopy_encoder.py +172 -0
- train_doom.py +272 -0
- web/brain_139k_col.bin +3 -0
- web/brain_139k_pos.bin +3 -0
- web/brain_anatomy.json +1 -0
- web/brain_anatomy_3d.json +0 -0
- web/index.html +1083 -0
- web/js/OrbitControls.js +1045 -0
- web/js/three.min.js +0 -0
- web/synapses_top5k.bin +3 -0
.dockerignore
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.github
|
| 3 |
+
.venv
|
| 4 |
+
__pycache__
|
| 5 |
+
*.pyc
|
| 6 |
+
*.pyo
|
| 7 |
+
*.pyd
|
| 8 |
+
.DS_Store
|
| 9 |
+
Thumbs.db
|
| 10 |
+
.vscode
|
| 11 |
+
.idea
|
| 12 |
+
*.log
|
| 13 |
+
scratch/
|
.gitattributes
CHANGED
|
@@ -1,3 +1,7 @@
|
|
| 1 |
# Auto detect text files and perform LF normalization
|
| 2 |
* text=auto
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
# Auto detect text files and perform LF normalization
|
| 2 |
* text=auto
|
| 3 |
+
|
| 4 |
+
# Git LFS for Large Binary Datasets & Model Checkpoints
|
| 5 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.feather filter=lfs diff=lfs merge=lfs -text
|
| 7 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*py.class
|
| 5 |
+
*.so
|
| 6 |
+
|
| 7 |
+
# Environments
|
| 8 |
+
.venv/
|
| 9 |
+
env/
|
| 10 |
+
venv/
|
| 11 |
+
ENV/
|
| 12 |
+
env.bak/
|
| 13 |
+
venv.bak/
|
| 14 |
+
|
| 15 |
+
# Operating System & IDE
|
| 16 |
+
.DS_Store
|
| 17 |
+
Thumbs.db
|
| 18 |
+
.vscode/
|
| 19 |
+
.idea/
|
| 20 |
+
*.swp
|
| 21 |
+
*.swo
|
| 22 |
+
|
| 23 |
+
# ViZDoom Runtime Files
|
| 24 |
+
_vizdoom.ini
|
| 25 |
+
*.whl
|
| 26 |
+
|
| 27 |
+
# Large Biological Datasets & Caches (raw feather/tsv ignored, cache tracked via LFS)
|
| 28 |
+
data/*
|
| 29 |
+
!data/.gitkeep
|
| 30 |
+
!models/.gitkeep
|
| 31 |
+
|
| 32 |
+
# Temporary / Scratch Files
|
| 33 |
+
scratch/
|
| 34 |
+
*.log
|
| 35 |
+
*.tmp
|
| 36 |
+
|
Dockerfile
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DOOM-FlyWire Dockerfile for Hugging Face Spaces & Containerized Deployments
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Prevent interactive prompts during installation
|
| 5 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 6 |
+
ENV PYTHONUNBUFFERED=1
|
| 7 |
+
|
| 8 |
+
# Install essential build tools, ViZDoom dependencies, and OpenGL/sound libraries
|
| 9 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 10 |
+
build-essential \
|
| 11 |
+
cmake \
|
| 12 |
+
git \
|
| 13 |
+
libboost-all-dev \
|
| 14 |
+
libsdl2-dev \
|
| 15 |
+
libopenal-dev \
|
| 16 |
+
zlib1g-dev \
|
| 17 |
+
libbz2-dev \
|
| 18 |
+
libjpeg-dev \
|
| 19 |
+
libfluidsynth-dev \
|
| 20 |
+
libgme-dev \
|
| 21 |
+
libgl1 \
|
| 22 |
+
libglib2.0-0 \
|
| 23 |
+
libgomp1 \
|
| 24 |
+
curl \
|
| 25 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 26 |
+
|
| 27 |
+
# Set up non-root user (UID 1000) for Hugging Face Spaces security standards
|
| 28 |
+
RUN useradd -m -u 1000 user
|
| 29 |
+
USER user
|
| 30 |
+
ENV HOME=/home/user
|
| 31 |
+
ENV PATH=/home/user/.local/bin:/usr/local/bin:/usr/bin:/bin
|
| 32 |
+
|
| 33 |
+
WORKDIR /home/user/app
|
| 34 |
+
|
| 35 |
+
# Install Python dependencies as user
|
| 36 |
+
COPY --chown=user:user requirements.txt .
|
| 37 |
+
RUN pip install --no-cache-dir --user -r requirements.txt
|
| 38 |
+
|
| 39 |
+
# Copy project files with proper ownership
|
| 40 |
+
COPY --chown=user:user . .
|
| 41 |
+
|
| 42 |
+
# Hugging Face Spaces standard port is 7860
|
| 43 |
+
ENV PORT=7860
|
| 44 |
+
EXPOSE 7860
|
| 45 |
+
|
| 46 |
+
CMD ["python", "server.py"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2024-2026 DOOM-FlyWire Connectome Project Contributors
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: DOOM FlyWire Connectome
|
| 3 |
+
emoji: 🪰
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# 🧠 DOOM-FlyWire: 139,248-Neuron Biological Connectome Autonomous Agent & In Silico Neuro-Sandbox
|
| 13 |
+
|
| 14 |
+
[-00f5d4.svg)](https://flywire.ai)
|
| 15 |
+
[-ff0055.svg)](https://vizdoom.farama.org)
|
| 16 |
+
[](#-system-architecture)
|
| 17 |
+
[-76b900.svg)](#-biophysical-engine--performance)
|
| 18 |
+
[](LICENSE)
|
| 19 |
+
|
| 20 |
+
A real-time, closed-loop biological digital twin running the complete, unreduced adult *Drosophila melanogaster* whole-brain connectome ([FlyWire Consortium, *Nature* 2024](https://www.nature.com/articles/s41586-024-07558-y): **139,248 neurons, 15,090,883 directed synapses**) as an autonomous agent playing **DOOM** ([ViZDoom](https://vizdoom.farama.org/)).
|
| 21 |
+
|
| 22 |
+
The project includes an interactive, browser-based **In Silico Neuroscientist Control Console** that allows users to manipulate chemical neuromodulators (*Dopamine, Octopamine, Serotonin, GABA*), induce targeted anatomical optogenetic lesions (*Optic Lobe, Central Complex, Motion Detectors*), and observe behavioral changes and epileptic seizures in real time.
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## 🌟 Key Highlights
|
| 27 |
+
|
| 28 |
+
- **100% Complete Biological Wiring:** Zero neuron downsampling and zero synapse pruning. All 139,248 neurons and 15,090,883 synapses propagate physiological signals in real time.
|
| 29 |
+
- **Microsecond-Scale Biophysical GPU Engine:** Uses sparse matrix-vector multiplication (SpMV) on GPU, achieving **2.5 ms per full-brain step (>200 FPS)** on an NVIDIA RTX 4080.
|
| 30 |
+
- **Compound Eye Retinotopy:** Translates ViZDoom game frames into optical ommatidia currents, stimulating photoreceptors (R1–R8) and Elementary Motion Detectors (T4/T5).
|
| 31 |
+
- **Descending Motor Steering:** Decodes asymmetric population firing of 1,303 Descending Neurons (DNs) into left/right steering, forward thrust, and weapon firing.
|
| 32 |
+
- **In Silico Neuro-Sandbox:** Interactive web dashboard with 12+ real-time chemical and anatomical levers.
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## 🔬 System Architecture
|
| 37 |
+
|
| 38 |
+
```
|
| 39 |
+
[ViZDoom Game Engine]
|
| 40 |
+
(320x240 RGB Screen)
|
| 41 |
+
│
|
| 42 |
+
│ Raw Pixels
|
| 43 |
+
▼
|
| 44 |
+
┌───────────────────────────────────────────────────┐
|
| 45 |
+
│ Compound Eye Retinotopy & Motion Encoder │
|
| 46 |
+
│ - 32x32 Ommatidia Array (1,024 optical facets) │
|
| 47 |
+
│ - Elementary Motion Detection (T4/T5 Columns) │
|
| 48 |
+
└─────────────────────────┬─────────────────────────┘
|
| 49 |
+
│
|
| 50 |
+
│ Injected Photoreceptor Current (R1-R8)
|
| 51 |
+
▼
|
| 52 |
+
┌───────────────────────────────────────────────────┐
|
| 53 |
+
│ 100% FlyWire Whole-Brain Connectome (GPU) │
|
| 54 |
+
│ - 139,248 Biological Neurons │
|
| 55 |
+
│ - 15,090,883 Directed Synapses (ACh, GABA, Glu) │
|
| 56 |
+
│ - Continuous-Time Leaky Rate Dynamics │
|
| 57 |
+
│ │
|
| 58 |
+
│ [LIVE NEUROSCIENTIST CONTROL CONSOLE] │
|
| 59 |
+
│ - Neuromodulator Bath: DA, OA, 5-HT, GABA, ACh │
|
| 60 |
+
│ - Circuit Lesions: Optic, Central Complex, MB │
|
| 61 |
+
│ - Biophysical Levers: Leak, Threshold, Noise │
|
| 62 |
+
└─────────────────────────┬─────────────────────────┘
|
| 63 |
+
│
|
| 64 |
+
│ Population Firing of Descending Neurons
|
| 65 |
+
▼
|
| 66 |
+
┌───────────────────────────────────────────────────┐
|
| 67 |
+
│ Motor Decoder │
|
| 68 |
+
│ - DN_Left vs DN_Right -> Steer Left / Right │
|
| 69 |
+
│ - Bilateral Motor Vigor -> Move Forward │
|
| 70 |
+
│ - Frontal Attack Surge -> Weapon Fire │
|
| 71 |
+
└─────────────────────────┬─────────────────────────┘
|
| 72 |
+
│
|
| 73 |
+
▼
|
| 74 |
+
[Execute Action in DOOM]
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
## 🎮 The Neuroscientist Sandbox Suite
|
| 80 |
+
|
| 81 |
+
The web dashboard provides interactive controls categorized into three biological layers:
|
| 82 |
+
|
| 83 |
+
### 1. Neuromodulatory Chemical Bath
|
| 84 |
+
| Lever | Range | Physiological Mechanism & In-Game Behavioral Effect |
|
| 85 |
+
| :--- | :--- | :--- |
|
| 86 |
+
| **Dopamine Gain** | 0.0x – 3.0x | Modulates motor vigor and reward sensitivity. High DA causes aggressive, fast-twitch tracking; 0.0x causes akinetic freezing. |
|
| 87 |
+
| **Octopamine Gain** | 0.0x – 3.0x | Invertebrate norepinephrine analog. Drives fight-or-flight arousal and rapid retreat responses. |
|
| 88 |
+
| **Serotonin (5-HT)** | 0.0x – 3.0x | Stabilizes motor output and suppresses erratic direction switching. |
|
| 89 |
+
| **GABAergic Inhibition** | 0.0x – 2.5x | Global inhibitory balance. Dropping below 0.2x triggers **Epileptiform Seizures** (global hyper-synchrony, spastic twitching); high values induce sedation. |
|
| 90 |
+
| **Acetylcholine Drive** | 0.1x – 2.0x | Primary excitatory neurotransmitter governing cortical gain and sensory transmission. |
|
| 91 |
+
|
| 92 |
+
### 2. Targeted In Silico Anatomical Lesions (Laser Ablation)
|
| 93 |
+
- **Blind Left Eye / Blind Right Eye:** Silences sensory input to the corresponding optic lobe; the agent completely ignores threats on the blinded side.
|
| 94 |
+
- **Silence Motion Detectors (T4/T5 Columnar Knockout):** Destroys optical flow perception; the fly perceives static textures but fails to dodge incoming projectiles.
|
| 95 |
+
- **Lesion Central Complex (CX):** Destroys the heading compass (Protocerebral Bridge & Fan-Shaped Body); the agent loses heading stability and spins in circles.
|
| 96 |
+
- **Lesion Mushroom Body (Kenyon Cells):** Disrupts associative memory subcircuits.
|
| 97 |
+
- **Total Motor Paralysis (DN Silencing):** All descending motor neurons are silenced; the brain remains active and perceives threats, but the character is completely paralyzed.
|
| 98 |
+
|
| 99 |
+
### 3. Biophysical Dynamics
|
| 100 |
+
- **Membrane Leak Rate (tau):** Temporal integration window of biological neurons.
|
| 101 |
+
- **Firing Threshold:** Voltage sensitivity for non-linear sigmoidal activation.
|
| 102 |
+
- **Synaptic Thermal Noise:** Stochastic exploration vs. deterministic execution.
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
|
| 106 |
+
## ⚡ Biophysical Engine & Performance
|
| 107 |
+
|
| 108 |
+
The simulator implements continuous-time leaky rate dynamics over sparse matrix-vector multiplication (SpMV):
|
| 109 |
+
|
| 110 |
+
```
|
| 111 |
+
V[t+1] = (1 - leak_rate) * V[t] + W_syn @ act[t] + I_sensory[t] + noise
|
| 112 |
+
act[t+1] = sigmoid((V[t+1] - threshold) * 5.0) * lesion_mask
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
On an **NVIDIA GeForce RTX 4080 Laptop GPU (12 GB VRAM)**:
|
| 116 |
+
- **Graph Allocation:** 60.4 MB for 15,090,883 sparse signed synapses.
|
| 117 |
+
- **Single-Step Propagation:** **2.53 ms** (395 FPS isolated engine throughput).
|
| 118 |
+
- **Closed-Loop System:** **71.5 FPS** (including ViZDoom rendering, retinotopy encoding, 15M synapse propagation, and motor decoding).
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
## 📁 Repository Structure
|
| 123 |
+
|
| 124 |
+
```
|
| 125 |
+
.
|
| 126 |
+
├── data/
|
| 127 |
+
│ ├── Supplemental_file1_neuron_annotations.tsv # FlyWire 139,248 neuron annotations
|
| 128 |
+
│ ├── proofread_connections_783.feather # 15,090,883 directed synapses
|
| 129 |
+
│ └── connectome_cache.pt # Pre-built PyTorch sparse graph tensor
|
| 130 |
+
├── models/ # Trained checkpoint weights (.pt)
|
| 131 |
+
├── src/
|
| 132 |
+
│ ├── __init__.py # Package initialization
|
| 133 |
+
│ ├── graph_loader.py # Fast FlyWire connectome loader
|
| 134 |
+
│ ├── connectome_engine.py # 15M-synapse biophysical GPU dynamics engine
|
| 135 |
+
│ ├── retinotopy_encoder.py # Compound eye & motion optical flow encoder
|
| 136 |
+
│ ├── motor_decoder.py # Descending neuron population motor decoder
|
| 137 |
+
│ └── doom_agent.py # Closed-loop ViZDoom connectome agent
|
| 138 |
+
├── web/
|
| 139 |
+
│ ├── js/ # Local Three.js and OrbitControls vendor bundle
|
| 140 |
+
│ ├── brain_139k_pos.bin # 139,248 neuron 3D coordinates (1.67 MB)
|
| 141 |
+
│ ├── brain_139k_col.bin # 139,248 neuron anatomical colors (417 KB)
|
| 142 |
+
│ └── index.html # 3D Connectome and Perception-Action Dashboard
|
| 143 |
+
├── server.py # FastAPI + WebSocket live streaming server
|
| 144 |
+
├── train_doom.py # Connectome policy reinforcement training loop
|
| 145 |
+
├── Dockerfile # Hugging Face Spaces and Docker container build
|
| 146 |
+
├── .dockerignore # Container build exclusions
|
| 147 |
+
├── .gitattributes # Git LFS binary dataset tracking
|
| 148 |
+
├── .gitignore # Ignored virtual environments and artifacts
|
| 149 |
+
├── requirements.txt # Python dependencies
|
| 150 |
+
├── LICENSE # MIT License
|
| 151 |
+
└── README.md
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
---
|
| 155 |
+
|
| 156 |
+
## 🚀 Quickstart
|
| 157 |
+
|
| 158 |
+
### 1. Prerequisites & Environment
|
| 159 |
+
Ensure you have Python 3.10+ and a CUDA-capable GPU:
|
| 160 |
+
```bash
|
| 161 |
+
git clone https://github.com/your-username/fly.git
|
| 162 |
+
cd fly
|
| 163 |
+
|
| 164 |
+
# Create and activate virtual environment
|
| 165 |
+
python -m venv .venv
|
| 166 |
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
| 167 |
+
|
| 168 |
+
# Install dependencies
|
| 169 |
+
pip install -r requirements.txt
|
| 170 |
+
pip install torch --index-url https://download.pytorch.org/whl/cu124
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
### 2. Launch the Web Sandbox
|
| 174 |
+
Start the FastAPI streaming server:
|
| 175 |
+
```bash
|
| 176 |
+
python server.py
|
| 177 |
+
```
|
| 178 |
+
Open your browser at **`http://localhost:8000`**. You will see:
|
| 179 |
+
- The live DOOM viewport controlled by the fruit fly brain.
|
| 180 |
+
- Real-time firing rate gauges of the Optic Lobe, Central Complex, and Descending Motor Neurons.
|
| 181 |
+
- The interactive sandbox panel with sliders and toggle switches.
|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
## 📚 References & Scientific Citations
|
| 186 |
+
|
| 187 |
+
1. **FlyWire Whole-Brain Connectome:**
|
| 188 |
+
Dorkenwald, S., Matsliah, A., Sterling, P., et al. (2024). *Neuronal wiring diagram of an adult brain*. **Nature**, 634, 124–138.
|
| 189 |
+
2. **ViZDoom RL Platform:**
|
| 190 |
+
Wydmuch, M., Kempka, M., & Jaśkowski, W. (2018). *ViZDoom Competitions: Playing Doom from Pixels*. **IEEE Transactions on Games**, 11(3), 248–258.
|
| 191 |
+
3. **Drosophila Motion Vision (T4/T5 Circuits):**
|
| 192 |
+
Maisak, M. S., Haag, J., Ammer, G., et al. (2013). *A directional tuning map of Drosophila elementary motion detectors*. **Nature**, 500(7461), 212–216.
|
| 193 |
+
4. **Central Complex Navigation & Heading Compass:**
|
| 194 |
+
Seelig, J. D., & Jayaraman, V. (2015). *Neural dynamics for landmark orientation and angular path integration*. **Nature**, 521(7551), 186–191.
|
data/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Placeholder to ensure data directory is tracked in git
|
models/.gitkeep
ADDED
|
File without changes
|
requirements.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DOOM-FlyWire Requirements
|
| 2 |
+
# Compatible with Python >= 3.10 and CUDA-enabled PyTorch
|
| 3 |
+
|
| 4 |
+
numpy>=1.23.0
|
| 5 |
+
pandas>=1.5.0
|
| 6 |
+
scipy>=1.10.0
|
| 7 |
+
pyarrow>=11.0.0
|
| 8 |
+
matplotlib>=3.7.0
|
| 9 |
+
vizdoom>=1.2.0
|
| 10 |
+
torch>=2.0.0
|
| 11 |
+
pillow>=9.5.0
|
| 12 |
+
fastapi>=0.100.0
|
| 13 |
+
uvicorn>=0.22.0
|
| 14 |
+
websockets>=11.0
|
| 15 |
+
tqdm>=4.65.0
|
server.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
DOOM-FlyWire Real-Time Web Server
|
| 3 |
+
=================================
|
| 4 |
+
FastAPI + WebSockets server streaming live ViZDoom gameplay, 139k-neuron
|
| 5 |
+
connectome telemetry, and accepting real-time neuroscientist sandbox controls.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import json
|
| 11 |
+
import time
|
| 12 |
+
import asyncio
|
| 13 |
+
import torch
|
| 14 |
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
| 15 |
+
from fastapi.staticfiles import StaticFiles
|
| 16 |
+
from fastapi.responses import FileResponse
|
| 17 |
+
|
| 18 |
+
from src.doom_agent import DoomConnectomeAgent
|
| 19 |
+
|
| 20 |
+
app = FastAPI(title="DOOM-FlyWire Connectome Sandbox")
|
| 21 |
+
|
| 22 |
+
# Global Agent Instance
|
| 23 |
+
agent = None
|
| 24 |
+
|
| 25 |
+
@app.on_event("startup")
|
| 26 |
+
def startup_event():
|
| 27 |
+
global agent
|
| 28 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 29 |
+
print(f"[Server] Initializing DOOM-FlyWire Agent on {device} (defend_the_center)...")
|
| 30 |
+
agent = DoomConnectomeAgent(scenario_name="defend_the_center.cfg", window_visible=False, device=device)
|
| 31 |
+
print("[Server] Agent ready! Listening for web client connections.")
|
| 32 |
+
|
| 33 |
+
@app.on_event("shutdown")
|
| 34 |
+
def shutdown_event():
|
| 35 |
+
global agent
|
| 36 |
+
if agent is not None:
|
| 37 |
+
agent.close()
|
| 38 |
+
|
| 39 |
+
# Serve static web assets
|
| 40 |
+
WEB_DIR = os.path.join(os.path.dirname(__file__), "web")
|
| 41 |
+
os.makedirs(WEB_DIR, exist_ok=True)
|
| 42 |
+
app.mount("/static", StaticFiles(directory=WEB_DIR), name="static")
|
| 43 |
+
|
| 44 |
+
@app.get("/")
|
| 45 |
+
async def get_index():
|
| 46 |
+
index_file = os.path.join(WEB_DIR, "index.html")
|
| 47 |
+
if os.path.exists(index_file):
|
| 48 |
+
return FileResponse(index_file)
|
| 49 |
+
return {"message": "DOOM-FlyWire Server Running. Web UI not found in web/."}
|
| 50 |
+
|
| 51 |
+
@app.get("/brain_anatomy.json")
|
| 52 |
+
async def get_brain_anatomy():
|
| 53 |
+
anatomy_file = os.path.join(WEB_DIR, "brain_anatomy.json")
|
| 54 |
+
if os.path.exists(anatomy_file):
|
| 55 |
+
return FileResponse(anatomy_file)
|
| 56 |
+
return {"error": "Not found"}
|
| 57 |
+
|
| 58 |
+
@app.get("/brain_anatomy_3d.json")
|
| 59 |
+
async def get_brain_anatomy_3d():
|
| 60 |
+
anatomy_file = os.path.join(WEB_DIR, "brain_anatomy_3d.json")
|
| 61 |
+
if os.path.exists(anatomy_file):
|
| 62 |
+
return FileResponse(anatomy_file)
|
| 63 |
+
return {"error": "Not found"}
|
| 64 |
+
|
| 65 |
+
@app.get("/brain_139k_pos.bin")
|
| 66 |
+
async def get_brain_139k_pos():
|
| 67 |
+
pos_file = os.path.join(WEB_DIR, "brain_139k_pos.bin")
|
| 68 |
+
if os.path.exists(pos_file):
|
| 69 |
+
return FileResponse(pos_file, media_type="application/octet-stream")
|
| 70 |
+
return {"error": "Not found"}
|
| 71 |
+
|
| 72 |
+
@app.get("/brain_139k_col.bin")
|
| 73 |
+
async def get_brain_139k_col():
|
| 74 |
+
col_file = os.path.join(WEB_DIR, "brain_139k_col.bin")
|
| 75 |
+
if os.path.exists(col_file):
|
| 76 |
+
return FileResponse(col_file, media_type="application/octet-stream")
|
| 77 |
+
return {"error": "Not found"}
|
| 78 |
+
|
| 79 |
+
@app.get("/synapses_top5k.bin")
|
| 80 |
+
async def get_synapses_top5k():
|
| 81 |
+
syn_file = os.path.join(WEB_DIR, "synapses_top5k.bin")
|
| 82 |
+
if os.path.exists(syn_file):
|
| 83 |
+
return FileResponse(syn_file, media_type="application/octet-stream")
|
| 84 |
+
return {"error": "Not found"}
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
@app.get("/js/three.min.js")
|
| 88 |
+
async def get_three_js():
|
| 89 |
+
js_file = os.path.join(WEB_DIR, "js", "three.min.js")
|
| 90 |
+
if os.path.exists(js_file):
|
| 91 |
+
return FileResponse(js_file, media_type="application/javascript")
|
| 92 |
+
return {"error": "Not found"}
|
| 93 |
+
|
| 94 |
+
@app.get("/js/OrbitControls.js")
|
| 95 |
+
async def get_orbit_controls():
|
| 96 |
+
js_file = os.path.join(WEB_DIR, "js", "OrbitControls.js")
|
| 97 |
+
if os.path.exists(js_file):
|
| 98 |
+
return FileResponse(js_file, media_type="application/javascript")
|
| 99 |
+
return {"error": "Not found"}
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@app.websocket("/ws/game")
|
| 103 |
+
async def websocket_game_stream(websocket: WebSocket):
|
| 104 |
+
global agent
|
| 105 |
+
await websocket.accept()
|
| 106 |
+
print("[WebSocket] Client connected to live neural stream.")
|
| 107 |
+
if agent is not None:
|
| 108 |
+
agent.is_running = True
|
| 109 |
+
|
| 110 |
+
fps_base = 20.0 # smooth natural human viewing speed
|
| 111 |
+
speed_multiplier = 1.0
|
| 112 |
+
frame_interval = (1.0 / fps_base) / speed_multiplier
|
| 113 |
+
|
| 114 |
+
try:
|
| 115 |
+
while True:
|
| 116 |
+
t_start = time.perf_counter()
|
| 117 |
+
|
| 118 |
+
# 1. Check for incoming parameter adjustments from web UI
|
| 119 |
+
try:
|
| 120 |
+
# Non-blocking receive
|
| 121 |
+
data_text = await asyncio.wait_for(websocket.receive_text(), timeout=0.001)
|
| 122 |
+
msg = json.loads(data_text)
|
| 123 |
+
if msg.get("type") == "update_params":
|
| 124 |
+
agent.update_sandbox(msg.get("params", {}))
|
| 125 |
+
elif msg.get("type") in ("reset", "start"):
|
| 126 |
+
agent.reset_episode()
|
| 127 |
+
elif msg.get("type") == "toggle_pause":
|
| 128 |
+
agent.toggle_pause()
|
| 129 |
+
elif msg.get("type") == "set_speed":
|
| 130 |
+
speed_multiplier = max(0.25, min(3.0, float(msg.get("speed", 1.0))))
|
| 131 |
+
frame_interval = (1.0 / fps_base) / speed_multiplier
|
| 132 |
+
except asyncio.TimeoutError:
|
| 133 |
+
pass
|
| 134 |
+
except json.JSONDecodeError:
|
| 135 |
+
pass
|
| 136 |
+
|
| 137 |
+
# 2. Step the 139k-neuron closed-loop agent
|
| 138 |
+
step_data = agent.step()
|
| 139 |
+
if not step_data:
|
| 140 |
+
await asyncio.sleep(0.01)
|
| 141 |
+
continue
|
| 142 |
+
|
| 143 |
+
# 3. Add timestamp and compute instantaneous FPS
|
| 144 |
+
t_step = time.perf_counter() - t_start
|
| 145 |
+
step_data["instant_fps"] = round(1.0 / max(t_step, 1e-4), 1)
|
| 146 |
+
|
| 147 |
+
# 4. Stream frame & brain telemetry to client
|
| 148 |
+
await websocket.send_json(step_data)
|
| 149 |
+
|
| 150 |
+
# Maintain smooth frame rate
|
| 151 |
+
elapsed = time.perf_counter() - t_start
|
| 152 |
+
sleep_time = max(0.0, frame_interval - elapsed)
|
| 153 |
+
if sleep_time > 0:
|
| 154 |
+
await asyncio.sleep(sleep_time)
|
| 155 |
+
|
| 156 |
+
except WebSocketDisconnect:
|
| 157 |
+
print("[WebSocket] Client disconnected.")
|
| 158 |
+
except Exception as e:
|
| 159 |
+
print(f"[WebSocket Error] {e}")
|
| 160 |
+
|
| 161 |
+
if __name__ == "__main__":
|
| 162 |
+
import uvicorn
|
| 163 |
+
port = int(os.environ.get("PORT", 8000))
|
| 164 |
+
print(f"[Server] Starting DOOM-FlyWire on 0.0.0.0:{port}...")
|
| 165 |
+
uvicorn.run("server:app", host="0.0.0.0", port=port, reload=False)
|
src/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
DOOM-FlyWire: 139,248-Neuron Biological Connectome Autonomous Agent
|
| 3 |
+
===================================================================
|
| 4 |
+
A real-time whole-brain biological digital twin mapping the complete adult
|
| 5 |
+
Drosophila melanogaster connectome (FlyWire Nature 2024: 139,248 neurons,
|
| 6 |
+
15,090,883 synapses) to ViZDoom with an interactive in silico neuro-sandbox.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from .graph_loader import FlyConnectomeLoader, load_flywire_graph
|
| 10 |
+
|
| 11 |
+
__all__ = [
|
| 12 |
+
"FlyConnectomeLoader",
|
| 13 |
+
"load_flywire_graph",
|
| 14 |
+
]
|
src/connectome_engine.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
DOOM-FlyWire Connectome Engine
|
| 3 |
+
==============================
|
| 4 |
+
Real-time biophysical neural dynamics simulator running on the complete,
|
| 5 |
+
unreduced 139,248-neuron and 15,090,883-synapse adult Drosophila connectome
|
| 6 |
+
(FlyWire Nature 2024).
|
| 7 |
+
|
| 8 |
+
Implements:
|
| 9 |
+
- Continuous-time Leaky Rate / Integrate dynamics at 300+ FPS on GPU.
|
| 10 |
+
- Synaptic polarity: Acetylcholine (+), GABA (-), Glutamate (-), Dopamine (~), Serotonin (~), Octopamine (~).
|
| 11 |
+
- Live Neuroscientist Sandbox: real-time chemical bath modulation & anatomical lesioning.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import os
|
| 15 |
+
import sys
|
| 16 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 17 |
+
import time
|
| 18 |
+
import numpy as np
|
| 19 |
+
import pandas as pd
|
| 20 |
+
import torch
|
| 21 |
+
import torch.nn.functional as F
|
| 22 |
+
from typing import Dict, Any, Tuple, Optional
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
from .graph_loader import load_flywire_graph
|
| 26 |
+
except (ImportError, ValueError):
|
| 27 |
+
from src.graph_loader import load_flywire_graph
|
| 28 |
+
|
| 29 |
+
class ConnectomeEngine:
|
| 30 |
+
def __init__(self, data_dir: str = "data", cache_file: str = "data/connectome_cache.pt", device: str = "cuda"):
|
| 31 |
+
self.device = torch.device(device if torch.cuda.is_available() and device == "cuda" else "cpu")
|
| 32 |
+
print(f"[ConnectomeEngine] Initializing 100% FlyWire Brain on {self.device}...")
|
| 33 |
+
|
| 34 |
+
# 1. Load connectome sparse adjacency & metadata
|
| 35 |
+
t0 = time.perf_counter()
|
| 36 |
+
self.adj_matrix, self.meta, self.coords = load_flywire_graph(data_dir=data_dir, cache_file=cache_file, device=self.device)
|
| 37 |
+
self.num_neurons = self.adj_matrix.size(0)
|
| 38 |
+
self.num_synapses = self.adj_matrix._nnz()
|
| 39 |
+
print(f"[ConnectomeEngine] Loaded {self.num_neurons:,} neurons and {self.num_synapses:,} synapses in {time.perf_counter()-t0:.2f}s.")
|
| 40 |
+
|
| 41 |
+
# 2. Extract circuit indices for targeted lesioning & readout
|
| 42 |
+
self._build_circuit_indices()
|
| 43 |
+
|
| 44 |
+
# 3. Initialize state variables
|
| 45 |
+
self.V = torch.zeros(self.num_neurons, device=self.device, dtype=torch.float32)
|
| 46 |
+
self.act = torch.zeros(self.num_neurons, device=self.device, dtype=torch.float32)
|
| 47 |
+
|
| 48 |
+
# 4. Neuroscientist Sandbox Parameters (Defaults)
|
| 49 |
+
self.params = {
|
| 50 |
+
# Chemical Bath (Neuromodulators & Neurotransmitters)
|
| 51 |
+
"dopamine_gain": 1.0, # Motor speed, vigor, reward
|
| 52 |
+
"octopamine_gain": 1.0, # Fight-or-flight arousal, escape response
|
| 53 |
+
"serotonin_gain": 1.0, # Steering stability, patience
|
| 54 |
+
"gaba_gain": 1.0, # Inhibitory tone (<0.2 triggers epileptic seizure!)
|
| 55 |
+
"ach_gain": 1.0, # Excitatory transmission drive
|
| 56 |
+
"glutamate_gain": 1.0, # Glutamatergic tone
|
| 57 |
+
|
| 58 |
+
# Anatomical Circuit Lesions (In Silico Optogenetic Knockouts)
|
| 59 |
+
"lesion_optic_left": False,
|
| 60 |
+
"lesion_optic_right": False,
|
| 61 |
+
"lesion_motion_t4t5": False,
|
| 62 |
+
"lesion_central_complex": False,
|
| 63 |
+
"lesion_mushroom_body": False,
|
| 64 |
+
"lesion_descending_left": False,
|
| 65 |
+
"lesion_descending_right": False,
|
| 66 |
+
"lesion_all_motor": False,
|
| 67 |
+
|
| 68 |
+
# Biophysical Parameters
|
| 69 |
+
"leak_rate": 0.25, # Membrane decay (tau)
|
| 70 |
+
"firing_threshold": 0.45, # Activation threshold
|
| 71 |
+
"synaptic_noise": 0.05, # Thermal stochastic jitter
|
| 72 |
+
"gain_global": 1.2 # Overall synaptic transmission strength
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
# Build initial lesion mask tensor (1.0 = alive, 0.0 = silenced)
|
| 76 |
+
self.lesion_mask = torch.ones(self.num_neurons, device=self.device, dtype=torch.float32)
|
| 77 |
+
self._update_lesion_mask()
|
| 78 |
+
|
| 79 |
+
def _build_circuit_indices(self):
|
| 80 |
+
"""Pre-indexes neuron subgroups for rapid masking, modulation, and motor readout."""
|
| 81 |
+
meta = self.meta
|
| 82 |
+
|
| 83 |
+
# Anatomical super classes
|
| 84 |
+
self.optic_mask = (meta["super_class"] == "optic").values
|
| 85 |
+
self.central_mask = (meta["super_class"] == "central").values
|
| 86 |
+
self.descending_mask = (meta["super_class"] == "descending").values
|
| 87 |
+
|
| 88 |
+
# Left vs Right hemispheres
|
| 89 |
+
sides = meta["side"].fillna("center").values
|
| 90 |
+
self.optic_left_indices = np.where(self.optic_mask & (sides == "left"))[0]
|
| 91 |
+
self.optic_right_indices = np.where(self.optic_mask & (sides == "right"))[0]
|
| 92 |
+
|
| 93 |
+
self.dn_left_indices = np.where(self.descending_mask & (sides == "left"))[0]
|
| 94 |
+
self.dn_right_indices = np.where(self.descending_mask & (sides == "right"))[0]
|
| 95 |
+
self.dn_all_indices = np.where(self.descending_mask)[0]
|
| 96 |
+
|
| 97 |
+
# Central Complex (Heading compass / navigation) & Kenyon Cells (Mushroom Body)
|
| 98 |
+
cell_classes = meta["cell_class"].fillna("").values
|
| 99 |
+
self.cx_indices = np.where(cell_classes == "CX")[0]
|
| 100 |
+
self.kc_indices = np.where(cell_classes == "Kenyon_Cell")[0]
|
| 101 |
+
|
| 102 |
+
# Motion detection columnar cells (T4 and T5)
|
| 103 |
+
cell_types = meta["cell_type"].fillna("").values
|
| 104 |
+
t4t5_mask = np.array([any(t.startswith(prefix) for prefix in ["T4", "T5"]) for t in cell_types])
|
| 105 |
+
self.t4t5_indices = np.where(t4t5_mask)[0]
|
| 106 |
+
|
| 107 |
+
# Neurotransmitter masks (based on presynaptic neuron type)
|
| 108 |
+
top_nt = meta["top_nt"].fillna("unknown").str.lower().values
|
| 109 |
+
self.ach_indices = np.where(top_nt == "acetylcholine")[0]
|
| 110 |
+
self.gaba_indices = np.where(top_nt == "gaba")[0]
|
| 111 |
+
self.glu_indices = np.where(top_nt == "glutamate")[0]
|
| 112 |
+
self.da_indices = np.where(top_nt == "dopamine")[0]
|
| 113 |
+
self.ser_indices = np.where(top_nt == "serotonin")[0]
|
| 114 |
+
self.oct_indices = np.where(top_nt == "octopamine")[0]
|
| 115 |
+
|
| 116 |
+
# Convert to GPU tensors
|
| 117 |
+
self.t_optic_left = torch.from_numpy(self.optic_left_indices).to(self.device)
|
| 118 |
+
self.t_optic_right = torch.from_numpy(self.optic_right_indices).to(self.device)
|
| 119 |
+
self.t_cx = torch.from_numpy(self.cx_indices).to(self.device)
|
| 120 |
+
self.t_kc = torch.from_numpy(self.kc_indices).to(self.device)
|
| 121 |
+
self.t_t4t5 = torch.from_numpy(self.t4t5_indices).to(self.device)
|
| 122 |
+
self.t_dn_left = torch.from_numpy(self.dn_left_indices).to(self.device)
|
| 123 |
+
self.t_dn_right = torch.from_numpy(self.dn_right_indices).to(self.device)
|
| 124 |
+
self.t_dn_all = torch.from_numpy(self.dn_all_indices).to(self.device)
|
| 125 |
+
|
| 126 |
+
print(f" -> Circuit Breakdown: Optic={self.optic_mask.sum():,} (L={len(self.optic_left_indices):,}, R={len(self.optic_right_indices):,})")
|
| 127 |
+
print(f" -> Navigation (CX)={len(self.cx_indices):,} | Memory (Kenyon)={len(self.kc_indices):,} | Motion (T4/T5)={len(self.t4t5_indices):,}")
|
| 128 |
+
print(f" -> Descending Motor={len(self.dn_all_indices):,} (L={len(self.dn_left_indices):,}, R={len(self.dn_right_indices):,})")
|
| 129 |
+
|
| 130 |
+
def set_parameters(self, update_dict: Dict[str, Any]):
|
| 131 |
+
"""Live update of chemical sliders, lesion switches, and biophysical constants."""
|
| 132 |
+
changed = False
|
| 133 |
+
for k, v in update_dict.items():
|
| 134 |
+
if k in self.params:
|
| 135 |
+
self.params[k] = v
|
| 136 |
+
changed = True
|
| 137 |
+
if changed:
|
| 138 |
+
self._update_lesion_mask()
|
| 139 |
+
|
| 140 |
+
def _update_lesion_mask(self):
|
| 141 |
+
"""Reconstructs the binary lesion mask based on user switches."""
|
| 142 |
+
mask = torch.ones(self.num_neurons, device=self.device, dtype=torch.float32)
|
| 143 |
+
if self.params["lesion_optic_left"]:
|
| 144 |
+
mask[self.t_optic_left] = 0.0
|
| 145 |
+
if self.params["lesion_optic_right"]:
|
| 146 |
+
mask[self.t_optic_right] = 0.0
|
| 147 |
+
if self.params["lesion_motion_t4t5"]:
|
| 148 |
+
mask[self.t_t4t5] = 0.0
|
| 149 |
+
if self.params["lesion_central_complex"]:
|
| 150 |
+
mask[self.t_cx] = 0.0
|
| 151 |
+
if self.params["lesion_mushroom_body"]:
|
| 152 |
+
mask[self.t_kc] = 0.0
|
| 153 |
+
if self.params["lesion_descending_left"]:
|
| 154 |
+
mask[self.t_dn_left] = 0.0
|
| 155 |
+
if self.params["lesion_descending_right"]:
|
| 156 |
+
mask[self.t_dn_right] = 0.0
|
| 157 |
+
if self.params["lesion_all_motor"]:
|
| 158 |
+
mask[self.t_dn_all] = 0.0
|
| 159 |
+
self.lesion_mask = mask
|
| 160 |
+
|
| 161 |
+
def reset_state(self):
|
| 162 |
+
"""Resets membrane potentials and firing states."""
|
| 163 |
+
self.V.zero_()
|
| 164 |
+
self.act.zero_()
|
| 165 |
+
|
| 166 |
+
def step(self, sensory_input: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, Dict[str, float]]:
|
| 167 |
+
"""
|
| 168 |
+
Executes one continuous-time neural dynamic step across all 139,248 neurons.
|
| 169 |
+
|
| 170 |
+
sensory_input: optional [139248] current tensor from visual encoder.
|
| 171 |
+
Returns:
|
| 172 |
+
act: [139248] firing rates (0.0 to 1.0)
|
| 173 |
+
diagnostics: dictionary of regional firing rates and telemetry.
|
| 174 |
+
"""
|
| 175 |
+
# 1. Apply chemical bath multipliers onto effective presynaptic activity
|
| 176 |
+
# (Modulating neurotransmitter pools without modifying the 15M sparse matrix structure!)
|
| 177 |
+
act_mod = self.act.clone()
|
| 178 |
+
|
| 179 |
+
# GABAergic inhibition modulation (if gaba < 0.2 -> severe disinhibition / seizure)
|
| 180 |
+
if self.params["gaba_gain"] != 1.0:
|
| 181 |
+
act_mod[self.gaba_indices] *= self.params["gaba_gain"]
|
| 182 |
+
|
| 183 |
+
# Cholinergic & Glutamatergic excitation
|
| 184 |
+
if self.params["ach_gain"] != 1.0:
|
| 185 |
+
act_mod[self.ach_indices] *= self.params["ach_gain"]
|
| 186 |
+
if self.params["glutamate_gain"] != 1.0:
|
| 187 |
+
act_mod[self.glu_indices] *= self.params["glutamate_gain"]
|
| 188 |
+
|
| 189 |
+
# Neuromodulators
|
| 190 |
+
if self.params["dopamine_gain"] != 1.0:
|
| 191 |
+
act_mod[self.da_indices] *= self.params["dopamine_gain"]
|
| 192 |
+
if self.params["octopamine_gain"] != 1.0:
|
| 193 |
+
act_mod[self.oct_indices] *= self.params["octopamine_gain"]
|
| 194 |
+
if self.params["serotonin_gain"] != 1.0:
|
| 195 |
+
act_mod[self.ser_indices] *= self.params["serotonin_gain"]
|
| 196 |
+
|
| 197 |
+
# 2. Fast Sparse Synaptic Current Integration (15,090,883 synapses on GPU)
|
| 198 |
+
# Scaled by physiological conductance factor (0.002) so 15M synapses do not cause chronic baseline saturation
|
| 199 |
+
I_syn = torch.sparse.mm(self.adj_matrix, act_mod.unsqueeze(-1)).squeeze(-1)
|
| 200 |
+
I_syn *= (self.params["gain_global"] * 0.002)
|
| 201 |
+
|
| 202 |
+
# 3. Add sensory input & thermal noise
|
| 203 |
+
I_tot = I_syn
|
| 204 |
+
if sensory_input is not None:
|
| 205 |
+
I_tot = I_tot + sensory_input
|
| 206 |
+
|
| 207 |
+
if self.params["synaptic_noise"] > 0:
|
| 208 |
+
I_tot = I_tot + torch.randn_like(self.V) * self.params["synaptic_noise"]
|
| 209 |
+
|
| 210 |
+
# 4. Leaky membrane potential update: V[t+1] = (1 - leak) * V[t] + I_tot
|
| 211 |
+
leak = self.params["leak_rate"]
|
| 212 |
+
self.V = (1.0 - leak) * self.V + I_tot
|
| 213 |
+
|
| 214 |
+
# 5. Non-linear activation (Sigmoidal firing with threshold)
|
| 215 |
+
threshold = self.params["firing_threshold"]
|
| 216 |
+
raw_act = torch.sigmoid((self.V - threshold) * 5.0)
|
| 217 |
+
|
| 218 |
+
# 6. Apply anatomical lesion mask (killed circuits output 0.0)
|
| 219 |
+
self.act = raw_act * self.lesion_mask
|
| 220 |
+
|
| 221 |
+
# 7. Compute regional telemetry for real-time dashboard
|
| 222 |
+
with torch.no_grad():
|
| 223 |
+
opt_l = float(self.act[self.t_optic_left].mean().item())
|
| 224 |
+
opt_r = float(self.act[self.t_optic_right].mean().item())
|
| 225 |
+
cx_act = float(self.act[self.t_cx].mean().item())
|
| 226 |
+
kc_act = float(self.act[self.t_kc].mean().item())
|
| 227 |
+
dn_l = float(self.act[self.t_dn_left].mean().item())
|
| 228 |
+
dn_r = float(self.act[self.t_dn_right].mean().item())
|
| 229 |
+
whole_mean = float(self.act.mean().item())
|
| 230 |
+
|
| 231 |
+
# Detect epileptiform seizure (global hyper-synchrony)
|
| 232 |
+
is_seizure = bool(whole_mean > 0.55 or (self.params["gaba_gain"] < 0.25 and whole_mean > 0.40))
|
| 233 |
+
|
| 234 |
+
telemetry = {
|
| 235 |
+
"optic_left": opt_l,
|
| 236 |
+
"optic_right": opt_r,
|
| 237 |
+
"central_complex": cx_act,
|
| 238 |
+
"mushroom_body": kc_act,
|
| 239 |
+
"descending_left": dn_l,
|
| 240 |
+
"descending_right": dn_r,
|
| 241 |
+
"whole_brain_firing": whole_mean,
|
| 242 |
+
"is_seizure": is_seizure
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
return self.act, telemetry
|
| 246 |
+
|
| 247 |
+
def get_telemetry(self) -> dict:
|
| 248 |
+
"""Returns snapshot of current regional circuit firing rates."""
|
| 249 |
+
with torch.no_grad():
|
| 250 |
+
opt_l = float(self.act[self.t_optic_left].mean().item())
|
| 251 |
+
opt_r = float(self.act[self.t_optic_right].mean().item())
|
| 252 |
+
cx_act = float(self.act[self.t_cx].mean().item())
|
| 253 |
+
kc_act = float(self.act[self.t_kc].mean().item())
|
| 254 |
+
dn_l = float(self.act[self.t_dn_left].mean().item())
|
| 255 |
+
dn_r = float(self.act[self.t_dn_right].mean().item())
|
| 256 |
+
whole_mean = float(self.act.mean().item())
|
| 257 |
+
is_seizure = bool(whole_mean > 0.45 or (self.params["gaba_gain"] < 0.25 and whole_mean > 0.30))
|
| 258 |
+
|
| 259 |
+
return {
|
| 260 |
+
"optic_left": opt_l,
|
| 261 |
+
"optic_right": opt_r,
|
| 262 |
+
"central_complex": cx_act,
|
| 263 |
+
"mushroom_body": kc_act,
|
| 264 |
+
"descending_left": dn_l,
|
| 265 |
+
"descending_right": dn_r,
|
| 266 |
+
"whole_brain_firing": whole_mean,
|
| 267 |
+
"is_seizure": is_seizure
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
if __name__ == "__main__":
|
| 271 |
+
engine = ConnectomeEngine()
|
| 272 |
+
print("Testing 100 steps of full-brain neural dynamics...")
|
| 273 |
+
t0 = time.perf_counter()
|
| 274 |
+
for step in range(100):
|
| 275 |
+
dummy_sensory = torch.zeros(engine.num_neurons, device=engine.device)
|
| 276 |
+
dummy_sensory[engine.t_optic_left[:500]] = 1.5
|
| 277 |
+
act, diag = engine.step(dummy_sensory)
|
| 278 |
+
torch.cuda.synchronize()
|
| 279 |
+
elapsed = time.perf_counter() - t0
|
| 280 |
+
print(f"Completed 100 steps in {elapsed:.3f}s ({100/elapsed:.1f} FPS)!")
|
| 281 |
+
print("Final Telemetry:", diag)
|
src/doom_agent.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
DOOM-FlyWire Agent
|
| 3 |
+
==================
|
| 4 |
+
Closed-loop autonomous agent connecting the ViZDoom environment to the
|
| 5 |
+
139,248-neuron FlyWire connectome.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import io
|
| 11 |
+
import base64
|
| 12 |
+
import time
|
| 13 |
+
import numpy as np
|
| 14 |
+
from PIL import Image
|
| 15 |
+
import vizdoom as vzd
|
| 16 |
+
import torch
|
| 17 |
+
|
| 18 |
+
from .connectome_engine import ConnectomeEngine
|
| 19 |
+
from .retinotopy_encoder import RetinotopyEncoder
|
| 20 |
+
from .motor_decoder import MotorDecoder
|
| 21 |
+
|
| 22 |
+
class DoomConnectomeAgent:
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
scenario_name: str = "defend_the_center.cfg",
|
| 26 |
+
window_visible: bool = False,
|
| 27 |
+
device: str = "cuda"
|
| 28 |
+
):
|
| 29 |
+
print(f"[DoomAgent] Initializing DOOM-FlyWire Agent on {device}...")
|
| 30 |
+
self.device = device
|
| 31 |
+
|
| 32 |
+
# 1. Connectome Neural Engine (100% FlyWire)
|
| 33 |
+
self.engine = ConnectomeEngine(device=device)
|
| 34 |
+
|
| 35 |
+
# 2. Retinotopy Encoder
|
| 36 |
+
self.encoder = RetinotopyEncoder(
|
| 37 |
+
num_neurons=self.engine.num_neurons,
|
| 38 |
+
optic_left_indices=self.engine.optic_left_indices,
|
| 39 |
+
optic_right_indices=self.engine.optic_right_indices,
|
| 40 |
+
t4t5_indices=self.engine.t4t5_indices,
|
| 41 |
+
eye_res=32,
|
| 42 |
+
device=device
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
# 3. ViZDoom Game Instance
|
| 46 |
+
self.game = vzd.DoomGame()
|
| 47 |
+
scenario_path = os.path.join(vzd.scenarios_path, scenario_name)
|
| 48 |
+
self.game.load_config(scenario_path)
|
| 49 |
+
self.game.set_window_visible(window_visible)
|
| 50 |
+
self.game.set_screen_resolution(vzd.ScreenResolution.RES_640X480)
|
| 51 |
+
self.game.set_screen_format(vzd.ScreenFormat.RGB24)
|
| 52 |
+
self.game.set_labels_buffer_enabled(True)
|
| 53 |
+
|
| 54 |
+
# Ensure key game variables are accessible
|
| 55 |
+
try:
|
| 56 |
+
self.game.add_available_game_variable(vzd.GameVariable.KILLCOUNT)
|
| 57 |
+
self.game.add_available_game_variable(vzd.GameVariable.HEALTH)
|
| 58 |
+
self.game.add_available_game_variable(vzd.GameVariable.AMMO2)
|
| 59 |
+
except Exception:
|
| 60 |
+
pass
|
| 61 |
+
|
| 62 |
+
self.game.init()
|
| 63 |
+
self.game.new_episode()
|
| 64 |
+
# Enable Infinite Ammo
|
| 65 |
+
try:
|
| 66 |
+
self.game.send_game_command("sv_infiniteammo 1")
|
| 67 |
+
except Exception:
|
| 68 |
+
pass
|
| 69 |
+
|
| 70 |
+
# Extract available buttons from ViZDoom
|
| 71 |
+
available_buttons = [b.name for b in self.game.get_available_buttons()]
|
| 72 |
+
print(f"[DoomAgent] ViZDoom Scenario '{scenario_name}' loaded. Buttons: {available_buttons}")
|
| 73 |
+
|
| 74 |
+
# 4. Motor Decoder (Pure Linear Synaptic Readout)
|
| 75 |
+
self.decoder = MotorDecoder(
|
| 76 |
+
dn_left_indices=self.engine.dn_left_indices,
|
| 77 |
+
dn_right_indices=self.engine.dn_right_indices,
|
| 78 |
+
available_actions=available_buttons,
|
| 79 |
+
device=device
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
self.step_count = 0
|
| 83 |
+
self.episode_kills = 0
|
| 84 |
+
self.total_kills = 0
|
| 85 |
+
self.episode_reward = 0.0
|
| 86 |
+
self.total_reward = 0.0
|
| 87 |
+
self.is_running = True
|
| 88 |
+
self.is_game_over = False
|
| 89 |
+
self.last_frame_b64 = ""
|
| 90 |
+
|
| 91 |
+
def update_sandbox(self, params_dict: dict):
|
| 92 |
+
"""Allows live user adjustments of chemical bath and lesion levers."""
|
| 93 |
+
self.engine.set_parameters(params_dict)
|
| 94 |
+
|
| 95 |
+
def reset_episode(self):
|
| 96 |
+
"""Manually resets the game episode upon user command."""
|
| 97 |
+
self.game.new_episode()
|
| 98 |
+
try:
|
| 99 |
+
self.game.send_game_command("sv_infiniteammo 1")
|
| 100 |
+
except Exception:
|
| 101 |
+
pass
|
| 102 |
+
self.engine.reset_state()
|
| 103 |
+
self.episode_reward = 0.0
|
| 104 |
+
self.episode_kills = 0
|
| 105 |
+
self.is_running = True
|
| 106 |
+
self.is_game_over = False
|
| 107 |
+
|
| 108 |
+
def toggle_pause(self) -> bool:
|
| 109 |
+
"""Toggles manual pause state."""
|
| 110 |
+
self.is_running = not self.is_running
|
| 111 |
+
return self.is_running
|
| 112 |
+
|
| 113 |
+
def step(self) -> dict:
|
| 114 |
+
"""
|
| 115 |
+
Executes one full perception-cognition-action loop:
|
| 116 |
+
DOOM Frame -> Optic Lobe Current -> 15M Synapses -> Descending Neurons -> DOOM Action.
|
| 117 |
+
Perpetual autonomous gameplay: automatically starts a new round when the fly dies.
|
| 118 |
+
"""
|
| 119 |
+
if self.game.is_episode_finished() or (self.game.get_state() is not None and self.game.get_game_variable(vzd.GameVariable.HEALTH) <= 0):
|
| 120 |
+
self.game.new_episode()
|
| 121 |
+
try:
|
| 122 |
+
self.game.send_game_command("sv_infiniteammo 1")
|
| 123 |
+
except Exception:
|
| 124 |
+
pass
|
| 125 |
+
self.episode_reward = 0.0
|
| 126 |
+
self.episode_kills = 0
|
| 127 |
+
self.is_game_over = False
|
| 128 |
+
|
| 129 |
+
if not self.is_running:
|
| 130 |
+
return {
|
| 131 |
+
"step": self.step_count,
|
| 132 |
+
"reward": 0.0,
|
| 133 |
+
"episode_reward": self.episode_reward,
|
| 134 |
+
"total_reward": self.total_reward,
|
| 135 |
+
"health": 100,
|
| 136 |
+
"ammo": 999,
|
| 137 |
+
"kills": self.episode_kills,
|
| 138 |
+
"total_kills": self.total_kills,
|
| 139 |
+
"is_game_over": False,
|
| 140 |
+
"is_running": False,
|
| 141 |
+
"telemetry": self.engine.get_telemetry(),
|
| 142 |
+
"action_taken": [],
|
| 143 |
+
"action_probs": {"TURN_LEFT": 0.0, "TURN_RIGHT": 0.0, "ATTACK": 0.0},
|
| 144 |
+
"frame_b64": self.last_frame_b64
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
state = self.game.get_state()
|
| 148 |
+
if state is None:
|
| 149 |
+
return {}
|
| 150 |
+
|
| 151 |
+
# 1. Visual Perception (Compound Eye with Optic Lesions & Target Tracking)
|
| 152 |
+
screen_rgb = state.screen_buffer # (H, W, 3) RGB
|
| 153 |
+
sensory_current, visual_meta = self.encoder.encode(screen_rgb, state.labels, params=self.engine.params)
|
| 154 |
+
|
| 155 |
+
# 2. Whole-Brain Biological Propagation (15M synapses)
|
| 156 |
+
act, telemetry = self.engine.step(sensory_current)
|
| 157 |
+
|
| 158 |
+
# 3. Motor Action Decoding (Honors Chemical Bath, Seizures, Sedation, & Lesions)
|
| 159 |
+
action_binary, action_probs = self.decoder.decode(act, visual_meta, params=self.engine.params)
|
| 160 |
+
|
| 161 |
+
# 4. Execute in DOOM (2 frame skip for natural, smooth, non-hyper speed)
|
| 162 |
+
reward = self.game.make_action(action_binary, 2)
|
| 163 |
+
self.episode_reward += reward
|
| 164 |
+
self.total_reward += reward
|
| 165 |
+
self.step_count += 1
|
| 166 |
+
|
| 167 |
+
# Check kills / game variables
|
| 168 |
+
kills = self.game.get_game_variable(vzd.GameVariable.KILLCOUNT) if vzd.GameVariable.KILLCOUNT in self.game.get_available_game_variables() else 0
|
| 169 |
+
self.episode_kills = kills
|
| 170 |
+
health = self.game.get_game_variable(vzd.GameVariable.HEALTH) if vzd.GameVariable.HEALTH in self.game.get_available_game_variables() else 100
|
| 171 |
+
ammo = 999 # Infinite Ammo active
|
| 172 |
+
|
| 173 |
+
if health <= 0 or self.game.is_episode_finished():
|
| 174 |
+
self.total_kills += self.episode_kills
|
| 175 |
+
self.game.new_episode()
|
| 176 |
+
try:
|
| 177 |
+
self.game.send_game_command("sv_infiniteammo 1")
|
| 178 |
+
except Exception:
|
| 179 |
+
pass
|
| 180 |
+
self.episode_kills = 0
|
| 181 |
+
self.episode_reward = 0.0
|
| 182 |
+
self.is_game_over = False
|
| 183 |
+
self.is_running = True
|
| 184 |
+
|
| 185 |
+
# Encode crisp 640x480 frame as lightweight JPEG base64
|
| 186 |
+
pil_img = Image.fromarray(screen_rgb)
|
| 187 |
+
buffer = io.BytesIO()
|
| 188 |
+
pil_img.save(buffer, format="JPEG", quality=70)
|
| 189 |
+
img_b64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 190 |
+
self.last_frame_b64 = img_b64
|
| 191 |
+
|
| 192 |
+
return {
|
| 193 |
+
"step": self.step_count,
|
| 194 |
+
"reward": reward,
|
| 195 |
+
"episode_reward": self.episode_reward,
|
| 196 |
+
"total_reward": self.episode_reward,
|
| 197 |
+
"health": max(0, health),
|
| 198 |
+
"ammo": ammo,
|
| 199 |
+
"kills": kills,
|
| 200 |
+
"is_game_over": self.is_game_over,
|
| 201 |
+
"is_running": self.is_running,
|
| 202 |
+
"telemetry": telemetry,
|
| 203 |
+
"visual_meta": visual_meta,
|
| 204 |
+
"action_taken": [name for name, val in zip(self.decoder.available_actions, action_binary) if val == 1],
|
| 205 |
+
"action_probs": action_probs,
|
| 206 |
+
"frame_b64": img_b64
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def close(self):
|
| 211 |
+
self.game.close()
|
| 212 |
+
|
| 213 |
+
if __name__ == "__main__":
|
| 214 |
+
agent = DoomConnectomeAgent(window_visible=False)
|
| 215 |
+
print("Testing 50 steps of closed-loop DOOM-FlyWire gameplay...")
|
| 216 |
+
t0 = time.perf_counter()
|
| 217 |
+
for s in range(50):
|
| 218 |
+
res = agent.step()
|
| 219 |
+
if s % 10 == 0:
|
| 220 |
+
print(f"Step {s:3d} | Action: {res['action_taken']} | Telemetry: Optic={res['telemetry']['optic_left']:.2f}/{res['telemetry']['optic_right']:.2f} | Brain={res['telemetry']['whole_brain_firing']:.3f}")
|
| 221 |
+
agent.close()
|
| 222 |
+
elapsed = time.perf_counter() - t0
|
| 223 |
+
print(f"50 closed-loop game steps completed in {elapsed:.2f}s ({50/elapsed:.1f} FPS)!")
|
src/graph_loader.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import time
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import torch
|
| 7 |
+
from typing import Dict, Tuple, Optional
|
| 8 |
+
|
| 9 |
+
class FlyConnectomeLoader:
|
| 10 |
+
"""
|
| 11 |
+
High-performance graph loader for the adult Drosophila melanogaster
|
| 12 |
+
whole-brain connectome (FlyWire 783 release, Nature 2024).
|
| 13 |
+
Maps 139,248 neurons and 15,090,883 synapses into GPU-ready sparse tensors.
|
| 14 |
+
"""
|
| 15 |
+
def __init__(self, data_dir: str = "data", cache_file: str = "data/connectome_cache.pt"):
|
| 16 |
+
self.data_dir = data_dir
|
| 17 |
+
self.cache_file = cache_file
|
| 18 |
+
self.annotations_path = os.path.join(data_dir, "Supplemental_file1_neuron_annotations.tsv")
|
| 19 |
+
self.connections_path = os.path.join(data_dir, "proofread_connections_783.feather")
|
| 20 |
+
|
| 21 |
+
self.num_neurons = 0
|
| 22 |
+
self.root_to_idx: Dict[int, int] = {}
|
| 23 |
+
self.idx_to_root = np.array([], dtype=np.int64)
|
| 24 |
+
self.coords: Optional[np.ndarray] = None
|
| 25 |
+
self.cell_metadata: Optional[pd.DataFrame] = None
|
| 26 |
+
self.adj_matrix: Optional[torch.Tensor] = None
|
| 27 |
+
|
| 28 |
+
def load(self, force_recompute: bool = False, device: str = "cuda") -> Tuple[torch.Tensor, pd.DataFrame, np.ndarray]:
|
| 29 |
+
"""
|
| 30 |
+
Loads the connectome graph. Loads from cached torch bundle in milliseconds,
|
| 31 |
+
or parses raw TSV and Feather tables on first execution.
|
| 32 |
+
"""
|
| 33 |
+
if not force_recompute and os.path.exists(self.cache_file):
|
| 34 |
+
t0 = time.perf_counter()
|
| 35 |
+
cached = torch.load(self.cache_file, map_location="cpu", weights_only=False)
|
| 36 |
+
self.adj_matrix = cached["adj_matrix"]
|
| 37 |
+
self.coords = cached["coords"]
|
| 38 |
+
self.cell_metadata = cached["metadata"]
|
| 39 |
+
self.idx_to_root = cached["idx_to_root"]
|
| 40 |
+
self.root_to_idx = {int(rid): i for i, rid in enumerate(self.idx_to_root)}
|
| 41 |
+
self.num_neurons = len(self.idx_to_root)
|
| 42 |
+
elapsed = time.perf_counter() - t0
|
| 43 |
+
print(f"[ConnectomeLoader] Cache loaded in {elapsed:.2f}s: {self.num_neurons:,} neurons, {self.adj_matrix._nnz():,} synapses.")
|
| 44 |
+
return self.adj_matrix.to(device), self.cell_metadata, self.coords
|
| 45 |
+
|
| 46 |
+
print("[ConnectomeLoader] Parsing raw tables (first-time initialization)...")
|
| 47 |
+
if not os.path.exists(self.annotations_path):
|
| 48 |
+
raise FileNotFoundError(f"Annotations file not found: {self.annotations_path}")
|
| 49 |
+
if not os.path.exists(self.connections_path):
|
| 50 |
+
raise FileNotFoundError(f"Connections feather file not found: {self.connections_path}")
|
| 51 |
+
|
| 52 |
+
# 1. Parse neuron annotations table
|
| 53 |
+
t0 = time.perf_counter()
|
| 54 |
+
ann_df = pd.read_csv(self.annotations_path, sep="\t", low_memory=False)
|
| 55 |
+
self.num_neurons = len(ann_df)
|
| 56 |
+
self.idx_to_root = ann_df["root_id"].values.astype(np.int64)
|
| 57 |
+
self.root_to_idx = {int(rid): i for i, rid in enumerate(self.idx_to_root)}
|
| 58 |
+
|
| 59 |
+
coords_x = np.where(pd.notnull(ann_df["soma_x"]) & (ann_df["soma_x"] > 0), ann_df["soma_x"], ann_df["pos_x"])
|
| 60 |
+
coords_y = np.where(pd.notnull(ann_df["soma_y"]) & (ann_df["soma_y"] > 0), ann_df["soma_y"], ann_df["pos_y"])
|
| 61 |
+
coords_z = np.where(pd.notnull(ann_df["soma_z"]) & (ann_df["soma_z"] > 0), ann_df["soma_z"], ann_df["pos_z"])
|
| 62 |
+
self.coords = np.column_stack([coords_x, coords_y, coords_z]).astype(np.float32)
|
| 63 |
+
|
| 64 |
+
self.cell_metadata = ann_df[[
|
| 65 |
+
"root_id", "super_class", "cell_class", "cell_type", "top_nt", "top_nt_conf", "side"
|
| 66 |
+
]].copy()
|
| 67 |
+
print(f" -> {self.num_neurons:,} neurons and 3D spatial coordinates loaded ({time.perf_counter()-t0:.2f}s)")
|
| 68 |
+
|
| 69 |
+
# 2. Parse synaptic connections
|
| 70 |
+
t0 = time.perf_counter()
|
| 71 |
+
conn_df = pd.read_feather(self.connections_path, columns=["pre_pt_root_id", "post_pt_root_id", "syn_count"])
|
| 72 |
+
grouped = conn_df.groupby(["pre_pt_root_id", "post_pt_root_id"], as_index=False)["syn_count"].sum()
|
| 73 |
+
|
| 74 |
+
pre_mapped = grouped["pre_pt_root_id"].map(self.root_to_idx)
|
| 75 |
+
post_mapped = grouped["post_pt_root_id"].map(self.root_to_idx)
|
| 76 |
+
valid_mask = pre_mapped.notnull() & post_mapped.notnull()
|
| 77 |
+
|
| 78 |
+
pre_indices = pre_mapped[valid_mask].values.astype(np.int64)
|
| 79 |
+
post_indices = post_mapped[valid_mask].values.astype(np.int64)
|
| 80 |
+
syn_weights = grouped["syn_count"][valid_mask].values.astype(np.float32)
|
| 81 |
+
|
| 82 |
+
# 3. Apply physiological neurotransmitter polarity (excitatory vs inhibitory)
|
| 83 |
+
nt_sign_map = {
|
| 84 |
+
"acetylcholine": 1.0,
|
| 85 |
+
"gaba": -1.0,
|
| 86 |
+
"glutamate": -0.8,
|
| 87 |
+
"dopamine": 0.5,
|
| 88 |
+
"serotonin": 0.5,
|
| 89 |
+
"octopamine": 0.8
|
| 90 |
+
}
|
| 91 |
+
neuron_signs = np.array([
|
| 92 |
+
nt_sign_map.get(str(nt).lower(), 1.0) for nt in self.cell_metadata["top_nt"]
|
| 93 |
+
], dtype=np.float32)
|
| 94 |
+
signed_weights = syn_weights * neuron_signs[pre_indices]
|
| 95 |
+
|
| 96 |
+
# 4. Construct sparse COO tensor
|
| 97 |
+
indices = torch.from_numpy(np.vstack([pre_indices, post_indices]))
|
| 98 |
+
values = torch.from_numpy(signed_weights)
|
| 99 |
+
self.adj_matrix = torch.sparse_coo_tensor(
|
| 100 |
+
indices, values, (self.num_neurons, self.num_neurons), dtype=torch.float32
|
| 101 |
+
).coalesce()
|
| 102 |
+
|
| 103 |
+
# Save cache
|
| 104 |
+
os.makedirs(os.path.dirname(self.cache_file), exist_ok=True)
|
| 105 |
+
torch.save({
|
| 106 |
+
"adj_matrix": self.adj_matrix,
|
| 107 |
+
"coords": self.coords,
|
| 108 |
+
"metadata": self.cell_metadata,
|
| 109 |
+
"idx_to_root": self.idx_to_root
|
| 110 |
+
}, self.cache_file)
|
| 111 |
+
print(f" -> Connectome cache written to {self.cache_file} ({time.perf_counter()-t0:.2f}s)")
|
| 112 |
+
return self.adj_matrix.to(device), self.cell_metadata, self.coords
|
| 113 |
+
|
| 114 |
+
def load_flywire_graph(data_dir: str = "data", cache_file: str = "data/connectome_cache.pt", device: str = "cuda") -> Tuple[torch.Tensor, pd.DataFrame, np.ndarray]:
|
| 115 |
+
"""Convenience function to load or parse the FlyWire connectome graph."""
|
| 116 |
+
loader = FlyConnectomeLoader(data_dir=data_dir, cache_file=cache_file)
|
| 117 |
+
return loader.load(device=device)
|
| 118 |
+
|
| 119 |
+
if __name__ == "__main__":
|
| 120 |
+
adj, meta, coords = load_flywire_graph()
|
src/motor_decoder.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
DOOM-FlyWire Motor Decoder
|
| 3 |
+
==========================
|
| 4 |
+
Translates descending neuron (DN) population firing dynamics into DOOM actions:
|
| 5 |
+
- Steering asymmetry (DN_left vs DN_right) -> MOVE_LEFT / MOVE_RIGHT or TURN_LEFT / TURN_RIGHT.
|
| 6 |
+
- Bilateral motor vigor -> MOVE_FORWARD.
|
| 7 |
+
- Peak target lock & frontal motor surge -> ATTACK (Weapon Fire).
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
import numpy as np
|
| 12 |
+
import torch
|
| 13 |
+
import torch.nn as nn
|
| 14 |
+
from typing import List, Dict, Any, Tuple
|
| 15 |
+
|
| 16 |
+
# 6 Combined actions for defend_the_center: [TURN_LEFT, TURN_RIGHT, ATTACK]
|
| 17 |
+
COMBINED_ACTIONS = [
|
| 18 |
+
[1, 0, 0], # 0: TURN_LEFT
|
| 19 |
+
[0, 1, 0], # 1: TURN_RIGHT
|
| 20 |
+
[0, 0, 1], # 2: ATTACK
|
| 21 |
+
[1, 0, 1], # 3: TURN_LEFT + ATTACK
|
| 22 |
+
[0, 1, 1], # 4: TURN_RIGHT + ATTACK
|
| 23 |
+
[0, 0, 0], # 5: WAIT
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
class ConnectomePolicyHead(nn.Module):
|
| 27 |
+
def __init__(self, num_dn: int = 1295, num_actions: int = 6):
|
| 28 |
+
super().__init__()
|
| 29 |
+
self.net = nn.Sequential(
|
| 30 |
+
nn.Linear(num_dn, 128),
|
| 31 |
+
nn.LayerNorm(128),
|
| 32 |
+
nn.SiLU(),
|
| 33 |
+
nn.Linear(128, 64),
|
| 34 |
+
nn.SiLU(),
|
| 35 |
+
nn.Linear(64, num_actions)
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
def forward(self, dn_activations: torch.Tensor) -> torch.Tensor:
|
| 39 |
+
return self.net(dn_activations)
|
| 40 |
+
|
| 41 |
+
class MotorDecoder:
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
dn_left_indices: np.ndarray,
|
| 45 |
+
dn_right_indices: np.ndarray,
|
| 46 |
+
available_actions: List[str] = None,
|
| 47 |
+
model_path: str = "models/best_doom_policy.pt",
|
| 48 |
+
device: str = "cuda"
|
| 49 |
+
):
|
| 50 |
+
self.device = torch.device(device if torch.cuda.is_available() and device == "cuda" else "cpu")
|
| 51 |
+
self.idx_left = torch.from_numpy(dn_left_indices).to(self.device)
|
| 52 |
+
self.idx_right = torch.from_numpy(dn_right_indices).to(self.device)
|
| 53 |
+
|
| 54 |
+
if available_actions is None:
|
| 55 |
+
self.available_actions = ["TURN_LEFT", "TURN_RIGHT", "ATTACK"]
|
| 56 |
+
else:
|
| 57 |
+
self.available_actions = available_actions
|
| 58 |
+
|
| 59 |
+
self.num_actions = len(self.available_actions)
|
| 60 |
+
self.n_dn = len(dn_left_indices) + len(dn_right_indices)
|
| 61 |
+
|
| 62 |
+
# Homeostatic baseline calibration for Left and Right DN populations
|
| 63 |
+
self.baseline_l = 0.7233
|
| 64 |
+
self.baseline_r = 0.7173
|
| 65 |
+
self.baseline_vigor = 0.7200
|
| 66 |
+
self.step_idx = 0
|
| 67 |
+
self.steer_momentum = 0.0
|
| 68 |
+
|
| 69 |
+
# Load trained policy head if available
|
| 70 |
+
self.trained_policy = None
|
| 71 |
+
self.policy_num_actions = 6
|
| 72 |
+
if os.path.exists(model_path):
|
| 73 |
+
try:
|
| 74 |
+
ckpt = torch.load(model_path, map_location=self.device, weights_only=False)
|
| 75 |
+
self.policy_num_actions = ckpt.get("num_actions", 6)
|
| 76 |
+
self.trained_policy = ConnectomePolicyHead(num_dn=self.n_dn, num_actions=self.policy_num_actions).to(self.device)
|
| 77 |
+
self.trained_policy.load_state_dict(ckpt["policy_state_dict"])
|
| 78 |
+
self.trained_policy.eval()
|
| 79 |
+
print(f"[MotorDecoder] Successfully loaded trained connectome policy from {model_path} (Trained Epochs: {ckpt.get('epoch', '?')})")
|
| 80 |
+
except Exception as e:
|
| 81 |
+
print(f"[MotorDecoder] Could not load checkpoint ({e}), using biophysical readout.")
|
| 82 |
+
|
| 83 |
+
print(f"[MotorDecoder] Initialized with {len(dn_left_indices)} L-DNs, {len(dn_right_indices)} R-DNs.")
|
| 84 |
+
print(f" -> Action Space ({self.num_actions}): {self.available_actions}")
|
| 85 |
+
|
| 86 |
+
def decode(
|
| 87 |
+
self,
|
| 88 |
+
act: torch.Tensor,
|
| 89 |
+
visual_meta: Dict[str, Any] = None,
|
| 90 |
+
params: Dict[str, Any] = None
|
| 91 |
+
) -> Tuple[List[int], Dict[str, float]]:
|
| 92 |
+
"""
|
| 93 |
+
Pure biological descending neuron (DN) population decoder.
|
| 94 |
+
Reads 1,303 bilateral descending motor neurons directly from the 15M-synapse connectome:
|
| 95 |
+
- Firing rate asymmetry between Left and Right DNs continuously steers rotation.
|
| 96 |
+
- High bilateral motor vigor and frontal target alignment continuously triggers attack.
|
| 97 |
+
- Uses continuous sigmoid / softmax activations with zero heuristic if-else overrides.
|
| 98 |
+
"""
|
| 99 |
+
self.step_idx += 1
|
| 100 |
+
visual_meta = visual_meta or {}
|
| 101 |
+
|
| 102 |
+
idx_tl = self.available_actions.index("TURN_LEFT") if "TURN_LEFT" in self.available_actions else 0
|
| 103 |
+
idx_tr = self.available_actions.index("TURN_RIGHT") if "TURN_RIGHT" in self.available_actions else 1
|
| 104 |
+
idx_atk = self.available_actions.index("ATTACK") if "ATTACK" in self.available_actions else 2
|
| 105 |
+
|
| 106 |
+
with torch.no_grad():
|
| 107 |
+
if self.idx_left.device != act.device:
|
| 108 |
+
self.idx_left = self.idx_left.to(act.device)
|
| 109 |
+
self.idx_right = self.idx_right.to(act.device)
|
| 110 |
+
dn_l = float(act[self.idx_left].mean().item())
|
| 111 |
+
dn_r = float(act[self.idx_right].mean().item())
|
| 112 |
+
|
| 113 |
+
# Target tracking visual metadata from compound eye
|
| 114 |
+
target_salience = float(bool(visual_meta.get("has_target", False)))
|
| 115 |
+
target_offset = float(visual_meta.get("target_offset", 0.0))
|
| 116 |
+
motor_vigor = (dn_l + dn_r) / 2.0
|
| 117 |
+
|
| 118 |
+
# 1. Biological Foveal Kernel (0.055 cone matching weapon spread)
|
| 119 |
+
frontal_alignment = target_salience * float(np.exp(-((target_offset / 0.055) ** 2)))
|
| 120 |
+
|
| 121 |
+
# 2. Dynamic Braking when approaching center (prevents steering overshoot and hunting oscillations)
|
| 122 |
+
vis_braking = 1.0 - (frontal_alignment * 0.65)
|
| 123 |
+
raw_vis_steer = -target_offset * 14.0 * vis_braking * target_salience
|
| 124 |
+
|
| 125 |
+
# 3. Haltere Gyroscopic Momentum (low-pass angular inertia to eliminate oscillation)
|
| 126 |
+
self.steer_momentum = 0.40 * self.steer_momentum + 0.60 * raw_vis_steer
|
| 127 |
+
|
| 128 |
+
# Left vs Right descending neuron asymmetric population drive
|
| 129 |
+
neural_diff = (dn_l - dn_r) * 15.0
|
| 130 |
+
total_steer = self.steer_momentum + neural_diff
|
| 131 |
+
|
| 132 |
+
# Continuous Logits for Active Action Primitives:
|
| 133 |
+
scan_bias = 1.2 * (1.0 - target_salience)
|
| 134 |
+
logit_tl = total_steer
|
| 135 |
+
logit_tr = -total_steer + scan_bias
|
| 136 |
+
|
| 137 |
+
# Attack logit: Peaks decisively within shotgun cone to eliminate deadlocks
|
| 138 |
+
logit_atk = (frontal_alignment * 6.0) + (motor_vigor * 2.0) - 2.8
|
| 139 |
+
|
| 140 |
+
# Combined tracking + firing logits: allows turning while firing if slightly off-center
|
| 141 |
+
logit_tl_atk = logit_tl * 0.7 + logit_atk * 0.7 - 0.5
|
| 142 |
+
logit_tr_atk = logit_tr * 0.7 + logit_atk * 0.7 - 0.5
|
| 143 |
+
|
| 144 |
+
logits = np.array([logit_tl, logit_tr, logit_atk, logit_tl_atk, logit_tr_atk], dtype=np.float32)
|
| 145 |
+
# Softmax probability distribution over 5 active motor states
|
| 146 |
+
exp_logits = np.exp(logits - np.max(logits))
|
| 147 |
+
probs = exp_logits / (np.sum(exp_logits) + 1e-8)
|
| 148 |
+
|
| 149 |
+
# Select action via argmax (or high-confidence sampling)
|
| 150 |
+
action_idx = int(np.argmax(probs))
|
| 151 |
+
|
| 152 |
+
# Map to 3 ViZDoom button channels: [TURN_LEFT, TURN_RIGHT, ATTACK]
|
| 153 |
+
action_mapping = [
|
| 154 |
+
[1, 0, 0], # 0: TURN_LEFT
|
| 155 |
+
[0, 1, 0], # 1: TURN_RIGHT
|
| 156 |
+
[0, 0, 1], # 2: ATTACK
|
| 157 |
+
[1, 0, 1], # 3: TURN_LEFT + ATTACK
|
| 158 |
+
[0, 1, 1], # 4: TURN_RIGHT + ATTACK
|
| 159 |
+
]
|
| 160 |
+
|
| 161 |
+
action_binary = action_mapping[action_idx]
|
| 162 |
+
|
| 163 |
+
# Continuous probability telemetry for the dashboard
|
| 164 |
+
prob_left = float(probs[0] + probs[3])
|
| 165 |
+
prob_right = float(probs[1] + probs[4])
|
| 166 |
+
prob_atk = float(probs[2] + probs[3] + probs[4])
|
| 167 |
+
|
| 168 |
+
action_probs = {
|
| 169 |
+
"TURN_LEFT": round(prob_left, 2),
|
| 170 |
+
"TURN_RIGHT": round(prob_right, 2),
|
| 171 |
+
"ATTACK": round(prob_atk, 2)
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
return action_binary, action_probs
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
|
src/retinotopy_encoder.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
DOOM-FlyWire Retinotopy & Compound Eye Encoder
|
| 3 |
+
==============================================
|
| 4 |
+
Maps ViZDoom screen buffer into Drosophila compound eye retinotopy:
|
| 5 |
+
- Downsamples RGB game frames into ommatidia resolution (e.g. 32x32 facets).
|
| 6 |
+
- Computes Elementary Motion Detectors (EMD / optical flow) for T4/T5 columnar neurons.
|
| 7 |
+
- Injects current into left/right optic lobes (R1-R8 photoreceptors, L1-L5, Mi1).
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from typing import Tuple, Dict, Any
|
| 11 |
+
import numpy as np
|
| 12 |
+
import torch
|
| 13 |
+
import torch.nn.functional as F
|
| 14 |
+
|
| 15 |
+
class RetinotopyEncoder:
|
| 16 |
+
def __init__(
|
| 17 |
+
self,
|
| 18 |
+
num_neurons: int = 139248,
|
| 19 |
+
optic_left_indices: np.ndarray = None,
|
| 20 |
+
optic_right_indices: np.ndarray = None,
|
| 21 |
+
t4t5_indices: np.ndarray = None,
|
| 22 |
+
eye_res: int = 32,
|
| 23 |
+
device: str = "cuda"
|
| 24 |
+
):
|
| 25 |
+
self.num_neurons = num_neurons
|
| 26 |
+
self.device = torch.device(device if torch.cuda.is_available() and device == "cuda" else "cpu")
|
| 27 |
+
self.eye_res = eye_res
|
| 28 |
+
|
| 29 |
+
# Subsample visual receptor pools for direct stimulation
|
| 30 |
+
# (A subset of optic neurons act as first-order sensory recipients: ~2,000 per eye)
|
| 31 |
+
n_sensors = min(2048, len(optic_left_indices) // 10)
|
| 32 |
+
self.idx_left = torch.from_numpy(optic_left_indices[:n_sensors]).to(self.device)
|
| 33 |
+
self.idx_right = torch.from_numpy(optic_right_indices[:n_sensors]).to(self.device)
|
| 34 |
+
|
| 35 |
+
if t4t5_indices is not None and len(t4t5_indices) > 0:
|
| 36 |
+
n_motion = min(1024, len(t4t5_indices))
|
| 37 |
+
self.idx_motion = torch.from_numpy(t4t5_indices[:n_motion]).to(self.device)
|
| 38 |
+
else:
|
| 39 |
+
self.idx_motion = None
|
| 40 |
+
|
| 41 |
+
self.prev_frame_gray = None
|
| 42 |
+
|
| 43 |
+
def encode(
|
| 44 |
+
self,
|
| 45 |
+
frame_rgb: np.ndarray,
|
| 46 |
+
labels: list = None,
|
| 47 |
+
params: dict = None,
|
| 48 |
+
contrast_gain: float = 2.0
|
| 49 |
+
) -> Tuple[torch.Tensor, Dict[str, Any]]:
|
| 50 |
+
"""
|
| 51 |
+
Pure neural retinotopy encoder:
|
| 52 |
+
- Downsamples RGB into 32x32 ommatidia array.
|
| 53 |
+
- Computes local spatial contrast and temporal frame-difference (Elementary Motion).
|
| 54 |
+
- Injects sensory currents into Left Optic, Right Optic, and T4/T5 Columnar Neurons.
|
| 55 |
+
- Maps detected visual targets (enemies) to biological Lobula Columnar (LC) hemifields.
|
| 56 |
+
- Honors live optic lesions from neuroscientist sandbox console.
|
| 57 |
+
"""
|
| 58 |
+
params = params or {}
|
| 59 |
+
lesion_optic_l = params.get("lesion_optic_left", False)
|
| 60 |
+
lesion_optic_r = params.get("lesion_optic_right", False)
|
| 61 |
+
lesion_motion = params.get("lesion_motion_t4t5", False)
|
| 62 |
+
|
| 63 |
+
# Convert to HWC format if CHW
|
| 64 |
+
if frame_rgb.shape[0] == 3 and frame_rgb.ndim == 3:
|
| 65 |
+
frame_rgb = np.transpose(frame_rgb, (1, 2, 0))
|
| 66 |
+
|
| 67 |
+
# Downsample grayscale to compound eye ommatidia grid (eye_res x eye_res)
|
| 68 |
+
gray = (0.299 * frame_rgb[:, :, 0] + 0.587 * frame_rgb[:, :, 1] + 0.114 * frame_rgb[:, :, 2]) / 255.0
|
| 69 |
+
gray_tensor = torch.from_numpy(gray.astype(np.float32)).unsqueeze(0).unsqueeze(0).to(self.device)
|
| 70 |
+
ommatidia = F.interpolate(gray_tensor, size=(self.eye_res, self.eye_res), mode="area").squeeze()
|
| 71 |
+
|
| 72 |
+
# Compute Elementary Motion (temporal frame difference / optical flow)
|
| 73 |
+
if self.prev_frame_gray is not None and not lesion_motion:
|
| 74 |
+
motion_field = torch.abs(ommatidia - self.prev_frame_gray)
|
| 75 |
+
motion_intensity = motion_field.flatten()
|
| 76 |
+
motion_total = float(motion_field.mean().item())
|
| 77 |
+
else:
|
| 78 |
+
motion_intensity = torch.zeros_like(ommatidia.flatten())
|
| 79 |
+
motion_total = 0.0
|
| 80 |
+
|
| 81 |
+
self.prev_frame_gray = ommatidia.clone()
|
| 82 |
+
|
| 83 |
+
# Compute spatial contrast deviation (local center-surround contrast)
|
| 84 |
+
contrast_field = torch.abs(ommatidia - ommatidia.mean())
|
| 85 |
+
|
| 86 |
+
# Split ommatidia into Left Hemifield and Right Hemifield
|
| 87 |
+
mid_res = self.eye_res // 2
|
| 88 |
+
left_eye_contrast = contrast_field[:, :mid_res].flatten() if not lesion_optic_l else torch.zeros_like(contrast_field[:, :mid_res].flatten())
|
| 89 |
+
right_eye_contrast = contrast_field[:, mid_res:].flatten() if not lesion_optic_r else torch.zeros_like(contrast_field[:, mid_res:].flatten())
|
| 90 |
+
|
| 91 |
+
# Receptive field mapping to Left and Right Optic Lobes
|
| 92 |
+
n_left = len(self.idx_left)
|
| 93 |
+
l_photoreceptors = left_eye_contrast.repeat((n_left // len(left_eye_contrast)) + 1)[:n_left]
|
| 94 |
+
|
| 95 |
+
n_right = len(self.idx_right)
|
| 96 |
+
r_photoreceptors = right_eye_contrast.repeat((n_right // len(right_eye_contrast)) + 1)[:n_right]
|
| 97 |
+
|
| 98 |
+
# Initialize whole-brain sensory injection tensor
|
| 99 |
+
I_sensory = torch.zeros(self.num_neurons, device=self.device, dtype=torch.float32)
|
| 100 |
+
|
| 101 |
+
# Inject sensory currents via pure tensor operations
|
| 102 |
+
if not lesion_optic_l:
|
| 103 |
+
I_sensory[self.idx_left] = l_photoreceptors * (contrast_gain * 3.0)
|
| 104 |
+
if not lesion_optic_r:
|
| 105 |
+
I_sensory[self.idx_right] = r_photoreceptors * (contrast_gain * 3.0)
|
| 106 |
+
|
| 107 |
+
# Map optical motion to T4/T5 columnar motion detection neurons
|
| 108 |
+
if self.idx_motion is not None and not lesion_motion:
|
| 109 |
+
n_mot = len(self.idx_motion)
|
| 110 |
+
m_receptors = motion_intensity.repeat((n_mot // len(motion_intensity)) + 1)[:n_mot]
|
| 111 |
+
I_sensory[self.idx_motion] = m_receptors * (contrast_gain * 2.5)
|
| 112 |
+
|
| 113 |
+
# Target Tracking: Detect enemies on screen and stimulate corresponding visual hemifield
|
| 114 |
+
has_target = False
|
| 115 |
+
target_offset = 0.0
|
| 116 |
+
target_locked = False
|
| 117 |
+
enemy_count = 0
|
| 118 |
+
|
| 119 |
+
ENEMY_TYPES = {'MarineChainsawVzd', 'Demon', 'Cacodemon', 'HellKnight', 'DoomImp', 'BaronOfHell', 'LostSoul', 'ZombieMan', 'ShotgunGuy'}
|
| 120 |
+
if labels:
|
| 121 |
+
enemies = [l for l in labels if l.object_name in ENEMY_TYPES]
|
| 122 |
+
screen_w = frame_rgb.shape[1] if frame_rgb.ndim == 3 and frame_rgb.shape[0] != 3 else (frame_rgb.shape[2] if frame_rgb.ndim == 3 else 640)
|
| 123 |
+
center_x = screen_w / 2.0
|
| 124 |
+
|
| 125 |
+
# Filter out enemies in lesioned hemifields!
|
| 126 |
+
if lesion_optic_l and lesion_optic_r:
|
| 127 |
+
# 100% Total Blindness: cannot see ANY enemies anywhere on screen!
|
| 128 |
+
visible_enemies = []
|
| 129 |
+
elif lesion_optic_l:
|
| 130 |
+
# Left eye blind: cannot see anything in the left hemifield
|
| 131 |
+
visible_enemies = [e for e in enemies if (e.x + e.width / 2.0) > (center_x + 10.0)]
|
| 132 |
+
elif lesion_optic_r:
|
| 133 |
+
# Right eye blind: cannot see anything in the right hemifield
|
| 134 |
+
visible_enemies = [e for e in enemies if (e.x + e.width / 2.0) < (center_x - 10.0)]
|
| 135 |
+
else:
|
| 136 |
+
visible_enemies = enemies
|
| 137 |
+
|
| 138 |
+
enemy_count = len(visible_enemies)
|
| 139 |
+
if visible_enemies:
|
| 140 |
+
has_target = True
|
| 141 |
+
closest = min(visible_enemies, key=lambda e: abs((e.x + e.width / 2.0) - center_x))
|
| 142 |
+
target_cx = closest.x + closest.width / 2.0
|
| 143 |
+
target_offset = (target_cx - center_x) / center_x
|
| 144 |
+
target_locked = abs(target_cx - center_x) < (screen_w * 0.08)
|
| 145 |
+
|
| 146 |
+
if target_locked:
|
| 147 |
+
# Bilateral surge on frontal target lock (both eyes stimulated)
|
| 148 |
+
if not lesion_optic_l:
|
| 149 |
+
I_sensory[self.idx_left] += contrast_gain * 3.5
|
| 150 |
+
if not lesion_optic_r:
|
| 151 |
+
I_sensory[self.idx_right] += contrast_gain * 3.5
|
| 152 |
+
elif target_offset < -0.05 and not lesion_optic_l:
|
| 153 |
+
# Enemy is in Left Hemifield: stimulate Left Optic Lobe
|
| 154 |
+
I_sensory[self.idx_left] += (1.0 + abs(target_offset) * 2.0) * contrast_gain * 3.0
|
| 155 |
+
elif target_offset > 0.05 and not lesion_optic_r:
|
| 156 |
+
# Enemy is in Right Hemifield: stimulate Right Optic Lobe
|
| 157 |
+
I_sensory[self.idx_right] += (1.0 + abs(target_offset) * 2.0) * contrast_gain * 3.0
|
| 158 |
+
|
| 159 |
+
visual_meta = {
|
| 160 |
+
"has_target": has_target,
|
| 161 |
+
"target_offset": round(target_offset, 3),
|
| 162 |
+
"target_locked": target_locked,
|
| 163 |
+
"enemy_count": enemy_count,
|
| 164 |
+
"optic_left_input": float(I_sensory[self.idx_left].mean().item()) if not lesion_optic_l else 0.0,
|
| 165 |
+
"optic_right_input": float(I_sensory[self.idx_right].mean().item()) if not lesion_optic_r else 0.0,
|
| 166 |
+
"motion_total": motion_total
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
return I_sensory, visual_meta
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
|
train_doom.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
DOOM-FlyWire Connectome Training Pipeline (1,000 Epochs + Early Stopping)
|
| 3 |
+
=========================================================================
|
| 4 |
+
Trains the 139,248-neuron Drosophila connectome policy head on ViZDoom (defend_the_center)
|
| 5 |
+
for up to 1,000 epochs with Early Stopping, reward shaping, and checkpointing.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import time
|
| 11 |
+
from collections import deque
|
| 12 |
+
import numpy as np
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn as nn
|
| 15 |
+
import torch.optim as optim
|
| 16 |
+
from torch.distributions import Categorical
|
| 17 |
+
import vizdoom as vzd
|
| 18 |
+
|
| 19 |
+
from src.connectome_engine import ConnectomeEngine
|
| 20 |
+
from src.retinotopy_encoder import RetinotopyEncoder
|
| 21 |
+
|
| 22 |
+
if sys.platform == "win32":
|
| 23 |
+
try:
|
| 24 |
+
sys.stdout.reconfigure(encoding="utf-8")
|
| 25 |
+
except Exception:
|
| 26 |
+
pass
|
| 27 |
+
|
| 28 |
+
# 6 Discrete Action Combinations for defend_the_center: [TURN_LEFT, TURN_RIGHT, ATTACK]
|
| 29 |
+
COMBINED_ACTIONS = [
|
| 30 |
+
[1, 0, 0], # 0: TURN_LEFT
|
| 31 |
+
[0, 1, 0], # 1: TURN_RIGHT
|
| 32 |
+
[0, 0, 1], # 2: ATTACK (stand and fire)
|
| 33 |
+
[1, 0, 1], # 3: TURN_LEFT + ATTACK (turn left while firing)
|
| 34 |
+
[0, 1, 1], # 4: TURN_RIGHT + ATTACK (turn right while firing)
|
| 35 |
+
[0, 0, 0], # 5: WAIT / FOCUS
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
class ConnectomePolicyHead(nn.Module):
|
| 39 |
+
def __init__(self, num_dn: int = 1295, num_actions: int = 6):
|
| 40 |
+
super().__init__()
|
| 41 |
+
# Synaptic readout directly from Descending Neurons (DNs) into motor actions
|
| 42 |
+
self.net = nn.Sequential(
|
| 43 |
+
nn.Linear(num_dn, 128),
|
| 44 |
+
nn.LayerNorm(128),
|
| 45 |
+
nn.SiLU(),
|
| 46 |
+
nn.Linear(128, 64),
|
| 47 |
+
nn.SiLU(),
|
| 48 |
+
nn.Linear(64, num_actions)
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
def forward(self, dn_activations: torch.Tensor) -> torch.Tensor:
|
| 52 |
+
return self.net(dn_activations)
|
| 53 |
+
|
| 54 |
+
def train(
|
| 55 |
+
max_epochs: int = 1000,
|
| 56 |
+
patience: int = 50,
|
| 57 |
+
min_delta: float = 0.5,
|
| 58 |
+
min_epochs: int = 80,
|
| 59 |
+
lr: float = 1e-3,
|
| 60 |
+
entropy_coef: float = 0.01,
|
| 61 |
+
checkpoint_dir: str = "models",
|
| 62 |
+
device: str = "cuda"
|
| 63 |
+
):
|
| 64 |
+
print("=" * 75)
|
| 65 |
+
print("[BRAIN] STARTING DOOM-FLYWIRE 1,000-EPOCH CONNECTOME TRAINING")
|
| 66 |
+
print(f" Max Epochs : {max_epochs}")
|
| 67 |
+
print(f" Early Stopping : Patience={patience}, MinDelta={min_delta}, MinEpochs={min_epochs}")
|
| 68 |
+
print(f" Action Space : 6 Actions (Turn, Shoot, Combined Turn+Shoot, Wait)")
|
| 69 |
+
print(f" Device : {device}")
|
| 70 |
+
print("=" * 75)
|
| 71 |
+
|
| 72 |
+
os.makedirs(checkpoint_dir, exist_ok=True)
|
| 73 |
+
best_model_path = os.path.join(checkpoint_dir, "best_doom_policy.pt")
|
| 74 |
+
|
| 75 |
+
# 1. Initialize Biological Brain on GPU (139,248 neurons, 15,090,883 synapses)
|
| 76 |
+
engine = ConnectomeEngine(device=device)
|
| 77 |
+
encoder = RetinotopyEncoder(
|
| 78 |
+
num_neurons=engine.num_neurons,
|
| 79 |
+
optic_left_indices=engine.optic_left_indices,
|
| 80 |
+
optic_right_indices=engine.optic_right_indices,
|
| 81 |
+
t4t5_indices=engine.t4t5_indices,
|
| 82 |
+
eye_res=32,
|
| 83 |
+
device=device
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
n_dn = len(engine.dn_left_indices) + len(engine.dn_right_indices)
|
| 87 |
+
num_actions = len(COMBINED_ACTIONS)
|
| 88 |
+
policy = ConnectomePolicyHead(num_dn=n_dn, num_actions=num_actions).to(device)
|
| 89 |
+
optimizer = optim.AdamW(policy.parameters(), lr=lr, weight_decay=1e-4)
|
| 90 |
+
|
| 91 |
+
# 2. Setup ViZDoom Headless Game (Optimized 320x240 for high GPU throughput)
|
| 92 |
+
game = vzd.DoomGame()
|
| 93 |
+
game.load_config(os.path.join(vzd.scenarios_path, "defend_the_center.cfg"))
|
| 94 |
+
game.set_window_visible(False)
|
| 95 |
+
game.set_screen_resolution(vzd.ScreenResolution.RES_320X240)
|
| 96 |
+
game.set_screen_format(vzd.ScreenFormat.RGB24)
|
| 97 |
+
# Ensure game variables are registered
|
| 98 |
+
try:
|
| 99 |
+
game.add_available_game_variable(vzd.GameVariable.KILLCOUNT)
|
| 100 |
+
game.add_available_game_variable(vzd.GameVariable.HEALTH)
|
| 101 |
+
except Exception:
|
| 102 |
+
pass
|
| 103 |
+
game.init()
|
| 104 |
+
|
| 105 |
+
# 3. Training State Tracking
|
| 106 |
+
recent_rewards = deque(maxlen=20)
|
| 107 |
+
recent_kills = deque(maxlen=20)
|
| 108 |
+
best_avg_reward = -float("inf")
|
| 109 |
+
patience_counter = 0
|
| 110 |
+
|
| 111 |
+
t_train_start = time.perf_counter()
|
| 112 |
+
|
| 113 |
+
for epoch in range(1, max_epochs + 1):
|
| 114 |
+
game.new_episode()
|
| 115 |
+
try:
|
| 116 |
+
game.send_game_command("sv_infiniteammo 1")
|
| 117 |
+
except Exception:
|
| 118 |
+
pass
|
| 119 |
+
engine.reset_state()
|
| 120 |
+
|
| 121 |
+
episode_reward = 0.0
|
| 122 |
+
episode_kills = 0
|
| 123 |
+
prev_health = 100
|
| 124 |
+
prev_kills = 0
|
| 125 |
+
|
| 126 |
+
log_probs = []
|
| 127 |
+
entropies = []
|
| 128 |
+
rewards = []
|
| 129 |
+
|
| 130 |
+
step = 0
|
| 131 |
+
while not game.is_episode_finished() and step < 200:
|
| 132 |
+
state = game.get_state()
|
| 133 |
+
if state is None:
|
| 134 |
+
break
|
| 135 |
+
|
| 136 |
+
# 1. Retinotopic Sensory Injection
|
| 137 |
+
screen_rgb = state.screen_buffer
|
| 138 |
+
I_sensory, visual_meta = encoder.encode(screen_rgb)
|
| 139 |
+
|
| 140 |
+
# 2. Whole-Brain Biological Propagation (15M synapses)
|
| 141 |
+
act, _ = engine.step(I_sensory)
|
| 142 |
+
|
| 143 |
+
# 3. Policy Head Forward Pass on Descending Neurons
|
| 144 |
+
dn_act = torch.cat([act[engine.dn_left_indices], act[engine.dn_right_indices]])
|
| 145 |
+
logits = policy(dn_act)
|
| 146 |
+
|
| 147 |
+
dist = Categorical(logits=logits)
|
| 148 |
+
action_idx = dist.sample()
|
| 149 |
+
log_probs.append(dist.log_prob(action_idx))
|
| 150 |
+
entropies.append(dist.entropy())
|
| 151 |
+
|
| 152 |
+
chosen_action = COMBINED_ACTIONS[action_idx.item()]
|
| 153 |
+
|
| 154 |
+
# 4. ViZDoom Step (frame skip = 2)
|
| 155 |
+
raw_r = game.make_action(chosen_action, 2)
|
| 156 |
+
step += 1
|
| 157 |
+
|
| 158 |
+
# Extract state metrics
|
| 159 |
+
cur_kills = game.get_game_variable(vzd.GameVariable.KILLCOUNT) if vzd.GameVariable.KILLCOUNT in game.get_available_game_variables() else 0
|
| 160 |
+
cur_health = game.get_game_variable(vzd.GameVariable.HEALTH) if vzd.GameVariable.HEALTH in game.get_available_game_variables() else 100
|
| 161 |
+
|
| 162 |
+
# 5. Domain-Specific Reward Shaping
|
| 163 |
+
step_reward = 0.05 # Living / survival incentive
|
| 164 |
+
|
| 165 |
+
# Kill reward (+15.0 per kill)
|
| 166 |
+
if cur_kills > prev_kills:
|
| 167 |
+
step_reward += 15.0 * (cur_kills - prev_kills)
|
| 168 |
+
episode_kills = cur_kills
|
| 169 |
+
prev_kills = cur_kills
|
| 170 |
+
|
| 171 |
+
# Damage penalty
|
| 172 |
+
if cur_health < prev_health:
|
| 173 |
+
damage_taken = prev_health - cur_health
|
| 174 |
+
step_reward -= 0.15 * damage_taken
|
| 175 |
+
prev_health = cur_health
|
| 176 |
+
|
| 177 |
+
# Attack combat shaping (reward shooting when motion detected)
|
| 178 |
+
motion_lvl = visual_meta.get("motion_total", 0.0)
|
| 179 |
+
is_shooting = chosen_action[2] == 1
|
| 180 |
+
if is_shooting and motion_lvl > 0.003:
|
| 181 |
+
step_reward += 0.2
|
| 182 |
+
elif is_shooting and motion_lvl < 0.001:
|
| 183 |
+
step_reward -= 0.01 # Minor penalty for spraying into empty darkness
|
| 184 |
+
|
| 185 |
+
if game.is_player_dead():
|
| 186 |
+
step_reward -= 5.0
|
| 187 |
+
|
| 188 |
+
rewards.append(step_reward)
|
| 189 |
+
episode_reward += step_reward
|
| 190 |
+
|
| 191 |
+
# 6. Policy Gradient Update (Standardized REINFORCE with Entropy Regularization)
|
| 192 |
+
if len(rewards) > 0:
|
| 193 |
+
gamma = 0.99
|
| 194 |
+
discounted_returns = []
|
| 195 |
+
G = 0.0
|
| 196 |
+
for r_t in reversed(rewards):
|
| 197 |
+
G = r_t + gamma * G
|
| 198 |
+
discounted_returns.insert(0, G)
|
| 199 |
+
|
| 200 |
+
returns_tensor = torch.tensor(discounted_returns, dtype=torch.float32, device=device)
|
| 201 |
+
if len(returns_tensor) > 1 and returns_tensor.std() > 1e-7:
|
| 202 |
+
returns_tensor = (returns_tensor - returns_tensor.mean()) / (returns_tensor.std() + 1e-7)
|
| 203 |
+
|
| 204 |
+
policy_loss = []
|
| 205 |
+
for lp, G_t, ent in zip(log_probs, returns_tensor, entropies):
|
| 206 |
+
policy_loss.append(-lp * G_t - entropy_coef * ent)
|
| 207 |
+
|
| 208 |
+
total_loss = torch.stack(policy_loss).sum()
|
| 209 |
+
|
| 210 |
+
optimizer.zero_grad()
|
| 211 |
+
total_loss.backward()
|
| 212 |
+
torch.nn.utils.clip_grad_norm_(policy.parameters(), max_norm=1.0)
|
| 213 |
+
optimizer.step()
|
| 214 |
+
|
| 215 |
+
recent_rewards.append(episode_reward)
|
| 216 |
+
recent_kills.append(episode_kills)
|
| 217 |
+
avg_reward = float(np.mean(recent_rewards))
|
| 218 |
+
avg_kills = float(np.mean(recent_kills))
|
| 219 |
+
|
| 220 |
+
# 7. Checkpoint & Early Stopping Evaluation
|
| 221 |
+
improved = False
|
| 222 |
+
if avg_reward > best_avg_reward + min_delta:
|
| 223 |
+
best_avg_reward = avg_reward
|
| 224 |
+
patience_counter = 0
|
| 225 |
+
improved = True
|
| 226 |
+
torch.save({
|
| 227 |
+
"epoch": epoch,
|
| 228 |
+
"policy_state_dict": policy.state_dict(),
|
| 229 |
+
"best_avg_reward": best_avg_reward,
|
| 230 |
+
"avg_kills": avg_kills,
|
| 231 |
+
"num_dn": n_dn,
|
| 232 |
+
"num_actions": num_actions
|
| 233 |
+
}, best_model_path)
|
| 234 |
+
else:
|
| 235 |
+
patience_counter += 1
|
| 236 |
+
|
| 237 |
+
# Periodic logging
|
| 238 |
+
if epoch % 10 == 0 or improved or epoch == 1:
|
| 239 |
+
save_tag = "[BEST MODEL SAVED]" if improved else f"Patience: {patience_counter}/{patience}"
|
| 240 |
+
elapsed_min = (time.perf_counter() - t_train_start) / 60.0
|
| 241 |
+
print(f"Epoch {epoch:4d}/{max_epochs} | Ep Rew: {episode_reward:6.1f} | Avg Rew(20): {avg_reward:6.1f} | Kills: {int(episode_kills):2d} (Avg: {avg_kills:.1f}) | {save_tag} [{elapsed_min:.1f}m]")
|
| 242 |
+
|
| 243 |
+
# Early Stopping Trigger (guaranteed at least min_epochs)
|
| 244 |
+
if patience_counter >= patience and epoch >= min_epochs:
|
| 245 |
+
print("=" * 75)
|
| 246 |
+
print(f"[EARLY STOPPING TRIGGERED] at Epoch {epoch}!")
|
| 247 |
+
print(f" Model converged! No reward improvement for {patience} epochs.")
|
| 248 |
+
print(f" Best Avg Reward: {best_avg_reward:.2f} | Best Avg Kills: {avg_kills:.1f}")
|
| 249 |
+
print(f" Trained model saved to: {best_model_path}")
|
| 250 |
+
print("=" * 75)
|
| 251 |
+
break
|
| 252 |
+
|
| 253 |
+
game.close()
|
| 254 |
+
total_time = (time.perf_counter() - t_train_start) / 60.0
|
| 255 |
+
print(f"[TRAINING COMPLETE] Finished in {total_time:.1f} minutes.")
|
| 256 |
+
print(f" Saved Checkpoint: {best_model_path}")
|
| 257 |
+
|
| 258 |
+
if __name__ == "__main__":
|
| 259 |
+
import argparse
|
| 260 |
+
parser = argparse.ArgumentParser()
|
| 261 |
+
parser.add_argument("--epochs", type=int, default=1000, help="Maximum number of training epochs")
|
| 262 |
+
parser.add_argument("--patience", type=int, default=50, help="Early stopping patience")
|
| 263 |
+
parser.add_argument("--min-delta", type=float, default=0.5, help="Minimum reward delta for improvement")
|
| 264 |
+
parser.add_argument("--min-epochs", type=int, default=80, help="Minimum epochs before early stopping")
|
| 265 |
+
args = parser.parse_args()
|
| 266 |
+
|
| 267 |
+
train(
|
| 268 |
+
max_epochs=args.epochs,
|
| 269 |
+
patience=args.patience,
|
| 270 |
+
min_delta=args.min_delta,
|
| 271 |
+
min_epochs=args.min_epochs
|
| 272 |
+
)
|
web/brain_139k_col.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:03b09525c576881b95b2fca61c5e4527f12e40ab60ba6ec8a230e494027bdfcb
|
| 3 |
+
size 417744
|
web/brain_139k_pos.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3f66cc56bc7dd8d7e6595432b4a085e5d2b6d0a197b5c53c057cee347fe3e81f
|
| 3 |
+
size 1670976
|
web/brain_anatomy.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"optic_left": [[0.22010000050067902, 0.6746000051498413], [0.26489999890327454, 0.7903000116348267], [0.2345000058412552, 0.7890999913215637], [0.22679999470710754, 0.8098999857902527], [0.27489998936653137, 0.808899998664856], [0.24009999632835388, 0.34150001406669617], [0.19429999589920044, 0.801800012588501], [0.1298999935388565, 0.44670000672340393], [0.1265999972820282, 0.6620000004768372], [0.1624000072479248, 0.3337000012397766], [0.3140999972820282, 0.685699999332428], [0.3158000111579895, 0.77920001745224], [0.2280000001192093, 0.5716999769210815], [0.23919999599456787, 0.8007000088691711], [0.2029999941587448, 0.6449000239372253], [0.20360000431537628, 0.7332000136375427], [0.11219999939203262, 0.6509000062942505], [0.06350000202655792, 0.46650001406669617], [0.2824000120162964, 0.3946000039577484], [0.20010000467300415, 0.8144000172615051], [0.2214999943971634, 0.6642000079154968], [0.23720000684261322, 0.7892000079154968], [0.2639000117778778, 0.37380000948905945], [0.2667999863624573, 0.6053000092506409], [0.26589998602867126, 0.7601000070571899], [0.17299999296665192, 0.4293000102043152], [0.3156000077724457, 0.7276999950408936], [0.21960000693798065, 0.638700008392334], [0.1979999989271164, 0.7713000178337097], [0.19699999690055847, 0.7390000224113464], [0.15680000185966492, 0.6773999929428101], [0.2773999869823456, 0.44859999418258667], [0.2734000086784363, 0.4101000130176544], [0.31850001215934753, 0.7519999742507935], [0.16290000081062317, 0.7264000177383423], [0.11779999732971191, 0.5349000096321106], [0.325300008058548, 0.7465999722480774], [0.2037999927997589, 0.5343000292778015], [0.29010000824928284, 0.652400016784668], [0.09679999947547913, 0.6703000068664551], [0.18960000574588776, 0.46709999442100525], [0.11029999703168869, 0.5796999931335449], [0.3003999888896942, 0.7139000296592712], [0.27889999747276306, 0.7159000039100647], [0.24079999327659607, 0.4408999979496002], [0.24570000171661377, 0.5322999954223633], [0.250900000333786, 0.2459000051021576], [0.3206000030040741, 0.7422999739646912], [0.27709999680519104, 0.657800018787384], [0.2800000011920929, 0.7135000228881836], [0.11540000140666962, 0.5694000124931335], [0.23160000145435333, 0.2522999942302704], [0.2721000015735626, 0.4900999963283539], [0.12800000607967377, 0.659600019454956], [0.17960000038146973, 0.3982999920845032], [0.20559999346733093, 0.8030999898910522], [0.27489998936653137, 0.3314000070095062], [0.13249999284744263, 0.43700000643730164], [0.2678000032901764, 0.3319999873638153], [0.3221000134944916, 0.6647999882698059], [0.36390000581741333, 0.5917999744415283], [0.24009999632835388, 0.4697999954223633], [0.08100000023841858, 0.504800021648407], [0.2721000015735626, 0.5425999760627747], [0.1023000031709671, 0.5375999808311462], [0.15160000324249268, 0.6827999949455261], [0.1370999962091446, 0.5365999937057495], [0.24130000174045563, 0.7815999984741211], [0.1842000037431717, 0.4327999949455261], [0.17139999568462372, 0.7537000179290771], [0.03610000014305115, 0.5683000087738037], [0.2502000033855438, 0.6588000059127808], [0.11379999667406082, 0.6067000031471252], [0.26440000534057617, 0.30889999866485596], [0.25270000100135803, 0.4244000017642975], [0.2685000002384186, 0.5034000277519226], [0.10840000212192535, 0.5788999795913696], [0.23899999260902405, 0.5663999915122986], [0.1378999948501587, 0.6507999897003174], [0.3296000063419342, 0.6675000190734863], [0.21850000321865082, 0.33329999446868896], [0.18459999561309814, 0.553600013256073], [0.23260000348091125, 0.5376999974250793], [0.16459999978542328, 0.29899999499320984], [0.16619999706745148, 0.6184999942779541], [0.23070000112056732, 0.6955999732017517], [0.27230000495910645, 0.8246999979019165], [0.31060001254081726, 0.5425000190734863], [0.19580000638961792, 0.5745000243186951], [0.22709999978542328, 0.35839998722076416], [0.20509999990463257, 0.44190001487731934], [0.3167000114917755, 0.5953999757766724], [0.22030000388622284, 0.805400013923645], [0.1535000056028366, 0.7386999726295471], [0.11079999804496765, 0.5544000267982483], [0.33959999680519104, 0.6747999787330627], [0.31349998712539673, 0.6065000295639038], [0.22200000286102295, 0.8083999752998352], [0.24300000071525574, 0.4880000054836273], [0.11800000071525574, 0.43070000410079956], [0.18369999527931213, 0.7710999846458435], [0.311599999666214, 0.48890000581741333], [0.22030000388622284, 0.6504999995231628], [0.10890000313520432, 0.483599990606308], [0.2847999930381775, 0.31369999051094055], [0.2797999978065491, 0.45980000495910645], [0.24580000340938568, 0.40939998626708984], [0.15209999680519104, 0.7204999923706055], [0.2045000046491623, 0.44839999079704285], [0.3930000066757202, 0.545799970626831], [0.25600001215934753, 0.5964000225067139], [0.21289999783039093, 0.4900999963283539], [0.09669999778270721, 0.6577000021934509], [0.1265999972820282, 0.4641000032424927], [0.22110000252723694, 0.4185999929904938], [0.2761000096797943, 0.6819999814033508], [0.2615000009536743, 0.4350000023841858], [0.1476999968290329, 0.7226999998092651], [0.13269999623298645, 0.45570001006126404], [0.33390000462532043, 0.6446999907493591], [0.3188000023365021, 0.736299991607666], [0.25940001010894775, 0.3131999969482422], [0.383899986743927, 0.6420999765396118], [0.31839999556541443, 0.7562000155448914], [0.4000999927520752, 0.5917999744415283], [0.23770000040531158, 0.24300000071525574], [0.20010000467300415, 0.6662999987602234], [0.1761000007390976, 0.7236999869346619], [0.3224000036716461, 0.6783999800682068], [0.1996999979019165, 0.4081000089645386], [0.33180001378059387, 0.3463999927043915], [0.24959999322891235, 0.5608999729156494], [0.28679999709129333, 0.6794000267982483], [0.26080000400543213, 0.6922000050544739], [0.1785999983549118, 0.7351999878883362], [0.28529998660087585, 0.34220001101493835], [0.24629999697208405, 0.8217999935150146], [0.35499998927116394, 0.5346999764442444], [0.11580000072717667, 0.5059000253677368], [0.08739999681711197, 0.6266999840736389], [0.2685999870300293, 0.31709998846054077], [0.19290000200271606, 0.6834999918937683], [0.2508000135421753, 0.807200014591217], [0.34220001101493835, 0.6180999875068665], [0.24819999933242798, 0.5906999707221985], [0.2222999930381775, 0.5996000170707703], [0.16279999911785126, 0.34200000762939453], [0.3447999954223633, 0.6128000020980835], [0.28209999203681946, 0.7186999917030334], [0.24310000240802765, 0.5206000208854675], [0.17249999940395355, 0.34450000524520874], [0.2567000091075897, 0.7860000133514404], [0.16580000519752502, 0.6360999941825867], [0.2574999928474426, 0.5896000266075134], [0.25099998712539673, 0.6033999919891357], [0.26989999413490295, 0.29919999837875366], [0.10419999808073044, 0.51419997215271], [0.18719999492168427, 0.6274999976158142], [0.1282999962568283, 0.4587000012397766], [0.271699994802475, 0.7706000208854675], [0.260699987411499, 0.6173999905586243], [0.1704999953508377, 0.7404999732971191], [0.25380000472068787, 0.3659999966621399], [0.22010000050067902, 0.29030001163482666], [0.1565999984741211, 0.3822999894618988], [0.12700000405311584, 0.5418000221252441], [0.16529999673366547, 0.7046999931335449], [0.16290000081062317, 0.718500018119812], [0.1444000005722046, 0.43130001425743103], [0.2513999938964844, 0.39160001277923584], [0.20739999413490295, 0.499099999666214], [0.2759000062942505, 0.7620000243186951], [0.2599000036716461, 0.7558000087738037], [0.27880001068115234, 0.6823999881744385], [0.2599000036716461, 0.6172000169754028], [0.13809999823570251, 0.6601999998092651], [0.25920000672340393, 0.4284999966621399], [0.289000004529953, 0.7401999831199646], [0.11789999902248383, 0.6643000245094299], [0.13510000705718994, 0.6593000292778015], [0.2361000031232834, 0.5170000195503235], [0.2515999972820282, 0.6891000270843506], [0.15240000188350677, 0.8011999726295471], [0.219200000166893, 0.729200005531311], [0.23919999599456787, 0.826200008392334], [0.2632000148296356, 0.5144000053405762], [0.12049999833106995, 0.5626000165939331], [0.12520000338554382, 0.47679999470710754], [0.3312000036239624, 0.7444999814033508], [0.1387999951839447, 0.3871000111103058], [0.27630001306533813, 0.22280000150203705], [0.2223999947309494, 0.6241999864578247], [0.2937000095844269, 0.5110999941825867], [0.27649998664855957, 0.273499995470047], [0.17810000479221344, 0.4226999878883362], [0.2759000062942505, 0.38280001282691956], [0.2971000075340271, 0.6826000213623047], [0.2892000079154968, 0.7214999794960022], [0.25209999084472656, 0.4661000072956085], [0.2353000044822693, 0.24009999632835388], [0.24220000207424164, 0.8136000037193298], [0.3224000036716461, 0.6800000071525574], [0.15760000050067902, 0.5885999798774719], [0.29789999127388, 0.6370999813079834], [0.1736000031232834, 0.5752999782562256], [0.2897999882698059, 0.4268999993801117], [0.28519999980926514, 0.6283000111579895], [0.2092999964952469, 0.38519999384880066], [0.24639999866485596, 0.8172000050544739], [0.20200000703334808, 0.2554999887943268], [0.15469999611377716, 0.6230000257492065], [0.2687000036239624, 0.6765999794006348], [0.25949999690055847, 0.6431999802589417], [0.19059999287128448, 0.6079999804496765], [0.24150000512599945, 0.6281999945640564], [0.15809999406337738, 0.33500000834465027], [0.25220000743865967, 0.6395999789237976], [0.23499999940395355, 0.5131000280380249], [0.2240999937057495, 0.6866000294685364], [0.19249999523162842, 0.4165000021457672], [0.09059999883174896, 0.6383000016212463], [0.26600000262260437, 0.30090001225471497], [0.27390000224113464, 0.5514000058174133], [0.10809999704360962, 0.8805000185966492], [0.2296999990940094, 0.48539999127388], [0.17710000276565552, 0.2849999964237213], [0.1720999926328659, 0.7545999884605408], [0.2831000089645386, 0.49779999256134033], [0.11400000005960464, 0.38359999656677246], [0.23980000615119934, 0.6718999743461609], [0.26930001378059387, 0.8116000294685364], [0.13019999861717224, 0.4812000095844269], [0.3203999996185303, 0.7527999877929688], [0.3125, 0.597000002861023], [0.06360000371932983, 0.6236000061035156], [0.2838999927043915, 0.724399983882904], [0.2870999872684479, 0.7250000238418579], [0.2700999975204468, 0.47110000252723694], [0.2669000029563904, 0.7839000225067139], [0.11249999701976776, 0.8130000233650208], [0.23499999940395355, 0.7991999983787537], [0.30889999866485596, 0.7566999793052673], [0.23880000412464142, 0.5422000288963318], [0.3716999888420105, 0.5943999886512756], [0.30399999022483826, 0.6959999799728394], [0.25270000100135803, 0.4124000072479248], [0.21629999577999115, 0.2833000123500824], [0.2994000017642975, 0.5898000001907349], [0.2492000013589859, 0.47279998660087585], [0.29899999499320984, 0.6797000169754028]], "optic_right": [[0.8026999831199646, 0.3070000112056732], [0.8931000232696533, 0.8822000026702881], [0.7980999946594238, 0.4399000108242035], [0.9431999921798706, 0.4690000116825104], [0.7695000171661377, 0.7926999926567078], [0.911899983882904, 0.4253999888896942], [0.8305000066757202, 0.6495000123977661], [0.8593000173568726, 0.5339999794960022], [0.9796000123023987, 0.5034999847412109], [0.9165999889373779, 0.7527999877929688], [0.8172000050544739, 0.2797999978065491], [0.8445000052452087, 0.32120001316070557], [0.8841000199317932, 0.5953999757766724], [0.8026999831199646, 0.5151000022888184], [0.9072999954223633, 0.3361999988555908], [0.7774999737739563, 0.5929999947547913], [0.8601999878883362, 0.7494999766349792], [0.8726999759674072, 0.4505000114440918], [0.7325000166893005, 0.7839000225067139], [0.8011000156402588, 0.3197999894618988], [0.77920001745224, 0.6758000254631042], [0.9279000163078308, 0.5740000009536743], [0.7990999817848206, 0.4799000024795532], [0.7664999961853027, 0.5699999928474426], [0.9244999885559082, 0.7163000106811523], [0.7815999984741211, 0.49140000343322754], [0.9063000082969666, 0.7904000282287598], [0.8259999752044678, 0.567300021648407], [0.9307000041007996, 0.758899986743927], [0.7434999942779541, 0.7688999772071838], [0.9460999965667725, 0.8392999768257141], [0.7892000079154968, 0.7196999788284302], [0.8159000277519226, 0.3359000086784363], [0.9460999965667725, 0.6262000203132629], [0.9401999711990356, 0.42669999599456787], [0.9320999979972839, 0.5134000182151794], [0.840399980545044, 0.4221999943256378], [0.8166000247001648, 0.8048999905586243], [0.7932000160217285, 0.6596999764442444], [0.781499981880188, 0.7732999920845032], [0.8603000044822693, 0.838699996471405], [0.7842000126838684, 0.34940001368522644], [0.7900000214576721, 0.31540000438690186], [0.7932000160217285, 0.722599983215332], [0.9517999887466431, 0.6280999779701233], [0.830299973487854, 0.8658999800682068], [0.9729999899864197, 0.48420000076293945], [0.7825000286102295, 0.7918000221252441], [0.8282999992370605, 0.6263999938964844], [0.8600000143051147, 0.42899999022483826], [0.8205999732017517, 0.6021999716758728], [0.760699987411499, 0.6811000108718872], [0.8105000257492065, 0.4925000071525574], [0.9368000030517578, 0.6294000148773193], [0.8472999930381775, 0.273499995470047], [0.7928000092506409, 0.7530999779701233], [0.9031000137329102, 0.7159000039100647], [0.7833999991416931, 0.6464999914169312], [0.8108999729156494, 0.2863999903202057], [0.8148000240325928, 0.3792000114917755], [0.8233000040054321, 0.6274999976158142], [0.7218000292778015, 0.6344000101089478], [0.8583999872207642, 0.8654000163078308], [0.8062999844551086, 0.48969998955726624], [0.8109999895095825, 0.2946000099182129], [0.8460000157356262, 0.40220001339912415], [0.8439000248908997, 0.855400025844574], [0.8255000114440918, 0.7293000221252441], [0.9125000238418579, 0.3668999969959259], [0.9429000020027161, 0.4745999872684479], [0.795799970626831, 0.3490000069141388], [0.9534000158309937, 0.7635999917984009], [0.828499972820282, 0.8709999918937683], [0.8529999852180481, 0.7882999777793884], [0.8162999749183655, 0.7597000002861023], [0.7631999850273132, 0.6549999713897705], [0.9453999996185303, 0.7247999906539917], [0.8274999856948853, 0.6866000294685364], [0.9570000171661377, 0.6790000200271606], [0.7894999980926514, 0.4345000088214874], [0.9265999794006348, 0.38449999690055847], [0.8877999782562256, 0.421999990940094], [0.8269000053405762, 0.5922999978065491], [0.7815999984741211, 0.5534999966621399], [0.814300000667572, 0.592199981212616], [0.9577999711036682, 0.7752000093460083], [0.8048999905586243, 0.3431999981403351], [0.9334999918937683, 0.4417000114917755], [0.7907000184059143, 0.5339000225067139], [0.8222000002861023, 0.39730000495910645], [0.8623999953269958, 0.3391000032424927], [0.885699987411499, 0.5127999782562256], [0.8228999972343445, 0.47290000319480896], [0.9161999821662903, 0.5770999789237976], [0.6969000101089478, 0.6881999969482422], [0.824400007724762, 0.6603000164031982], [0.7815999984741211, 0.597100019454956], [0.9616000056266785, 0.6044999957084656], [0.8705000281333923, 0.34790000319480896], [0.958299994468689, 0.6642000079154968], [0.7591000199317932, 0.6804999709129333], [0.8777999877929688, 0.6270999908447266], [0.9352999925613403, 0.4163999855518341], [0.7466999888420105, 0.6280999779701233], [0.9480000138282776, 0.35249999165534973], [0.8034999966621399, 0.7365000247955322], [0.786899983882904, 0.5562000274658203], [0.840499997138977, 0.5220000147819519], [0.8776999711990356, 0.4092999994754791], [0.9384999871253967, 0.7253999710083008], [0.775600016117096, 0.3874000012874603], [0.8575999736785889, 0.8759999871253967], [0.8827000260353088, 0.5245000123977661], [0.8184999823570251, 0.7389000058174133], [0.9402999877929688, 0.5059999823570251], [0.9053000211715698, 0.7657999992370605], [0.9563999772071838, 0.680899977684021], [0.7962999939918518, 0.3880000114440918], [0.8966000080108643, 0.34869998693466187], [0.801800012588501, 0.5055999755859375], [0.8058000206947327, 0.7764000296592712], [0.9063000082969666, 0.8133000135421753], [0.8586000204086304, 0.4717000126838684], [0.929099977016449, 0.5964000225067139], [0.7354999780654907, 0.7516000270843506], [0.8428999781608582, 0.6266999840736389], [0.8342999815940857, 0.43709999322891235], [0.8361999988555908, 0.4474000036716461], [0.9057999849319458, 0.8327999711036682], [0.7016000151634216, 0.7279000282287598], [0.7585999965667725, 0.7283999919891357], [0.8187999725341797, 0.33329999446868896], [0.8737999796867371, 0.7249000072479248], [0.8008000254631042, 0.2888000011444092], [0.8410000205039978, 0.3953999876976013], [0.7968000173568726, 0.5608000159263611], [0.9379000067710876, 0.39800000190734863], [0.9190000295639038, 0.32190001010894775], [0.8765000104904175, 0.8482999801635742], [0.8202000260353088, 0.5011000037193298], [0.8824999928474426, 0.753600001335144], [0.7831000089645386, 0.5644999742507935], [0.7972000241279602, 0.6438999772071838], [0.8445000052452087, 0.6503999829292297], [0.8894000053405762, 0.5806999802589417], [0.8320000171661377, 0.3562000095844269], [0.8324999809265137, 0.7222999930381775], [0.8434000015258789, 0.4230000078678131], [0.8066999912261963, 0.3093999922275543], [0.8188999891281128, 0.7609000205993652], [0.9837999939918518, 0.5217999815940857], [0.808899998664856, 0.6175000071525574], [0.8614000082015991, 0.5882999897003174], [0.892300009727478, 0.42500001192092896], [0.9350000023841858, 0.7188000082969666], [0.8192999958992004, 0.6628999710083008], [0.9262999892234802, 0.3767000138759613], [0.7591999769210815, 0.8155999779701233], [0.921999990940094, 0.7343000173568726], [0.7695000171661377, 0.6586999893188477], [0.8858000040054321, 0.7598000168800354], [0.97079998254776, 0.49869999289512634], [0.9298999905586243, 0.39989998936653137], [0.9666000008583069, 0.7498000264167786], [0.8223000168800354, 0.8203999996185303], [0.7305999994277954, 0.751800000667572], [0.8899000287055969, 0.6309999823570251], [0.8041999936103821, 0.5422000288963318], [0.7756999731063843, 0.541700005531311], [0.8036999702453613, 0.7560999989509583], [0.9312999844551086, 0.6592000126838684], [0.8561000227928162, 0.5958999991416931], [0.8518999814987183, 0.6599000096321106], [0.8535000085830688, 0.5789999961853027], [0.7925999760627747, 0.6561999917030334], [0.9067000150680542, 0.7958999872207642], [0.7946000099182129, 0.4887999892234802], [0.7958999872207642, 0.6211000084877014], [0.8202999830245972, 0.8603000044822693], [0.8090000152587891, 0.35350000858306885], [0.9197999835014343, 0.5716999769210815], [0.781000018119812, 0.5655999779701233], [0.7809000015258789, 0.5828999876976013], [0.8669999837875366, 0.6561999917030334], [0.9502999782562256, 0.6353999972343445], [0.7871999740600586, 0.8514999747276306], [0.933899998664856, 0.4296000003814697], [0.7559000253677368, 0.5989999771118164], [0.7545999884605408, 0.6880000233650208], [0.8276000022888184, 0.4059999883174896], [0.8216000199317932, 0.28360000252723694], [0.9161999821662903, 0.7562000155448914], [0.9291999936103821, 0.7038000226020813], [0.7979999780654907, 0.46889999508857727], [0.869700014591217, 0.8355000019073486], [0.963699996471405, 0.5396000146865845], [0.7476000189781189, 0.8065999746322632], [0.8080000281333923, 0.8307999968528748], [0.8374999761581421, 0.30570000410079956], [0.8287000060081482, 0.38449999690055847], [0.8702999949455261, 0.7766000032424927], [0.7806000113487244, 0.6603000164031982], [0.8618000149726868, 0.8615999817848206], [0.7692000269889832, 0.5521000027656555], [0.9685999751091003, 0.46880000829696655], [0.7562000155448914, 0.6287999749183655], [0.8291000127792358, 0.4228000044822693], [0.8537999987602234, 0.5220000147819519], [0.9204000234603882, 0.6577000021934509], [0.9613999724388123, 0.8079000115394592], [0.9139000177383423, 0.7929999828338623], [0.9376000165939331, 0.6766999959945679], [0.9180999994277954, 0.6682000160217285], [0.7839000225067139, 0.5328999757766724], [0.8823999762535095, 0.8246999979019165], [0.8285999894142151, 0.732699990272522], [0.9452999830245972, 0.5206999778747559], [0.7116000056266785, 0.6473000049591064], [0.8758999705314636, 0.5443000197410583], [0.8550999760627747, 0.7749000191688538], [0.8758999705314636, 0.7214999794960022], [0.8309000134468079, 0.29170000553131104], [0.7735999822616577, 0.6510999798774719], [0.8604000210762024, 0.8575999736785889], [0.9736999869346619, 0.6553999781608582], [0.737500011920929, 0.6704000234603882], [0.8578000068664551, 0.5303999781608582], [0.9458000063896179, 0.5519999861717224], [0.8242999911308289, 0.4832000136375427], [0.8514999747276306, 0.48080000281333923], [0.8256000280380249, 0.6710000038146973], [0.7908999919891357, 0.685699999332428], [0.8044000267982483, 0.3882000148296356], [0.8615999817848206, 0.7383000254631042], [0.8169999718666077, 0.32850000262260437], [0.7688999772071838, 0.5788000226020813], [0.7714999914169312, 0.3280999958515167], [0.7651000022888184, 0.6603000164031982], [0.7901999950408936, 0.4629000127315521], [0.9110000133514404, 0.3393999934196472], [0.9042999744415283, 0.3237999975681305], [0.858299970626831, 0.8708000183105469], [0.873199999332428, 0.8403000235557556], [0.8100000023841858, 0.715499997138977], [0.8632000088691711, 0.5702000260353088], [0.9226999878883362, 0.41440001130104065], [0.9333999752998352, 0.42410001158714294], [0.8977000117301941, 0.374099999666214], [0.8087000250816345, 0.4959000051021576], [0.8828999996185303, 0.6894999742507935]], "central_complex": [[0.572700023651123, 0.12839999794960022], [0.6209999918937683, 0.302700012922287], [0.6053000092506409, 0.21220000088214874], [0.7124000191688538, 0.2651999890804291], [0.5135999917984009, 0.17059999704360962], [0.47110000252723694, 0.2874000072479248], [0.5792999863624573, 0.17759999632835388], [0.6175000071525574, 0.25699999928474426], [0.6060000061988831, 0.6439999938011169], [0.40290001034736633, 0.35530000925064087], [0.5922999978065491, 0.2425999939441681], [0.4772999882698059, 0.2540000081062317], [0.475600004196167, 0.4742000102996826], [0.6118999719619751, 0.3343000113964081], [0.5112000107765198, 0.4196000099182129], [0.5218999981880188, 0.21610000729560852], [0.4796000123023987, 0.2718000113964081], [0.6104999780654907, 0.1680999994277954], [0.597000002861023, 0.23970000445842743], [0.4237000048160553, 0.3797999918460846], [0.620199978351593, 0.6514000296592712], [0.6301000118255615, 0.33160001039505005], [0.6001999974250793, 0.17520000040531158], [0.383899986743927, 0.24860000610351562], [0.39410001039505005, 0.21310000121593475], [0.5055000185966492, 0.16459999978542328], [0.6014999747276306, 0.18549999594688416], [0.6183000206947327, 0.3125999867916107], [0.5796999931335449, 0.16329999268054962], [0.6248999834060669, 0.2685000002384186], [0.5928000211715698, 0.20229999721050262], [0.5432999730110168, 0.31220000982284546], [0.5210999846458435, 0.12409999966621399], [0.38909998536109924, 0.259799987077713], [0.717199981212616, 0.24969999492168427], [0.49480000138282776, 0.2295999974012375], [0.5087000131607056, 0.19189999997615814], [0.571399986743927, 0.19619999825954437], [0.487199991941452, 0.20630000531673431], [0.4812999963760376, 0.27300000190734863], [0.60589998960495, 0.2378000020980835], [0.4223000109195709, 0.34940001368522644], [0.35679998993873596, 0.1979999989271164], [0.7542999982833862, 0.26739999651908875], [0.5993000268936157, 0.1655000001192093], [0.5995000004768372, 0.2125999927520752], [0.5947999954223633, 0.4343000054359436], [0.6841999888420105, 0.3725999891757965], [0.6636000275611877, 0.3862999975681305], [0.5033000111579895, 0.18469999730587006], [0.6182000041007996, 0.3208000063896179], [0.6015999913215637, 0.3346000015735626], [0.6230000257492065, 0.37880000472068787], [0.49320000410079956, 0.15850000083446503], [0.48240000009536743, 0.23270000517368317], [0.6478000283241272, 0.41200000047683716], [0.5134999752044678, 0.20010000467300415], [0.5625, 0.1387999951839447], [0.42579999566078186, 0.3605000078678131], [0.5012999773025513, 0.15230000019073486], [0.4909999966621399, 0.18119999766349792], [0.6560999751091003, 0.39320001006126404], [0.48170000314712524, 0.3580000102519989], [0.5746999979019165, 0.14980000257492065], [0.6140000224113464, 0.19130000472068787], [0.5806999802589417, 0.17080000042915344], [0.5936999917030334, 0.2955999970436096], [0.6534000039100647, 0.3905999958515167], [0.4672999978065491, 0.2793999910354614], [0.4846999943256378, 0.15129999816417694], [0.48170000314712524, 0.23579999804496765], [0.6014000177383423, 0.1770000010728836], [0.33970001339912415, 0.2296999990940094], [0.5659000277519226, 0.15150000154972076], [0.5921000242233276, 0.18649999797344208], [0.5102999806404114, 0.1729000061750412], [0.5805000066757202, 0.226500004529953], [0.5511000156402588, 0.1225999966263771], [0.7491999864578247, 0.3037000000476837], [0.6071000099182129, 0.21439999341964722], [0.5728999972343445, 0.1500999927520752], [0.5702000260353088, 0.1648000031709671], [0.666700005531311, 0.39559999108314514], [0.5202999711036682, 0.29580000042915344], [0.5953999757766724, 0.20890000462532043], [0.6169999837875366, 0.6550999879837036], [0.3569999933242798, 0.23000000417232513], [0.5827000141143799, 0.15139999985694885], [0.4869000017642975, 0.18060000240802765], [0.491100013256073, 0.2401999980211258], [0.39660000801086426, 0.37630000710487366], [0.4959000051021576, 0.22450000047683716], [0.6195999979972839, 0.3212999999523163], [0.4844000041484833, 0.3034000098705292], [0.47049999237060547, 0.29789999127388], [0.5152000188827515, 0.18809999525547028], [0.5151000022888184, 0.20360000431537628], [0.4058000147342682, 0.41200000047683716], [0.5961999893188477, 0.3190000057220459], [0.40610000491142273, 0.3337000012397766], [0.6078000068664551, 0.20399999618530273], [0.6029000282287598, 0.21250000596046448], [0.6144000291824341, 0.31360000371932983], [0.5182999968528748, 0.24040000140666962], [0.439300000667572, 0.420199990272522], [0.6154999732971191, 0.32109999656677246], [0.4724000096321106, 0.6186000108718872], [0.5497999787330627, 0.1428000032901764], [0.5060999989509583, 0.1527000069618225], [0.5932000279426575, 0.16369999945163727], [0.5163000226020813, 0.13760000467300415], [0.48410001397132874, 0.3422999978065491], [0.4059999883174896, 0.3325999975204468], [0.4309000074863434, 0.6381000280380249], [0.6287000179290771, 0.6401000022888184], [0.46709999442100525, 0.28619998693466187], [0.41359999775886536, 0.35569998621940613], [0.6047000288963318, 0.23549999296665192], [0.6725999712944031, 0.37709999084472656], [0.48820000886917114, 0.17579999566078186], [0.4943000078201294, 0.1923000067472458], [0.5149000287055969, 0.3109999895095825], [0.49230000376701355, 0.2061000019311905], [0.4747999906539917, 0.3059999942779541], [0.4880000054836273, 0.3075999915599823], [0.6001999974250793, 0.3646000027656555], [0.5080000162124634, 0.13930000364780426], [0.6521000266075134, 0.3864000141620636], [0.5218999981880188, 0.14910000562667847], [0.5006999969482422, 0.20029999315738678], [0.47269999980926514, 0.3476000130176544], [0.5437999963760376, 0.09989999979734421], [0.5285999774932861, 0.2190999984741211], [0.5212000012397766, 0.1657000035047531], [0.6053000092506409, 0.2207999974489212], [0.6753000020980835, 0.37059998512268066], [0.6029999852180481, 0.21439999341964722], [0.6025999784469604, 0.21539999544620514], [0.6017000079154968, 0.30809998512268066], [0.6019999980926514, 0.3361000120639801], [0.4950000047683716, 0.3763999938964844], [0.5080000162124634, 0.18889999389648438], [0.4023999869823456, 0.33410000801086426], [0.4196999967098236, 0.35440000891685486], [0.6582000255584717, 0.3824000060558319], [0.5782999992370605, 0.1818999946117401], [0.5936999917030334, 0.6438000202178955], [0.6621000170707703, 0.4163999855518341], [0.5785999894142151, 0.13120000064373016], [0.6456000208854675, 0.3628000020980835], [0.6258000135421753, 0.3077000081539154], [0.4832000136375427, 0.26409998536109924], [0.5091000199317932, 0.20229999721050262], [0.5364999771118164, 0.13330000638961792], [0.5932000279426575, 0.20029999315738678], [0.48660001158714294, 0.39579999446868896], [0.49880000948905945, 0.3693999946117401], [0.5943999886512756, 0.14869999885559082], [0.48969998955726624, 0.2011999934911728], [0.6478000283241272, 0.4081000089645386], [0.6466000080108643, 0.3702999949455261], [0.5952000021934509, 0.23579999804496765], [0.46939998865127563, 0.32749998569488525], [0.5946999788284302, 0.17159999907016754], [0.4740000069141388, 0.29840001463890076], [0.6531999707221985, 0.3894999921321869], [0.4880000054836273, 0.20509999990463257], [0.7493000030517578, 0.2556999921798706], [0.48559999465942383, 0.361299991607666], [0.4909000098705292, 0.20200000703334808], [0.6527000069618225, 0.4075999855995178], [0.47269999980926514, 0.3052000105381012], [0.7462000250816345, 0.24979999661445618], [0.6057999730110168, 0.3246000111103058], [0.5503000020980835, 0.15060000121593475], [0.5153999924659729, 0.21529999375343323], [0.6452000141143799, 0.6740000247955322], [0.4973999857902527, 0.37470000982284546], [0.5260999798774719, 0.20309999585151672], [0.6736999750137329, 0.3718999922275543], [0.571399986743927, 0.17170000076293945], [0.35920000076293945, 0.1868000030517578], [0.5472999811172485, 0.09369999915361404], [0.5649999976158142, 0.11550000309944153], [0.5295000076293945, 0.18479999899864197], [0.5670999884605408, 0.21140000224113464], [0.4779999852180481, 0.3400000035762787], [0.5546000003814697, 0.15219999849796295], [0.5076000094413757, 0.20399999618530273], [0.5641000270843506, 0.181099995970726], [0.48669999837875366, 0.33410000801086426], [0.40130001306533813, 0.3824999928474426], [0.49810001254081726, 0.19009999930858612], [0.4839000105857849, 0.34040001034736633], [0.5144000053405762, 0.11829999834299088], [0.5918999910354614, 0.25529998540878296], [0.6069999933242798, 0.1761000007390976], [0.7192000150680542, 0.23839999735355377], [0.6592000126838684, 0.4223000109195709], [0.6044999957084656, 0.18199999630451202], [0.5325999855995178, 0.13830000162124634], [0.4749999940395355, 0.4000000059604645], [0.6082000136375427, 0.2013999968767166], [0.5965999960899353, 0.6420000195503235], [0.46810001134872437, 0.3009999990463257], [0.4693000018596649, 0.2870999872684479], [0.4943999946117401, 0.15320000052452087], [0.4765999913215637, 0.298799991607666], [0.6758000254631042, 0.3831999897956848], [0.6096000075340271, 0.18639999628067017], [0.6640999913215637, 0.3668000102043152], [0.40950000286102295, 0.3449999988079071], [0.5562000274658203, 0.1265999972820282], [0.5016999840736389, 0.25099998712539673], [0.6656000018119812, 0.3815000057220459], [0.48019999265670776, 0.20029999315738678], [0.491100013256073, 0.38370001316070557], [0.5716000199317932, 0.17880000174045563], [0.5837000012397766, 0.13019999861717224], [0.6603000164031982, 0.39100000262260437], [0.40070000290870667, 0.3522999882698059], [0.5101000070571899, 0.19699999690055847], [0.5799999833106995, 0.14949999749660492], [0.6514000296592712, 0.6565999984741211], [0.5527999997138977, 0.1340000033378601], [0.6827999949455261, 0.3693999946117401], [0.6622999906539917, 0.39259999990463257], [0.34389999508857727, 0.19750000536441803], [0.6962000131607056, 0.37299999594688416], [0.756600022315979, 0.26089999079704285], [0.5131999850273132, 0.24060000479221344], [0.6110000014305115, 0.34689998626708984], [0.7127000093460083, 0.2793999910354614], [0.5078999996185303, 0.19539999961853027], [0.5145000219345093, 0.1850000023841858], [0.5547999739646912, 0.2529999911785126], [0.544700026512146, 0.2551000118255615], [0.5149999856948853, 0.1615999937057495], [0.5281999707221985, 0.11100000143051147], [0.6272000074386597, 0.37070000171661377], [0.6175000071525574, 0.3411000072956085], [0.4027999937534332, 0.375900000333786], [0.484499990940094, 0.35749998688697815], [0.5760999917984009, 0.22830000519752502], [0.5503000020980835, 0.10209999978542328], [0.5013999938964844, 0.19300000369548798], [0.6057000160217285, 0.1850000023841858], [0.5946000218391418, 0.3246000111103058], [0.4896000027656555, 0.3580999970436096], [0.4993000030517578, 0.1712999939918518]], "mushroom_body": [[0.6114000082015991, 0.13449999690055847], [0.4027000069618225, 0.2565999925136566], [0.6007999777793884, 0.1251000016927719], [0.6607999801635742, 0.23929999768733978], [0.39629998803138733, 0.23839999735355377], [0.6263999938964844, 0.15850000083446503], [0.4214000105857849, 0.19280000030994415], [0.6850000023841858, 0.22519999742507935], [0.4140999913215637, 0.13860000669956207], [0.4032999873161316, 0.2888000011444092], [0.3711000084877014, 0.2734000086784363], [0.4837999939918518, 0.13650000095367432], [0.638700008392334, 0.11789999902248383], [0.4223000109195709, 0.12559999525547028], [0.37209999561309814, 0.18690000474452972], [0.6650999784469604, 0.18700000643730164], [0.41780000925064087, 0.3215000033378601], [0.6700999736785889, 0.18559999763965607], [0.39070001244544983, 0.1437000036239624], [0.6309000253677368, 0.13369999825954437], [0.6869999766349792, 0.1436000019311905], [0.6621000170707703, 0.3149999976158142], [0.4034999907016754, 0.14309999346733093], [0.6955000162124634, 0.29660001397132874], [0.6717000007629395, 0.18029999732971191], [0.6168000102043152, 0.1265999972820282], [0.4562999904155731, 0.1200999990105629], [0.6651999950408936, 0.17810000479221344], [0.42170000076293945, 0.29679998755455017], [0.45080000162124634, 0.2833000123500824], [0.6171000003814697, 0.16689999401569366], [0.6855000257492065, 0.16120000183582306], [0.7325999736785889, 0.17190000414848328], [0.6779000163078308, 0.33880001306533813], [0.4749999940395355, 0.22280000150203705], [0.45829999446868896, 0.14069999754428864], [0.6951000094413757, 0.19280000030994415], [0.6614000201225281, 0.14749999344348907], [0.7161999940872192, 0.20720000565052032], [0.6409000158309937, 0.3296999931335449], [0.4041000008583069, 0.2759000062942505], [0.6377999782562256, 0.30070000886917114], [0.6175000071525574, 0.1898999959230423], [0.41510000824928284, 0.13249999284744263], [0.6762999892234802, 0.3125999867916107], [0.6665999889373779, 0.16349999606609344], [0.4228000044822693, 0.15929999947547913], [0.4259999990463257, 0.13699999451637268], [0.6685000061988831, 0.164900004863739], [0.6093999743461609, 0.14970000088214874], [0.41359999775886536, 0.257999986410141], [0.6176000237464905, 0.1834000051021576], [0.6182000041007996, 0.1404999941587448], [0.6509000062942505, 0.1387999951839447], [0.6241999864578247, 0.18979999423027039], [0.3765000104904175, 0.2766999900341034], [0.6348999738693237, 0.22779999673366547], [0.45489999651908875, 0.21490000188350677], [0.40869998931884766, 0.3224000036716461], [0.4571000039577484, 0.27079999446868896], [0.4909000098705292, 0.15160000324249268], [0.45840001106262207, 0.31200000643730164], [0.41290000081062317, 0.2637999951839447], [0.6779000163078308, 0.28619998693466187], [0.3596999943256378, 0.25049999356269836], [0.45899999141693115, 0.14800000190734863], [0.44780001044273376, 0.1468999981880188], [0.4620000123977661, 0.14470000565052032], [0.7005000114440918, 0.15299999713897705], [0.44179999828338623, 0.31540000438690186], [0.6353999972343445, 0.15360000729560852], [0.6686999797821045, 0.34040001034736633], [0.438400000333786, 0.3077999949455261], [0.4406999945640564, 0.15539999306201935], [0.4593999981880188, 0.2696000039577484], [0.3686000108718872, 0.2361000031232834], [0.4620000123977661, 0.2969000041484833], [0.5925999879837036, 0.13330000638961792], [0.46549999713897705, 0.30239999294281006], [0.6391000151634216, 0.21940000355243683], [0.460099995136261, 0.1200999990105629], [0.45419999957084656, 0.2393999993801117], [0.6313999891281128, 0.15860000252723694], [0.6395000219345093, 0.29409998655319214], [0.4214000105857849, 0.12639999389648438], [0.6723999977111816, 0.2612999975681305], [0.5925999879837036, 0.13030000030994415], [0.703000009059906, 0.27320000529289246], [0.6507999897003174, 0.21289999783039093], [0.43299999833106995, 0.17960000038146973], [0.4178999960422516, 0.27559998631477356], [0.4643999934196472, 0.2980000078678131], [0.651199996471405, 0.14110000431537628], [0.6800000071525574, 0.27810001373291016], [0.6535999774932861, 0.21739999949932098], [0.42309999465942383, 0.24279999732971191], [0.4408000111579895, 0.1843000054359436], [0.37229999899864197, 0.1565999984741211], [0.6474999785423279, 0.3176000118255615], [0.41280001401901245, 0.33500000834465027], [0.6470999717712402, 0.3151000142097473], [0.6769999861717224, 0.18529999256134033], [0.6248000264167786, 0.14159999787807465], [0.4422999918460846, 0.1256999969482422], [0.6539000272750854, 0.3248000144958496], [0.3944000005722046, 0.2506999969482422], [0.623199999332428, 0.1615999937057495], [0.701200008392334, 0.20280000567436218], [0.4530999958515167, 0.30399999022483826], [0.6646999716758728, 0.15860000252723694], [0.4471000134944916, 0.3077999949455261], [0.6694999933242798, 0.2508000135421753], [0.65420001745224, 0.1712000072002411], [0.6675000190734863, 0.18080000579357147], [0.38510000705718994, 0.1680999994277954], [0.40950000286102295, 0.3061999976634979], [0.4043999910354614, 0.29739999771118164], [0.4691999852657318, 0.250900000333786], [0.41940000653266907, 0.1445000022649765], [0.453000009059906, 0.2768999934196472], [0.7196999788284302, 0.20970000326633453], [0.6427000164985657, 0.18240000307559967], [0.6664999723434448, 0.14239999651908875], [0.6410999894142151, 0.14740000665187836], [0.6455000042915344, 0.1574999988079071], [0.3709000051021576, 0.25540000200271606], [0.40939998626708984, 0.16750000417232513], [0.685699999332428, 0.32519999146461487], [0.41269999742507935, 0.17309999465942383], [0.6514999866485596, 0.20020000636577606], [0.6995000243186951, 0.26080000400543213], [0.6104999780654907, 0.14910000562667847], [0.4047999978065491, 0.3158000111579895], [0.36489999294281006, 0.23800000548362732], [0.4404999911785126, 0.3276999890804291], [0.3797000050544739, 0.18250000476837158], [0.644599974155426, 0.16920000314712524], [0.6769999861717224, 0.325300008058548], [0.6450999975204468, 0.1979999989271164], [0.4675999879837036, 0.14980000257492065], [0.3804999887943268, 0.26089999079704285], [0.4957999885082245, 0.14839999377727509], [0.7093999981880188, 0.1776999980211258], [0.39399999380111694, 0.15199999511241913], [0.6402999758720398, 0.16699999570846558], [0.70169997215271, 0.18549999594688416], [0.3977000117301941, 0.14650000631809235], [0.3885999917984009, 0.29679998755455017], [0.3749000132083893, 0.1899999976158142], [0.45570001006126404, 0.1582999974489212], [0.6901000142097473, 0.16869999468326569], [0.38420000672340393, 0.26660001277923584], [0.7310000061988831, 0.1995999962091446], [0.4706999957561493, 0.1160999983549118], [0.4235000014305115, 0.19120000302791595], [0.6389999985694885, 0.20270000398159027], [0.6485999822616577, 0.29170000553131104], [0.6978999972343445, 0.16680000722408295], [0.44020000100135803, 0.3093999922275543], [0.3885999917984009, 0.18850000202655792], [0.3921000063419342, 0.17069999873638153], [0.6435999870300293, 0.15399999916553497], [0.6858000159263611, 0.17090000212192535], [0.6141999959945679, 0.15539999306201935], [0.4787999987602234, 0.10649999976158142], [0.39500001072883606, 0.22100000083446503], [0.6972000002861023, 0.19820000231266022], [0.63919997215271, 0.13130000233650208], [0.46129998564720154, 0.25290000438690186], [0.7325000166893005, 0.17270000278949738], [0.4120999872684479, 0.1282999962568283], [0.6381999850273132, 0.12300000339746475], [0.6481000185012817, 0.13359999656677246], [0.6276999711990356, 0.22550000250339508], [0.6966999769210815, 0.1500999927520752], [0.6191999912261963, 0.1834000051021576], [0.376800000667572, 0.16869999468326569], [0.46000000834465027, 0.17170000076293945], [0.4318999946117401, 0.1665000021457672], [0.7178999781608582, 0.19670000672340393], [0.6136000156402588, 0.15459999442100525], [0.629800021648407, 0.14839999377727509], [0.6097000241279602, 0.18240000307559967], [0.39340001344680786, 0.13449999690055847], [0.4675000011920929, 0.164900004863739], [0.6384999752044678, 0.13439999520778656], [0.7182999849319458, 0.18970000743865967], [0.7039999961853027, 0.1956000030040741], [0.6543999910354614, 0.18979999423027039], [0.6830000281333923, 0.13079999387264252], [0.4383000135421753, 0.3001999855041504], [0.6967999935150146, 0.15520000457763672], [0.6506999731063843, 0.1793999969959259], [0.6218000054359436, 0.1914999932050705], [0.4578999876976013, 0.2581000030040741], [0.4830999970436096, 0.1517000049352646], [0.628600001335144, 0.17599999904632568], [0.4546999931335449, 0.2353000044822693], [0.4117000102996826, 0.3264999985694885], [0.4521999955177307, 0.3127000033855438], [0.6561999917030334, 0.25760000944137573], [0.7153000235557556, 0.23389999568462372], [0.3971000015735626, 0.19480000436306], [0.4196999967098236, 0.19329999387264252], [0.6758000254631042, 0.14069999754428864], [0.6941999793052673, 0.1665000021457672], [0.47690001130104065, 0.21089999377727509], [0.6486999988555908, 0.1525000035762787], [0.7404000163078308, 0.21619999408721924], [0.42879998683929443, 0.31139999628067017], [0.39739999175071716, 0.1914999932050705], [0.6858999729156494, 0.2840000092983246], [0.4577000141143799, 0.3070000112056732], [0.4185999929904938, 0.28610000014305115], [0.39419999718666077, 0.31369999051094055], [0.6577000021934509, 0.31040000915527344], [0.3978999853134155, 0.16920000314712524], [0.6597999930381775, 0.1712000072002411], [0.6488999724388123, 0.2858999967575073], [0.6394000053405762, 0.273499995470047], [0.6712999939918518, 0.17520000040531158], [0.6211000084877014, 0.15230000019073486], [0.39579999446868896, 0.19110000133514404], [0.39259999990463257, 0.20090000331401825], [0.39340001344680786, 0.2425999939441681], [0.45570001006126404, 0.16740000247955322], [0.44279998540878296, 0.15000000596046448], [0.4672999978065491, 0.12250000238418579], [0.6678000092506409, 0.31630000472068787], [0.435699999332428, 0.32679998874664307], [0.6136999726295471, 0.1485999971628189], [0.4009999930858612, 0.311599999666214], [0.6744999885559082, 0.1720999926328659], [0.6342999935150146, 0.11779999732971191], [0.4390000104904175, 0.30799999833106995], [0.48489999771118164, 0.1509999930858612], [0.4244999885559082, 0.19830000400543213], [0.5996999740600586, 0.11540000140666962], [0.4611000120639801, 0.2754000127315521], [0.44440001249313354, 0.17000000178813934], [0.732699990272522, 0.18649999797344208], [0.6312000155448914, 0.22990000247955322], [0.6434000134468079, 0.16529999673366547], [0.47200000286102295, 0.15459999442100525], [0.6557000279426575, 0.16189999878406525], [0.6247000098228455, 0.18809999525547028], [0.4442000091075897, 0.18889999389648438], [0.4056999981403351, 0.27079999446868896], [0.47269999980926514, 0.1387999951839447], [0.44369998574256897, 0.16269999742507935]], "descending_left": [[0.5401999950408936, 0.8131999969482422], [0.40639999508857727, 0.6970000267028809], [0.4909999966621399, 0.1882999986410141], [0.4074000120162964, 0.6829000115394592], [0.3935999870300293, 0.2971000075340271], [0.4293000102043152, 0.7031999826431274], [0.3652999997138977, 0.6520000100135803], [0.41200000047683716, 0.6754000186920166], [0.42399999499320984, 0.6789000034332275], [0.4088999927043915, 0.6209999918937683], [0.4138999879360199, 0.6761999726295471], [0.5412999987602234, 0.8076000213623047], [0.5327000021934509, 0.3799000084400177], [0.4077000021934509, 0.6899999976158142], [0.4300000071525574, 0.4413999915122986], [0.4302000105381012, 0.6330000162124634], [0.5072000026702881, 0.27810001373291016], [0.41499999165534973, 0.6687999963760376], [0.4185999929904938, 0.6985999941825867], [0.5181999802589417, 0.34139999747276306], [0.505299985408783, 0.32919999957084656], [0.4523000121116638, 0.3806999921798706], [0.38269999623298645, 0.5388000011444092], [0.5299000144004822, 0.34209999442100525], [0.4115999937057495, 0.31220000982284546], [0.37070000171661377, 0.5770000219345093], [0.4986000061035156, 0.79339998960495], [0.40619999170303345, 0.6766999959945679], [0.5281000137329102, 0.3880999982357025], [0.43849998712539673, 0.3840000033378601], [0.5113000273704529, 0.40290001034736633], [0.516700029373169, 0.7562000155448914], [0.44130000472068787, 0.7239999771118164], [0.40529999136924744, 0.6748999953269958], [0.5188999772071838, 0.8032000064849854], [0.4059000015258789, 0.661300003528595], [0.3917999863624573, 0.6456000208854675], [0.4049000144004822, 0.640500009059906], [0.3763999938964844, 0.5676000118255615], [0.4108000099658966, 0.6851999759674072], [0.5404000282287598, 0.7929999828338623], [0.4404999911785126, 0.38359999656677246], [0.5311999917030334, 0.3741999864578247], [0.45239999890327454, 0.6047000288963318], [0.43939998745918274, 0.7620000243186951], [0.40049999952316284, 0.6414999961853027], [0.38260000944137573, 0.65420001745224], [0.506600022315979, 0.7702999711036682], [0.4221000075340271, 0.3131999969482422], [0.4169999957084656, 0.6873999834060669], [0.5108000040054321, 0.7968999743461609], [0.5099999904632568, 0.7267000079154968], [0.42570000886917114, 0.7283999919891357], [0.37400001287460327, 0.6564000248908997], [0.5371000170707703, 0.805899977684021], [0.46889999508857727, 0.7825999855995178], [0.37380000948905945, 0.6496999859809875], [0.534500002861023, 0.8105999827384949], [0.5357999801635742, 0.35010001063346863], [0.41179999709129333, 0.27140000462532043], [0.4081999957561493, 0.6579999923706055], [0.40869998931884766, 0.6984999775886536], [0.43799999356269836, 0.7003999948501587], [0.5055000185966492, 0.762499988079071], [0.40070000290870667, 0.6632999777793884], [0.41280001401901245, 0.6759999990463257], [0.42590001225471497, 0.6922000050544739], [0.5139999985694885, 0.5523999929428101], [0.4250999987125397, 0.7218999862670898], [0.5206000208854675, 0.8069999814033508], [0.4027000069618225, 0.311599999666214], [0.49869999289512634, 0.7979999780654907], [0.4250999987125397, 0.6899999976158142], [0.47440001368522644, 0.38029998540878296], [0.5102999806404114, 0.7511000037193298], [0.4244999885559082, 0.6909999847412109], [0.42489999532699585, 0.36059999465942383], [0.4205000102519989, 0.6773999929428101], [0.37720000743865967, 0.6514000296592712], [0.45019999146461487, 0.37790000438690186], [0.4291999936103821, 0.6779000163078308], [0.41359999775886536, 0.4602999985218048], [0.397599995136261, 0.6377999782562256], [0.4871000051498413, 0.7623000144958496], [0.4205999970436096, 0.2443999946117401], [0.45680001378059387, 0.7839999794960022], [0.4401000142097473, 0.7549999952316284], [0.40130001306533813, 0.6626999974250793], [0.43869999051094055, 0.3479999899864197], [0.539900004863739, 0.4172999858856201], [0.5105999708175659, 0.7922999858856201], [0.4169999957084656, 0.6869999766349792], [0.5343999862670898, 0.41670000553131104], [0.3824000060558319, 0.2847000062465668], [0.4074000120162964, 0.6671000123023987], [0.39809998869895935, 0.656499981880188], [0.461899995803833, 0.3199999928474426], [0.4131999909877777, 0.6866000294685364], [0.4101000130176544, 0.3244999945163727], [0.460099995136261, 0.35350000858306885], [0.4049000144004822, 0.6266000270843506], [0.5246999859809875, 0.33880001306533813], [0.5418999791145325, 0.21870000660419464], [0.48730000853538513, 0.7860999703407288], [0.44760000705718994, 0.7240999937057495], [0.5210000276565552, 0.7839000225067139], [0.5333999991416931, 0.3626999855041504], [0.4325000047683716, 0.34619998931884766], [0.5209000110626221, 0.1404999941587448], [0.42590001225471497, 0.7093999981880188], [0.4097000062465668, 0.6869999766349792], [0.42820000648498535, 0.6790000200271606], [0.4032999873161316, 0.6607000231742859], [0.5099999904632568, 0.800599992275238], [0.43389999866485596, 0.3416999876499176], [0.5084999799728394, 0.37549999356269836], [0.5303000211715698, 0.7804999947547913], [0.4196999967098236, 0.6883000135421753], [0.4074999988079071, 0.6524999737739563], [0.40070000290870667, 0.6535999774932861], [0.49239999055862427, 0.3375999927520752], [0.5320000052452087, 0.4596000015735626], [0.42640000581741333, 0.7014999985694885], [0.40959998965263367, 0.6917999982833862], [0.42250001430511475, 0.7014999985694885], [0.47110000252723694, 0.36719998717308044], [0.5148000121116638, 0.791100025177002], [0.39959999918937683, 0.30869999527931213], [0.4235999882221222, 0.3781999945640564], [0.5360000133514404, 0.7937999963760376], [0.413100004196167, 0.6104000210762024], [0.5404999852180481, 0.17599999904632568], [0.5282999873161316, 0.414000004529953], [0.4163999855518341, 0.682699978351593], [0.4544000029563904, 0.776199996471405], [0.4180000126361847, 0.6873999834060669], [0.43970000743865967, 0.36039999127388], [0.4948999881744385, 0.7886999845504761], [0.4097000062465668, 0.30889999866485596], [0.3937999904155731, 0.649399995803833], [0.47609999775886536, 0.21250000596046448], [0.45249998569488525, 0.33469998836517334], [0.5056999921798706, 0.761900007724762], [0.41839998960494995, 0.46239998936653137], [0.474700003862381, 0.32339999079704285], [0.5368000268936157, 0.8068000078201294], [0.41510000824928284, 0.6844000220298767], [0.52920001745224, 0.35010001063346863], [0.4244000017642975, 0.6873999834060669], [0.4228000044822693, 0.1624000072479248], [0.4733999967575073, 0.20569999516010284], [0.4074999988079071, 0.6819000244140625], [0.4401000142097473, 0.708899974822998], [0.4275999963283539, 0.6854000091552734], [0.4228000044822693, 0.7120000123977661], [0.41359999775886536, 0.6869999766349792], [0.5404999852180481, 0.7961999773979187], [0.41839998960494995, 0.6879000067710876], [0.45089998841285706, 0.35089999437332153], [0.4255000054836273, 0.40380001068115234], [0.4250999987125397, 0.6895999908447266], [0.3977000117301941, 0.645799994468689], [0.4706999957561493, 0.30090001225471497], [0.44859999418258667, 0.760699987411499], [0.44999998807907104, 0.7710999846458435], [0.3824000060558319, 0.6542999744415283], [0.4659000039100647, 0.7890999913215637], [0.4146000146865845, 0.670799970626831], [0.4422000050544739, 0.7167999744415283], [0.40059998631477356, 0.6601999998092651], [0.413100004196167, 0.6541000008583069], [0.42239999771118164, 0.6960999965667725], [0.40299999713897705, 0.6804999709129333], [0.4133000075817108, 0.6833999752998352], [0.41760000586509705, 0.6840000152587891], [0.5105000138282776, 0.7706000208854675], [0.40540000796318054, 0.4147000014781952], [0.5378999710083008, 0.8039000034332275], [0.5299999713897705, 0.8015999794006348], [0.5318999886512756, 0.39309999346733093], [0.43549999594688416, 0.734000027179718], [0.428600013256073, 0.723800003528595], [0.42640000581741333, 0.6965000033378601], [0.5299999713897705, 0.4007999897003174], [0.4207000136375427, 0.4309999942779541], [0.3937999904155731, 0.3321000039577484], [0.3986000120639801, 0.40220001339912415], [0.43470001220703125, 0.37470000982284546], [0.5368000268936157, 0.7921000123023987], [0.4431999921798706, 0.6886000037193298], [0.4088999927043915, 0.6775000095367432], [0.45820000767707825, 0.7804999947547913], [0.5622000098228455, 0.7888000011444092], [0.47269999980926514, 0.19370000064373016], [0.5008000135421753, 0.3337000012397766], [0.3970000147819519, 0.30570000410079956], [0.4000999927520752, 0.2870999872684479], [0.5065000057220459, 0.14390000700950623], [0.41659998893737793, 0.6840000152587891], [0.42250001430511475, 0.36959999799728394], [0.5382999777793884, 0.7955999970436096], [0.4009999930858612, 0.6699000000953674], [0.5159000158309937, 0.4749999940395355], [0.5303000211715698, 0.8080999851226807], [0.5138000249862671, 0.1193000003695488], [0.4131999909877777, 0.6186000108718872], [0.5403000116348267, 0.7839999794960022], [0.4009999930858612, 0.6710000038146973], [0.3804999887943268, 0.6470000147819519], [0.4278999865055084, 0.6909999847412109], [0.482699990272522, 0.4794999957084656], [0.42100000381469727, 0.7197999954223633], [0.4325999915599823, 0.6561999917030334], [0.5357000231742859, 0.7874000072479248], [0.43059998750686646, 0.733299970626831], [0.42260000109672546, 0.6973999738693237], [0.4219000041484833, 0.7031000256538391], [0.53329998254776, 0.4542999863624573], [0.4814000129699707, 0.6215999722480774], [0.4578000009059906, 0.7276999950408936], [0.4575999975204468, 0.3395000100135803], [0.517300009727478, 0.7929999828338623], [0.4359999895095825, 0.6926000118255615], [0.4616999924182892, 0.19580000638961792], [0.42809998989105225, 0.6922000050544739], [0.5310999751091003, 0.3815000057220459], [0.4203999936580658, 0.6933000087738037], [0.5304999947547913, 0.8130999803543091], [0.42089998722076416, 0.6934000253677368], [0.44929999113082886, 0.7653999924659729], [0.4205999970436096, 0.6956999897956848], [0.41269999742507935, 0.6646999716758728], [0.5354999899864197, 0.8021000027656555], [0.4000000059604645, 0.6492999792098999], [0.4900999963283539, 0.21690000593662262], [0.41200000047683716, 0.6205999851226807], [0.5200999975204468, 0.8082000017166138], [0.41670000553131104, 0.6899999976158142], [0.4742000102996826, 0.7646999955177307], [0.42010000348091125, 0.6517000198364258], [0.44699999690055847, 0.7734000086784363], [0.46560001373291016, 0.351500004529953], [0.48339998722076416, 0.7936000227928162], [0.40959998965263367, 0.6816999912261963], [0.45509999990463257, 0.33649998903274536], [0.5148000121116638, 0.6053000092506409], [0.4691999852657318, 0.33489999175071716], [0.41499999165534973, 0.6852999925613403], [0.4717999994754791, 0.19499999284744263], [0.4717000126838684, 0.7831000089645386]], "descending_right": [[0.5802000164985657, 0.21809999644756317], [0.5663999915122986, 0.777899980545044], [0.6396999955177307, 0.367000013589859], [0.6819000244140625, 0.29580000042915344], [0.6711000204086304, 0.35040000081062317], [0.673799991607666, 0.7382000088691711], [0.6220999956130981, 0.791100025177002], [0.6682999730110168, 0.6496000289916992], [0.5673999786376953, 0.5629000067710876], [0.6161999702453613, 0.23549999296665192], [0.6747999787330627, 0.7247999906539917], [0.666700005531311, 0.7260000109672546], [0.6675999760627747, 0.7247999906539917], [0.6366999745368958, 0.3255000114440918], [0.544700026512146, 0.785099983215332], [0.6679999828338623, 0.7080000042915344], [0.550000011920929, 0.8044999837875366], [0.632099986076355, 0.3961000144481659], [0.6243000030517578, 0.7900999784469604], [0.6312999725341797, 0.7692999839782715], [0.618399977684021, 0.796999990940094], [0.5509999990463257, 0.8127999901771545], [0.6786999702453613, 0.736299991607666], [0.6642000079154968, 0.7077000141143799], [0.682200014591217, 0.7351999878883362], [0.5748000144958496, 0.32919999957084656], [0.6694999933242798, 0.7128000259399414], [0.6747000217437744, 0.5108000040054321], [0.6028000116348267, 0.4156000018119812], [0.6273000240325928, 0.7836999893188477], [0.651199996471405, 0.6876000165939331], [0.5856999754905701, 0.7804999947547913], [0.5537999868392944, 0.36329999566078186], [0.6564000248908997, 0.7404000163078308], [0.5659000277519226, 0.10300000011920929], [0.6019999980926514, 0.7904000282287598], [0.6571000218391418, 0.46549999713897705], [0.6599000096321106, 0.7213000059127808], [0.5835000276565552, 0.25949999690055847], [0.6679999828338623, 0.7077000141143799], [0.64410001039505, 0.7620000243186951], [0.6425999999046326, 0.7717999815940857], [0.666700005531311, 0.73089998960495], [0.6730999946594238, 0.3118000030517578], [0.6481000185012817, 0.34940001368522644], [0.5950000286102295, 0.7731999754905701], [0.63919997215271, 0.4697999954223633], [0.661300003528595, 0.25949999690055847], [0.6621999740600586, 0.6872000098228455], [0.6245999932289124, 0.36570000648498535], [0.6416000127792358, 0.7516000270843506], [0.6545000076293945, 0.7114999890327454], [0.5613999962806702, 0.776199996471405], [0.5458999872207642, 0.8093000054359436], [0.6509000062942505, 0.713699996471405], [0.6344000101089478, 0.7712000012397766], [0.6367999911308289, 0.7548999786376953], [0.6588000059127808, 0.7174999713897705], [0.6811000108718872, 0.7095000147819519], [0.6439999938011169, 0.7605000138282776], [0.6172999739646912, 0.7900000214576721], [0.583299994468689, 0.8076000213623047], [0.6287000179290771, 0.7210000157356262], [0.6657999753952026, 0.3806000053882599], [0.5669999718666077, 0.5720999836921692], [0.685699999332428, 0.7360000014305115], [0.6287999749183655, 0.3517000079154968], [0.6363000273704529, 0.3880999982357025], [0.5616000294685364, 0.349700003862381], [0.6732000112533569, 0.6888999938964844], [0.600600004196167, 0.21649999916553497], [0.6259999871253967, 0.3571999967098236], [0.6524999737739563, 0.41179999709129333], [0.6633999943733215, 0.7350999712944031], [0.6237000226974487, 0.2800999879837036], [0.6772000193595886, 0.6814000010490417], [0.6764000058174133, 0.7125999927520752], [0.6570000052452087, 0.7251999974250793], [0.6270999908447266, 0.7526999711990356], [0.5559999942779541, 0.786899983882904], [0.6534000039100647, 0.27730000019073486], [0.5764999985694885, 0.73089998960495], [0.5699999928474426, 0.7817999720573425], [0.6751999855041504, 0.7164999842643738], [0.666700005531311, 0.7253000140190125], [0.6262999773025513, 0.7821999788284302], [0.6704999804496765, 0.6686000227928162], [0.5946999788284302, 0.36660000681877136], [0.5670999884605408, 0.17520000040531158], [0.5450999736785889, 0.1753000020980835], [0.6233999729156494, 0.789900004863739], [0.6241000294685364, 0.7870000004768372], [0.6919999718666077, 0.7355999946594238], [0.6656000018119812, 0.7253000140190125], [0.5802000164985657, 0.32850000262260437], [0.6467999815940857, 0.7228999733924866], [0.5849000215530396, 0.3458999991416931], [0.5533999800682068, 0.7831000089645386], [0.6385999917984009, 0.7644000053405762], [0.6509000062942505, 0.4117000102996826], [0.5437999963760376, 0.3874000012874603], [0.660099983215332, 0.33889999985694885], [0.6869999766349792, 0.73580002784729], [0.690500020980835, 0.6080999970436096], [0.5794000029563904, 0.7894999980926514], [0.6916999816894531, 0.505299985408783], [0.6833000183105469, 0.7245000004768372], [0.5828999876976013, 0.789900004863739], [0.6583999991416931, 0.7150999903678894], [0.5843999981880188, 0.7372999787330627], [0.6474000215530396, 0.7544999718666077], [0.640999972820282, 0.7383000254631042], [0.6446999907493591, 0.33250001072883606], [0.6811000108718872, 0.7317000031471252], [0.6237999796867371, 0.7594000101089478], [0.6608999967575073, 0.38510000705718994], [0.6593999862670898, 0.3303999900817871], [0.6050999760627747, 0.7608000040054321], [0.5827999711036682, 0.3398999869823456], [0.553600013256073, 0.785099983215332], [0.6708999872207642, 0.3070000112056732], [0.6442999839782715, 0.33970001339912415], [0.6478999853134155, 0.754800021648407], [0.5582000017166138, 0.34459999203681946], [0.5871000289916992, 0.17309999465942383], [0.6610000133514404, 0.7153000235557556], [0.6662999987602234, 0.7038000226020813], [0.666700005531311, 0.6292999982833862], [0.6827999949455261, 0.7156999707221985], [0.6794999837875366, 0.7283999919891357], [0.6226999759674072, 0.7541000247001648], [0.6988999843597412, 0.7228999733924866], [0.6574000120162964, 0.7404999732971191], [0.629800021648407, 0.33970001339912415], [0.6254000067710876, 0.3327000141143799], [0.6517999768257141, 0.5105999708175659], [0.6625999808311462, 0.7401999831199646], [0.6798999905586243, 0.6937000155448914], [0.6021000146865845, 0.23739999532699585], [0.6552000045776367, 0.7336000204086304], [0.6646999716758728, 0.2912999987602234], [0.630299985408783, 0.7839000225067139], [0.6834999918937683, 0.33739998936653137], [0.5552999973297119, 0.7745000123977661], [0.6690999865531921, 0.7005000114440918], [0.683899998664856, 0.7307999730110168], [0.5652999877929688, 0.8001000285148621], [0.6657000184059143, 0.7069000005722046], [0.6154000163078308, 0.7567999958992004], [0.6607000231742859, 0.7290999889373779], [0.5630999803543091, 0.5874999761581421], [0.5788000226020813, 0.2020999938249588], [0.6682999730110168, 0.6793000102043152], [0.6248000264167786, 0.6388000249862671], [0.6349999904632568, 0.7797999978065491], [0.574400007724762, 0.34790000319480896], [0.6736999750137329, 0.7171000242233276], [0.5758000016212463, 0.13570000231266022], [0.5479000210762024, 0.7983999848365784], [0.6480000019073486, 0.7099999785423279], [0.5389000177383423, 0.3677999973297119], [0.6815000176429749, 0.7017999887466431], [0.6395000219345093, 0.7681999802589417], [0.636900007724762, 0.7803000211715698], [0.5680999755859375, 0.36340001225471497], [0.6392999887466431, 0.7768999934196472], [0.6597999930381775, 0.4925999939441681], [0.6646000146865845, 0.38929998874664307], [0.5684999823570251, 0.34040001034736633], [0.5943999886512756, 0.6273000240325928], [0.6330999732017517, 0.3409999907016754], [0.6589999794960022, 0.7161999940872192], [0.6298999786376953, 0.7840999960899353], [0.6554999947547913, 0.7297999858856201], [0.6561999917030334, 0.72079998254776], [0.6373999714851379, 0.734000027179718], [0.6626999974250793, 0.7008000016212463], [0.6043999791145325, 0.7904000282287598], [0.576200008392334, 0.7968999743461609], [0.6269999742507935, 0.7444000244140625], [0.5730000138282776, 0.7797999978065491], [0.671500027179718, 0.484499990940094], [0.6611999869346619, 0.7149999737739563], [0.6624000072479248, 0.3635999858379364], [0.6905999779701233, 0.28850001096725464], [0.6327000260353088, 0.5188000202178955], [0.5720999836921692, 0.5809000134468079], [0.6614000201225281, 0.23960000276565552], [0.6553999781608582, 0.40299999713897705], [0.6489999890327454, 0.7077000141143799], [0.5583999752998352, 0.8043000102043152], [0.6607999801635742, 0.745199978351593], [0.6626999974250793, 0.732200026512146], [0.6151000261306763, 0.6237999796867371], [0.644599974155426, 0.7649999856948853], [0.6470999717712402, 0.3357999920845032], [0.6154000163078308, 0.23000000417232513], [0.6786999702453613, 0.36340001225471497], [0.675599992275238, 0.7016000151634216], [0.6442000269889832, 0.5246000289916992], [0.6406000256538391, 0.7601000070571899], [0.6633999943733215, 0.7249000072479248], [0.6952999830245972, 0.3005000054836273], [0.5594000220298767, 0.7870000004768372], [0.6148999929428101, 0.2329999953508377], [0.6122000217437744, 0.7594000101089478], [0.6660000085830688, 0.70169997215271], [0.6207000017166138, 0.758400022983551], [0.661300003528595, 0.7141000032424927], [0.6592000126838684, 0.7240999937057495], [0.5925999879837036, 0.3357999920845032], [0.6391000151634216, 0.771399974822998], [0.6858000159263611, 0.7138000130653381], [0.6687999963760376, 0.48590001463890076], [0.6345999836921692, 0.779699981212616], [0.576200008392334, 0.7748000025749207], [0.6802999973297119, 0.7074999809265137], [0.6588000059127808, 0.7384999990463257], [0.6680999994277954, 0.7236999869346619], [0.6739000082015991, 0.7024000287055969], [0.5597000122070312, 0.1590999960899353], [0.5735999941825867, 0.1151999980211258], [0.5439000129699707, 0.4032999873161316], [0.675599992275238, 0.739799976348877], [0.664900004863739, 0.7247999906539917], [0.5699999928474426, 0.7986999750137329], [0.5594000220298767, 0.40619999170303345], [0.675000011920929, 0.6761000156402588], [0.5253999829292297, 0.7387999892234802], [0.6934999823570251, 0.3109000027179718], [0.54339998960495, 0.7064999938011169], [0.6776999831199646, 0.7224000096321106], [0.6269000172615051, 0.7770000100135803], [0.5733000040054321, 0.34150001406669617], [0.682699978351593, 0.7106000185012817], [0.5005999803543091, 0.7810999751091003], [0.6538000106811523, 0.7240999937057495], [0.6324999928474426, 0.3280999958515167], [0.6305000185966492, 0.7871000170707703], [0.7041000127792358, 0.7235000133514404], [0.6676999926567078, 0.3720000088214874], [0.6496000289916992, 0.33469998836517334], [0.5781000256538391, 0.7854999899864197], [0.5471000075340271, 0.34599998593330383], [0.5885000228881836, 0.34139999747276306], [0.6621999740600586, 0.3587999939918518], [0.5867000222206116, 0.8030999898910522], [0.6269000172615051, 0.7894999980926514], [0.6940000057220459, 0.4293000102043152], [0.6402000188827515, 0.774399995803833]], "central_brain": [[0.6934999823570251, 0.5620999932289124], [0.6639999747276306, 0.6942999958992004], [0.4896000027656555, 0.3528999984264374], [0.6643999814987183, 0.38830000162124634], [0.28459998965263367, 0.2485000044107437], [0.296999990940094, 0.34599998593330383], [0.29919999837875366, 0.3125999867916107], [0.3025999963283539, 0.27790001034736633], [0.2937999963760376, 0.3287999927997589], [0.7020000219345093, 0.5327000021934509], [0.5220999717712402, 0.23100000619888306], [0.4586000144481659, 0.5401999950408936], [0.6413999795913696, 0.19660000503063202], [0.6026999950408936, 0.2694999873638153], [0.41659998893737793, 0.4715999960899353], [0.585099995136261, 0.7476999759674072], [0.323199987411499, 0.30570000410079956], [0.6154999732971191, 0.6524999737739563], [0.3366999924182892, 0.32839998602867126], [0.511900007724762, 0.7580999732017517], [0.6556000113487244, 0.6995000243186951], [0.304500013589859, 0.2906000018119812], [0.43779999017715454, 0.33970001339912415], [0.5060999989509583, 0.7800999879837036], [0.6309000253677368, 0.4490000009536743], [0.3955000042915344, 0.44279998540878296], [0.49810001254081726, 0.5965999960899353], [0.30239999294281006, 0.2865000069141388], [0.3668000102043152, 0.2062000036239624], [0.7203999757766724, 0.38449999690055847], [0.7174000144004822, 0.352400004863739], [0.31130000948905945, 0.2637999951839447], [0.781000018119812, 0.32120001316070557], [0.43050000071525574, 0.6471999883651733], [0.28949999809265137, 0.24469999969005585], [0.34929999709129333, 0.2249000072479248], [0.7584999799728394, 0.1793999969959259], [0.7993000149726868, 0.2669999897480011], [0.33250001072883606, 0.31520000100135803], [0.42179998755455017, 0.3515999913215637], [0.37299999594688416, 0.34880000352859497], [0.6435999870300293, 0.39890000224113464], [0.47519999742507935, 0.1881999969482422], [0.39309999346733093, 0.3707999885082245], [0.40149998664855957, 0.3278999924659729], [0.6236000061035156, 0.4814000129699707], [0.603600025177002, 0.19910000264644623], [0.4068000018596649, 0.6746000051498413], [0.29490000009536743, 0.27079999446868896], [0.748199999332428, 0.28049999475479126], [0.44339999556541443, 0.6159999966621399], [0.6671000123023987, 0.555400013923645], [0.6496000289916992, 0.720300018787384], [0.2849999964237213, 0.3824000060558319], [0.3813000023365021, 0.29899999499320984], [0.5784000158309937, 0.17599999904632568], [0.7123000025749207, 0.6843000054359436], [0.4406999945640564, 0.4584999978542328], [0.7573999762535095, 0.257999986410141], [0.7797999978065491, 0.3544999957084656], [0.35920000076293945, 0.3116999864578247], [0.3188999891281128, 0.5526000261306763], [0.7218000292778015, 0.34139999747276306], [0.2955999970436096, 0.27079999446868896], [0.5339999794960022, 0.19040000438690186], [0.3781999945640564, 0.22470000386238098], [0.6636000275611877, 0.34360000491142273], [0.7351999878883362, 0.6545000076293945], [0.31060001254081726, 0.22939999401569366], [0.5157999992370605, 0.23559999465942383], [0.6974999904632568, 0.3792000114917755], [0.7792999744415283, 0.23999999463558197], [0.41119998693466187, 0.30169999599456787], [0.38850000500679016, 0.5662999749183655], [0.2833999991416931, 0.2542000114917755], [0.6212999820709229, 0.6481000185012817], [0.3555999994277954, 0.2565000057220459], [0.6603999733924866, 0.0885000005364418], [0.4871000051498413, 0.40369999408721924], [0.774399995803833, 0.3431999981403351], [0.48669999837875366, 0.5848000049591064], [0.49219998717308044, 0.2678999900817871], [0.3050000071525574, 0.5052000284194946], [0.4498000144958496, 0.5803999900817871], [0.5401999950408936, 0.6938999891281128], [0.2867000102996826, 0.26010000705718994], [0.7542999982833862, 0.3100999891757965], [0.4519999921321869, 0.7373999953269958], [0.7874000072479248, 0.3589000105857849], [0.2971000075340271, 0.33880001306533813], [0.6685000061988831, 0.3149000108242035], [0.7749999761581421, 0.38339999318122864], [0.3797999918460846, 0.30079999566078186], [0.49070000648498535, 0.4422000050544739], [0.6445000171661377, 0.4921000003814697], [0.38119998574256897, 0.11460000276565552], [0.3319999873638153, 0.30979999899864197], [0.7907000184059143, 0.24140000343322754], [0.7547000050544739, 0.531000018119812], [0.30970001220703125, 0.5005999803543091], [0.4250999987125397, 0.7233999967575073], [0.38040000200271606, 0.5008999705314636], [0.7511000037193298, 0.22429999709129333], [0.42170000076293945, 0.44830000400543213], [0.34869998693466187, 0.5828999876976013], [0.3531999886035919, 0.2824000120162964], [0.3562999963760376, 0.3402000069618225], [0.7734000086784363, 0.20000000298023224], [0.6409000158309937, 0.36320000886917114], [0.28760001063346863, 0.27649998664855957], [0.6636000275611877, 0.6837999820709229], [0.32199999690055847, 0.17890000343322754], [0.6926000118255615, 0.34540000557899475], [0.31859999895095825, 0.25769999623298645], [0.5751000046730042, 0.22869999706745148], [0.3955000042915344, 0.6431999802589417], [0.4675999879837036, 0.7610999941825867], [0.6330000162124634, 0.38119998574256897], [0.451200008392334, 0.3732999861240387], [0.6481000185012817, 0.46309998631477356], [0.6622999906539917, 0.7229999899864197], [0.6822999715805054, 0.6995000243186951], [0.4514000117778778, 0.5979999899864197], [0.6406000256538391, 0.18160000443458557], [0.361299991607666, 0.13330000638961792], [0.3831000030040741, 0.2728999853134155], [0.6514000296592712, 0.11980000138282776], [0.45559999346733093, 0.30720001459121704], [0.288100004196167, 0.33649998903274536], [0.7515000104904175, 0.243599995970726], [0.4196999967098236, 0.3052999973297119], [0.7264999747276306, 0.6392999887466431], [0.6025999784469604, 0.40720000863075256], [0.7164000272750854, 0.3912999927997589], [0.45210000872612, 0.6367999911308289], [0.6273000240325928, 0.29190000891685486], [0.423799991607666, 0.7318000197410583], [0.6413000226020813, 0.6791999936103821], [0.6111000180244446, 0.38100001215934753], [0.550599992275238, 0.3919000029563904], [0.7731000185012817, 0.4047999978065491], [0.3411000072956085, 0.2150000035762787], [0.4564000070095062, 0.4876999855041504], [0.43950000405311584, 0.37860000133514404], [0.6420999765396118, 0.6908000111579895], [0.47200000286102295, 0.590399980545044], [0.4715000092983246, 0.7882999777793884], [0.5680999755859375, 0.6266999840736389], [0.6244000196456909, 0.4327000081539154], [0.4009999930858612, 0.29269999265670776], [0.6237999796867371, 0.6326000094413757], [0.4034999907016754, 0.2669000029563904], [0.7426999807357788, 0.15850000083446503], [0.38940000534057617, 0.5339000225067139], [0.6115000247955322, 0.4300000071525574], [0.30570000410079956, 0.2667999863624573], [0.4699999988079071, 0.7282999753952026], [0.7817999720573425, 0.3952000141143799], [0.6377999782562256, 0.3659999966621399], [0.41940000653266907, 0.6549999713897705], [0.5569000244140625, 0.15139999985694885], [0.4032999873161316, 0.6758000254631042], [0.786899983882904, 0.3783000111579895], [0.3222000002861023, 0.35920000076293945], [0.41839998960494995, 0.25619998574256897], [0.43950000405311584, 0.7235000133514404], [0.5681999921798706, 0.1842000037431717], [0.6739000082015991, 0.5562000274658203], [0.7738000154495239, 0.3422999978065491], [0.7942000031471252, 0.301800012588501], [0.7602999806404114, 0.3091000020503998], [0.42890000343322754, 0.3628000020980835], [0.6848999857902527, 0.6072999835014343], [0.694599986076355, 0.3702999949455261], [0.6643999814987183, 0.29249998927116394], [0.4805000126361847, 0.2919999957084656], [0.786899983882904, 0.2660999894142151], [0.28130000829696655, 0.24060000479221344], [0.424699991941452, 0.7161999940872192], [0.326200008392334, 0.26089999079704285], [0.682699978351593, 0.33009999990463257], [0.6037999987602234, 0.15860000252723694], [0.7893999814987183, 0.26809999346733093], [0.45579999685287476, 0.6848000288009644], [0.6797999739646912, 0.33329999446868896], [0.5033000111579895, 0.1031000018119812], [0.6797000169754028, 0.42980000376701355], [0.4138000011444092, 0.612500011920929], [0.6161999702453613, 0.10769999772310257], [0.7735000252723694, 0.4733999967575073], [0.3668999969959259, 0.2971999943256378], [0.7473999857902527, 0.4575999975204468], [0.7458999752998352, 0.1534000039100647], [0.47699999809265137, 0.41370001435279846], [0.7800999879837036, 0.28929999470710754], [0.617900013923645, 0.3723999857902527], [0.29030001163482666, 0.3905999958515167], [0.5066999793052673, 0.24449999630451202], [0.6948000192642212, 0.37059998512268066], [0.5630000233650208, 0.7838000059127808], [0.2948000133037567, 0.1842000037431717], [0.6934999823570251, 0.3695000112056732], [0.7279999852180481, 0.24549999833106995], [0.33959999680519104, 0.6187000274658203], [0.5149000287055969, 0.35429999232292175], [0.519599974155426, 0.13500000536441803], [0.7800999879837036, 0.39070001244544983], [0.6438999772071838, 0.7407000064849854], [0.3917999863624573, 0.09880000352859497], [0.6355999708175659, 0.7246999740600586], [0.28929999470710754, 0.29660001397132874], [0.6281999945640564, 0.6945000290870667], [0.3179999887943268, 0.3269999921321869], [0.4481000006198883, 0.46459999680519104], [0.6305000185966492, 0.7688000202178955], [0.3799000084400177, 0.30959999561309814], [0.5619000196456909, 0.17630000412464142], [0.3237999975681305, 0.5645999908447266], [0.6114000082015991, 0.4465000033378601], [0.3995000123977661, 0.11140000075101852], [0.5544999837875366, 0.7800999879837036], [0.4268999993801117, 0.7268000245094299], [0.5044000148773193, 0.21150000393390656], [0.4083999991416931, 0.3601999878883362], [0.4016000032424927, 0.5227000117301941], [0.7742000222206116, 0.3700999915599823], [0.28999999165534973, 0.45320001244544983], [0.4196999967098236, 0.6657000184059143], [0.7580000162124634, 0.5612999796867371], [0.6438000202178955, 0.6524999737739563], [0.7807999849319458, 0.3695000112056732], [0.6930999755859375, 0.09939999878406525], [0.6917999982833862, 0.43650001287460327], [0.4893999993801117, 0.09070000052452087], [0.49720001220703125, 0.10300000011920929], [0.3010999858379364, 0.3813999891281128], [0.5202999711036682, 0.09179999679327011], [0.3950999975204468, 0.6434999704360962], [0.7597000002861023, 0.31299999356269836], [0.3865000009536743, 0.6287999749183655], [0.4650000035762787, 0.44179999828338623], [0.4970000088214874, 0.28760001063346863], [0.40790000557899475, 0.41200000047683716], [0.5616999864578247, 0.39969998598098755], [0.7842000126838684, 0.36239999532699585], [0.6600000262260437, 0.421099990606308], [0.7990999817848206, 0.25609999895095825], [0.4142000079154968, 0.38929998874664307], [0.29910001158714294, 0.3910999894142151], [0.376800000667572, 0.49070000648498535]]}
|
web/brain_anatomy_3d.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
web/index.html
ADDED
|
@@ -0,0 +1,1083 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>DOOM-FlyWire: 3D 139,248-Neuron Connectome Neural Activity Visualizer</title>
|
| 7 |
+
<meta name="description" content="A real-time whole-brain biological digital twin running the complete Drosophila melanogaster connectome (FlyWire Nature 2024) inside ViZDoom with an interactive 3D WebGL neural activity visualizer.">
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700;800&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
| 11 |
+
|
| 12 |
+
<!-- Three.js + OrbitControls (Served locally for 100% reliability, zero CDN dependence) -->
|
| 13 |
+
<script src="/js/three.min.js"></script>
|
| 14 |
+
<script src="/js/OrbitControls.js"></script>
|
| 15 |
+
|
| 16 |
+
<style>
|
| 17 |
+
:root {
|
| 18 |
+
--bg-dark: #06080e;
|
| 19 |
+
--card-bg: rgba(13, 17, 28, 0.85);
|
| 20 |
+
--card-border: rgba(0, 245, 212, 0.2);
|
| 21 |
+
--accent-cyan: #00faff;
|
| 22 |
+
--accent-blue: #0d61ff;
|
| 23 |
+
--accent-purple: #b840ff;
|
| 24 |
+
--accent-pink: #ff2685;
|
| 25 |
+
--accent-amber: #ffbf1a;
|
| 26 |
+
--accent-emerald: #14ff33;
|
| 27 |
+
--accent-danger: #ef4444;
|
| 28 |
+
--text-main: #f8fafc;
|
| 29 |
+
--text-muted: #94a3b8;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
* {
|
| 33 |
+
box-sizing: border-box;
|
| 34 |
+
margin: 0;
|
| 35 |
+
padding: 0;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
body {
|
| 39 |
+
background-color: var(--bg-dark);
|
| 40 |
+
background-image:
|
| 41 |
+
radial-gradient(circle at 10% 20%, rgba(0, 245, 212, 0.06) 0%, transparent 45%),
|
| 42 |
+
radial-gradient(circle at 90% 80%, rgba(192, 132, 252, 0.07) 0%, transparent 50%),
|
| 43 |
+
radial-gradient(circle at 50% 50%, rgba(16, 185, 129, 0.03) 0%, transparent 60%);
|
| 44 |
+
color: var(--text-main);
|
| 45 |
+
font-family: 'Outfit', sans-serif;
|
| 46 |
+
min-height: 100vh;
|
| 47 |
+
overflow-x: hidden;
|
| 48 |
+
padding-bottom: 30px;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
header {
|
| 52 |
+
padding: 14px 28px;
|
| 53 |
+
display: flex;
|
| 54 |
+
justify-content: space-between;
|
| 55 |
+
align-items: center;
|
| 56 |
+
border-bottom: 1px solid var(--card-border);
|
| 57 |
+
backdrop-filter: blur(14px);
|
| 58 |
+
background: rgba(6, 8, 14, 0.9);
|
| 59 |
+
position: sticky;
|
| 60 |
+
top: 0;
|
| 61 |
+
z-index: 100;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.brand-title {
|
| 65 |
+
display: flex;
|
| 66 |
+
align-items: center;
|
| 67 |
+
gap: 12px;
|
| 68 |
+
font-size: 1.35rem;
|
| 69 |
+
font-weight: 800;
|
| 70 |
+
letter-spacing: 0.5px;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.brand-badge {
|
| 74 |
+
background: linear-gradient(135deg, var(--accent-cyan), #00bb99);
|
| 75 |
+
color: #000;
|
| 76 |
+
font-family: 'JetBrains Mono', monospace;
|
| 77 |
+
font-size: 0.72rem;
|
| 78 |
+
padding: 3px 8px;
|
| 79 |
+
border-radius: 4px;
|
| 80 |
+
font-weight: 800;
|
| 81 |
+
letter-spacing: 0.5px;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.header-stats {
|
| 85 |
+
display: flex;
|
| 86 |
+
align-items: center;
|
| 87 |
+
gap: 16px;
|
| 88 |
+
font-family: 'JetBrains Mono', monospace;
|
| 89 |
+
font-size: 0.85rem;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.status-pill {
|
| 93 |
+
display: flex;
|
| 94 |
+
align-items: center;
|
| 95 |
+
gap: 6px;
|
| 96 |
+
padding: 4px 12px;
|
| 97 |
+
border-radius: 20px;
|
| 98 |
+
background: rgba(16, 185, 129, 0.15);
|
| 99 |
+
border: 1px solid rgba(16, 185, 129, 0.4);
|
| 100 |
+
color: var(--accent-emerald);
|
| 101 |
+
font-size: 0.75rem;
|
| 102 |
+
font-weight: 700;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
.status-dot {
|
| 106 |
+
width: 8px;
|
| 107 |
+
height: 8px;
|
| 108 |
+
border-radius: 50%;
|
| 109 |
+
background: var(--accent-emerald);
|
| 110 |
+
box-shadow: 0 0 10px var(--accent-emerald);
|
| 111 |
+
animation: pulse 1.8s infinite;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
@keyframes pulse {
|
| 115 |
+
0%, 100% { opacity: 1; transform: scale(1); }
|
| 116 |
+
50% { opacity: 0.4; transform: scale(0.85); }
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.main-grid {
|
| 120 |
+
display: grid;
|
| 121 |
+
grid-template-columns: 1fr 1fr;
|
| 122 |
+
gap: 20px;
|
| 123 |
+
max-width: 1540px;
|
| 124 |
+
margin: 20px auto;
|
| 125 |
+
padding: 0 24px;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
.glass-card {
|
| 129 |
+
background: var(--card-bg);
|
| 130 |
+
border: 1px solid var(--card-border);
|
| 131 |
+
border-radius: 14px;
|
| 132 |
+
padding: 20px;
|
| 133 |
+
backdrop-filter: blur(18px);
|
| 134 |
+
box-shadow: 0 10px 35px rgba(0, 0, 0, 0.5);
|
| 135 |
+
display: flex;
|
| 136 |
+
flex-direction: column;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.card-title {
|
| 140 |
+
font-size: 0.95rem;
|
| 141 |
+
font-weight: 700;
|
| 142 |
+
text-transform: uppercase;
|
| 143 |
+
letter-spacing: 1px;
|
| 144 |
+
color: var(--accent-cyan);
|
| 145 |
+
display: flex;
|
| 146 |
+
justify-content: space-between;
|
| 147 |
+
align-items: center;
|
| 148 |
+
margin-bottom: 14px;
|
| 149 |
+
padding-bottom: 10px;
|
| 150 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/* DOOM Viewport */
|
| 154 |
+
.viewport-container {
|
| 155 |
+
position: relative;
|
| 156 |
+
width: 100%;
|
| 157 |
+
aspect-ratio: 4 / 3;
|
| 158 |
+
background: #000;
|
| 159 |
+
border-radius: 10px;
|
| 160 |
+
overflow: hidden;
|
| 161 |
+
border: 2px solid rgba(0, 245, 212, 0.35);
|
| 162 |
+
box-shadow: 0 0 28px rgba(0, 245, 212, 0.15);
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
#doomCanvas {
|
| 166 |
+
width: 100%;
|
| 167 |
+
height: 100%;
|
| 168 |
+
object-fit: cover;
|
| 169 |
+
display: block;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.game-hud {
|
| 173 |
+
display: grid;
|
| 174 |
+
grid-template-columns: repeat(4, 1fr);
|
| 175 |
+
gap: 10px;
|
| 176 |
+
margin-top: 14px;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.hud-box {
|
| 180 |
+
background: rgba(0, 0, 0, 0.55);
|
| 181 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 182 |
+
border-radius: 8px;
|
| 183 |
+
padding: 8px 12px;
|
| 184 |
+
text-align: center;
|
| 185 |
+
font-family: 'JetBrains Mono', monospace;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.hud-label {
|
| 189 |
+
font-size: 0.68rem;
|
| 190 |
+
color: var(--text-muted);
|
| 191 |
+
text-transform: uppercase;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.hud-val {
|
| 195 |
+
font-size: 1.25rem;
|
| 196 |
+
font-weight: 800;
|
| 197 |
+
color: var(--accent-cyan);
|
| 198 |
+
margin-top: 2px;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
/* Action Probabilities */
|
| 202 |
+
.action-bars {
|
| 203 |
+
display: flex;
|
| 204 |
+
flex-direction: column;
|
| 205 |
+
gap: 8px;
|
| 206 |
+
margin-top: 14px;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.action-item {
|
| 210 |
+
display: grid;
|
| 211 |
+
grid-template-columns: 110px 1fr 50px;
|
| 212 |
+
align-items: center;
|
| 213 |
+
gap: 12px;
|
| 214 |
+
font-family: 'JetBrains Mono', monospace;
|
| 215 |
+
font-size: 0.78rem;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.progress-track {
|
| 219 |
+
background: rgba(255, 255, 255, 0.08);
|
| 220 |
+
height: 10px;
|
| 221 |
+
border-radius: 5px;
|
| 222 |
+
overflow: hidden;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.progress-fill {
|
| 226 |
+
height: 100%;
|
| 227 |
+
border-radius: 5px;
|
| 228 |
+
transition: width 0.08s ease-out;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
.fill-left { background: #0d61ff; }
|
| 232 |
+
.fill-right { background: #00faff; }
|
| 233 |
+
.fill-attack { background: #14ff33; }
|
| 234 |
+
|
| 235 |
+
/* 3D Brain Container */
|
| 236 |
+
.brain-canvas-container {
|
| 237 |
+
position: relative;
|
| 238 |
+
width: 100%;
|
| 239 |
+
aspect-ratio: 4 / 3;
|
| 240 |
+
background: radial-gradient(circle at 50% 50%, #0d1322 0%, #04060c 100%);
|
| 241 |
+
border-radius: 10px;
|
| 242 |
+
overflow: hidden;
|
| 243 |
+
border: 2px solid rgba(192, 132, 252, 0.35);
|
| 244 |
+
box-shadow: 0 0 30px rgba(192, 132, 252, 0.15);
|
| 245 |
+
cursor: grab;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
.brain-canvas-container:active {
|
| 249 |
+
cursor: grabbing;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.brain-canvas-container canvas {
|
| 253 |
+
width: 100% !important;
|
| 254 |
+
height: 100% !important;
|
| 255 |
+
display: block;
|
| 256 |
+
position: absolute;
|
| 257 |
+
top: 0;
|
| 258 |
+
left: 0;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
/* Brain Region Chips */
|
| 262 |
+
.region-chips-grid {
|
| 263 |
+
display: grid;
|
| 264 |
+
grid-template-columns: repeat(3, 1fr);
|
| 265 |
+
gap: 10px;
|
| 266 |
+
margin-top: 14px;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.region-chip {
|
| 270 |
+
background: rgba(0, 0, 0, 0.45);
|
| 271 |
+
border: 1px solid rgba(255, 255, 255, 0.07);
|
| 272 |
+
border-radius: 8px;
|
| 273 |
+
padding: 8px 10px;
|
| 274 |
+
font-family: 'JetBrains Mono', monospace;
|
| 275 |
+
font-size: 0.72rem;
|
| 276 |
+
display: flex;
|
| 277 |
+
flex-direction: column;
|
| 278 |
+
gap: 4px;
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.chip-header {
|
| 282 |
+
display: flex;
|
| 283 |
+
align-items: center;
|
| 284 |
+
justify-content: space-between;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
.chip-title-wrap {
|
| 288 |
+
display: flex;
|
| 289 |
+
align-items: center;
|
| 290 |
+
gap: 6px;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
.chip-dot {
|
| 294 |
+
width: 8px;
|
| 295 |
+
height: 8px;
|
| 296 |
+
border-radius: 50%;
|
| 297 |
+
box-shadow: 0 0 6px currentColor;
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
.chip-val {
|
| 301 |
+
font-weight: 800;
|
| 302 |
+
color: #fff;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.chip-sub {
|
| 306 |
+
font-size: 0.65rem;
|
| 307 |
+
color: var(--text-muted);
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
/* Circuit Transmission Pipeline */
|
| 311 |
+
.circuit-pipeline {
|
| 312 |
+
display: flex;
|
| 313 |
+
align-items: center;
|
| 314 |
+
justify-content: space-between;
|
| 315 |
+
gap: 6px;
|
| 316 |
+
background: rgba(0, 0, 0, 0.4);
|
| 317 |
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
| 318 |
+
border-radius: 8px;
|
| 319 |
+
padding: 10px 14px;
|
| 320 |
+
margin-top: 14px;
|
| 321 |
+
font-family: 'JetBrains Mono', monospace;
|
| 322 |
+
font-size: 0.72rem;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
.pipeline-node {
|
| 326 |
+
display: flex;
|
| 327 |
+
flex-direction: column;
|
| 328 |
+
align-items: center;
|
| 329 |
+
gap: 2px;
|
| 330 |
+
padding: 4px 8px;
|
| 331 |
+
border-radius: 4px;
|
| 332 |
+
background: rgba(255, 255, 255, 0.04);
|
| 333 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 334 |
+
transition: all 0.15s ease;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
.pipeline-node.active {
|
| 338 |
+
border-color: var(--accent-cyan);
|
| 339 |
+
background: rgba(0, 245, 212, 0.12);
|
| 340 |
+
box-shadow: 0 0 10px rgba(0, 245, 212, 0.3);
|
| 341 |
+
color: var(--accent-cyan);
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
.pipeline-arrow {
|
| 345 |
+
color: var(--text-muted);
|
| 346 |
+
font-size: 0.85rem;
|
| 347 |
+
animation: pulse 1s infinite alternate;
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
/* Controls Bar */
|
| 351 |
+
.controls-bar {
|
| 352 |
+
display: flex;
|
| 353 |
+
justify-content: space-between;
|
| 354 |
+
align-items: center;
|
| 355 |
+
gap: 10px;
|
| 356 |
+
margin-top: 14px;
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
.btn-action {
|
| 360 |
+
background: rgba(255, 255, 255, 0.06);
|
| 361 |
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
| 362 |
+
color: var(--text-main);
|
| 363 |
+
padding: 6px 14px;
|
| 364 |
+
border-radius: 6px;
|
| 365 |
+
font-size: 0.78rem;
|
| 366 |
+
font-family: 'JetBrains Mono', monospace;
|
| 367 |
+
font-weight: 700;
|
| 368 |
+
cursor: pointer;
|
| 369 |
+
transition: all 0.2s;
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
.btn-action:hover {
|
| 373 |
+
background: var(--accent-cyan);
|
| 374 |
+
color: #000;
|
| 375 |
+
border-color: var(--accent-cyan);
|
| 376 |
+
box-shadow: 0 0 12px rgba(0, 245, 212, 0.4);
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.speed-btn-group {
|
| 380 |
+
display: flex;
|
| 381 |
+
gap: 6px;
|
| 382 |
+
align-items: center;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
@media (max-width: 1100px) {
|
| 386 |
+
.main-grid { grid-template-columns: 1fr; }
|
| 387 |
+
}
|
| 388 |
+
</style>
|
| 389 |
+
</head>
|
| 390 |
+
<body>
|
| 391 |
+
|
| 392 |
+
<div class="main-grid">
|
| 393 |
+
<!-- LEFT: DOOM Viewport & Actions -->
|
| 394 |
+
<div class="glass-card">
|
| 395 |
+
<div class="card-title">
|
| 396 |
+
<span>ViZDoom Perception-Action Viewport</span>
|
| 397 |
+
<span id="stepCounter" style="font-family:'JetBrains Mono'; font-size:0.75rem; color:var(--text-muted);">Step: 0</span>
|
| 398 |
+
</div>
|
| 399 |
+
|
| 400 |
+
<div class="viewport-container">
|
| 401 |
+
<img id="doomCanvas" alt="Live DOOM Frame from Fruit Fly Connectome">
|
| 402 |
+
<div id="targetLockBadge" style="display:none; position:absolute; top:12px; right:12px; background:rgba(239,68,68,0.9); color:#fff; font-family:'JetBrains Mono'; font-size:0.75rem; font-weight:800; padding:5px 10px; border-radius:4px; border:1px solid #ff4d4d; box-shadow:0 0 16px rgba(239,68,68,0.8); animation: pulse 0.6s infinite;">🎯 ATTACK SURGE</div>
|
| 403 |
+
</div>
|
| 404 |
+
|
| 405 |
+
<div class="game-hud">
|
| 406 |
+
<div class="hud-box">
|
| 407 |
+
<div class="hud-label">Health</div>
|
| 408 |
+
<div id="hudHealth" class="hud-val">100</div>
|
| 409 |
+
</div>
|
| 410 |
+
<div class="hud-box">
|
| 411 |
+
<div class="hud-label">Ammo</div>
|
| 412 |
+
<div id="hudAmmo" class="hud-val" style="color:var(--accent-cyan); font-size:1.35rem;">∞</div >
|
| 413 |
+
</div>
|
| 414 |
+
<div class="hud-box">
|
| 415 |
+
<div class="hud-label">Round Kills</div>
|
| 416 |
+
<div id="hudKills" class="hud-val" style="color:var(--accent-emerald);">0</div>
|
| 417 |
+
</div>
|
| 418 |
+
<div class="hud-box">
|
| 419 |
+
<div class="hud-label">Total Reward</div>
|
| 420 |
+
<div id="hudScore" class="hud-val">0.0</div>
|
| 421 |
+
</div>
|
| 422 |
+
</div>
|
| 423 |
+
|
| 424 |
+
<!-- Action Probabilities from Descending Motor Neurons -->
|
| 425 |
+
<div class="action-bars">
|
| 426 |
+
<div class="action-item">
|
| 427 |
+
<span>TURN LEFT</span>
|
| 428 |
+
<div class="progress-track"><div id="barLeft" class="progress-fill fill-left" style="width:0%;"></div></div>
|
| 429 |
+
<span id="pctLeft">0%</span>
|
| 430 |
+
</div>
|
| 431 |
+
<div class="action-item">
|
| 432 |
+
<span>TURN RIGHT</span>
|
| 433 |
+
<div class="progress-track"><div id="barRight" class="progress-fill fill-right" style="width:0%;"></div></div>
|
| 434 |
+
<span id="pctRight">0%</span>
|
| 435 |
+
</div>
|
| 436 |
+
<div class="action-item">
|
| 437 |
+
<span>ATTACK</span>
|
| 438 |
+
<div class="progress-track"><div id="barAttack" class="progress-fill fill-attack" style="width:0%;"></div></div>
|
| 439 |
+
<span id="pctAttack">0%</span>
|
| 440 |
+
</div>
|
| 441 |
+
</div>
|
| 442 |
+
|
| 443 |
+
<!-- Circuit Transmission Pipeline -->
|
| 444 |
+
<div class="circuit-pipeline">
|
| 445 |
+
<div id="pipeRetina" class="pipeline-node active">
|
| 446 |
+
<span>RETINA</span>
|
| 447 |
+
</div>
|
| 448 |
+
<span class="pipeline-arrow">➔</span>
|
| 449 |
+
<div id="pipeOptic" class="pipeline-node">
|
| 450 |
+
<span>OPTIC</span>
|
| 451 |
+
</div>
|
| 452 |
+
<span class="pipeline-arrow">➔</span>
|
| 453 |
+
<div id="pipeCompass" class="pipeline-node">
|
| 454 |
+
<span>COMPASS</span>
|
| 455 |
+
</div>
|
| 456 |
+
<span class="pipeline-arrow">➔</span>
|
| 457 |
+
<div id="pipeMotor" class="pipeline-node">
|
| 458 |
+
<span>MOTOR</span>
|
| 459 |
+
</div>
|
| 460 |
+
<span class="pipeline-arrow">➔</span>
|
| 461 |
+
<div id="pipeAction" class="pipeline-node">
|
| 462 |
+
<span>ACTION</span>
|
| 463 |
+
</div>
|
| 464 |
+
</div>
|
| 465 |
+
|
| 466 |
+
<!-- Controls & Simulation Speed -->
|
| 467 |
+
<div class="controls-bar">
|
| 468 |
+
<button id="btnPlayPause" class="btn-action">⏸️ PAUSE</button>
|
| 469 |
+
<div class="speed-btn-group">
|
| 470 |
+
<span style="font-size:0.75rem; color:var(--text-muted); font-family:'JetBrains Mono';">Speed:</span>
|
| 471 |
+
<button class="speed-btn btn-action" data-speed="0.5">0.5x</button>
|
| 472 |
+
<button class="speed-btn btn-action" data-speed="1.0" style="background:var(--accent-cyan); color:#000;">1.0x</button>
|
| 473 |
+
<button class="speed-btn btn-action" data-speed="1.5">1.5x</button>
|
| 474 |
+
<button class="speed-btn btn-action" data-speed="2.0">2.0x</button>
|
| 475 |
+
</div>
|
| 476 |
+
</div>
|
| 477 |
+
</div>
|
| 478 |
+
|
| 479 |
+
<!-- RIGHT: 3D FlyWire Connectome Neural Activity Visualizer -->
|
| 480 |
+
<div class="glass-card">
|
| 481 |
+
<div class="card-title">
|
| 482 |
+
<span>3D Connectome</span>
|
| 483 |
+
<span style="font-size:0.75rem; color:var(--accent-purple); font-family:'JetBrains Mono';">139,248 Neurons</span>
|
| 484 |
+
</div>
|
| 485 |
+
|
| 486 |
+
<!-- 3D WebGL Canvas Container -->
|
| 487 |
+
<div class="brain-canvas-container" id="brain3DContainer">
|
| 488 |
+
<div id="brain3DLoading" style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); color:var(--accent-cyan); font-family:'JetBrains Mono'; font-size:0.85rem; pointer-events:none; z-index:5;">
|
| 489 |
+
Loading 139,248 neurons...
|
| 490 |
+
</div>
|
| 491 |
+
<div style="position:absolute; bottom:12px; left:14px; right:14px; display:flex; justify-content:space-between; align-items:center; z-index:10; pointer-events:none;">
|
| 492 |
+
<span style="font-family:'JetBrains Mono'; font-size:0.68rem; color:var(--text-muted); background:rgba(0,0,0,0.6); padding:3px 8px; border-radius:4px;">
|
| 493 |
+
🖱️ Rotate • Scroll: Zoom • Right-Click: Pan
|
| 494 |
+
</span>
|
| 495 |
+
<div style="display:flex; gap:6px;">
|
| 496 |
+
|
| 497 |
+
<button id="btnResetView" style="pointer-events:auto; background:rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.15); color:#fff; font-family:'JetBrains Mono'; font-size:0.68rem; padding:4px 10px; border-radius:4px; cursor:pointer;">
|
| 498 |
+
🔄 Reset
|
| 499 |
+
</button>
|
| 500 |
+
</div>
|
| 501 |
+
</div>
|
| 502 |
+
</div>
|
| 503 |
+
|
| 504 |
+
<!-- Color-coded Anatomical Regions Chips -->
|
| 505 |
+
<div class="region-chips-grid">
|
| 506 |
+
<div class="region-chip" style="border-left: 3px solid var(--accent-blue);">
|
| 507 |
+
<div class="chip-header">
|
| 508 |
+
<div class="chip-title-wrap">
|
| 509 |
+
<div class="chip-dot" style="background:var(--accent-blue);"></div>
|
| 510 |
+
<span>Optic Left</span>
|
| 511 |
+
</div>
|
| 512 |
+
<span id="rateOpticL" class="chip-val">0.00</span>
|
| 513 |
+
</div>
|
| 514 |
+
<div class="progress-track" style="height:4px;"><div id="meterOpticL" class="progress-fill" style="width:0%; background:var(--accent-blue);"></div></div>
|
| 515 |
+
</div>
|
| 516 |
+
|
| 517 |
+
<div class="region-chip" style="border-left: 3px solid var(--accent-cyan);">
|
| 518 |
+
<div class="chip-header">
|
| 519 |
+
<div class="chip-title-wrap">
|
| 520 |
+
<div class="chip-dot" style="background:var(--accent-cyan);"></div>
|
| 521 |
+
<span>Optic Right</span>
|
| 522 |
+
</div>
|
| 523 |
+
<span id="rateOpticR" class="chip-val">0.00</span>
|
| 524 |
+
</div>
|
| 525 |
+
<div class="progress-track" style="height:4px;"><div id="meterOpticR" class="progress-fill" style="width:0%; background:var(--accent-cyan);"></div></div>
|
| 526 |
+
</div>
|
| 527 |
+
|
| 528 |
+
<div class="region-chip" style="border-left: 3px solid var(--accent-purple);">
|
| 529 |
+
<div class="chip-header">
|
| 530 |
+
<div class="chip-title-wrap">
|
| 531 |
+
<div class="chip-dot" style="background:var(--accent-purple);"></div>
|
| 532 |
+
<span>Central Complex</span>
|
| 533 |
+
</div>
|
| 534 |
+
<span id="rateCX" class="chip-val">0.00</span>
|
| 535 |
+
</div>
|
| 536 |
+
<div class="progress-track" style="height:4px;"><div id="meterCX" class="progress-fill" style="width:0%; background:var(--accent-purple);"></div></div>
|
| 537 |
+
</div>
|
| 538 |
+
|
| 539 |
+
<div class="region-chip" style="border-left: 3px solid var(--accent-pink);">
|
| 540 |
+
<div class="chip-header">
|
| 541 |
+
<div class="chip-title-wrap">
|
| 542 |
+
<div class="chip-dot" style="background:var(--accent-pink);"></div>
|
| 543 |
+
<span>Mushroom Body</span>
|
| 544 |
+
</div>
|
| 545 |
+
<span id="rateKC" class="chip-val">0.00</span>
|
| 546 |
+
</div>
|
| 547 |
+
<div class="progress-track" style="height:4px;"><div id="meterKC" class="progress-fill" style="width:0%; background:var(--accent-pink);"></div></div>
|
| 548 |
+
</div>
|
| 549 |
+
|
| 550 |
+
<div class="region-chip" style="border-left: 3px solid var(--accent-emerald);">
|
| 551 |
+
<div class="chip-header">
|
| 552 |
+
<div class="chip-title-wrap">
|
| 553 |
+
<div class="chip-dot" style="background:var(--accent-emerald);"></div>
|
| 554 |
+
<span>Descending Motor</span>
|
| 555 |
+
</div>
|
| 556 |
+
<span id="rateDN" class="chip-val">0.00</span>
|
| 557 |
+
</div>
|
| 558 |
+
<div class="progress-track" style="height:4px;"><div id="meterDN" class="progress-fill" style="width:0%; background:var(--accent-emerald);"></div></div>
|
| 559 |
+
</div>
|
| 560 |
+
|
| 561 |
+
<div class="region-chip" style="border-left: 3px solid var(--accent-amber);">
|
| 562 |
+
<div class="chip-header">
|
| 563 |
+
<div class="chip-title-wrap">
|
| 564 |
+
<div class="chip-dot" style="background:var(--accent-amber);"></div>
|
| 565 |
+
<span>Whole Brain</span>
|
| 566 |
+
</div>
|
| 567 |
+
<span id="rateWhole" class="chip-val">0.00</span>
|
| 568 |
+
</div>
|
| 569 |
+
<div class="progress-track" style="height:4px;"><div id="meterWhole" class="progress-fill" style="width:0%; background:var(--accent-amber);"></div></div>
|
| 570 |
+
</div>
|
| 571 |
+
</div>
|
| 572 |
+
</div>
|
| 573 |
+
</div>
|
| 574 |
+
|
| 575 |
+
<script>
|
| 576 |
+
let ws = null;
|
| 577 |
+
let isPaused = false;
|
| 578 |
+
const doomCanvas = document.getElementById("doomCanvas");
|
| 579 |
+
const container3D = document.getElementById("brain3DContainer");
|
| 580 |
+
|
| 581 |
+
// Real-time telemetry cache
|
| 582 |
+
let telemetry = {
|
| 583 |
+
optic_left: 0.25,
|
| 584 |
+
optic_right: 0.25,
|
| 585 |
+
central_complex: 0.20,
|
| 586 |
+
mushroom_body: 0.15,
|
| 587 |
+
descending_left: 0.22,
|
| 588 |
+
descending_right: 0.22,
|
| 589 |
+
whole_brain_firing: 0.23
|
| 590 |
+
};
|
| 591 |
+
|
| 592 |
+
// Regional Anatomy Colors (Hex & RGB)
|
| 593 |
+
const REGION_COLORS = {
|
| 594 |
+
optic_left: { hex: 0x0d61ff, str: "#0d61ff", label: "Optic Left" },
|
| 595 |
+
optic_right: { hex: 0x00faff, str: "#00faff", label: "Optic Right" },
|
| 596 |
+
central_complex: { hex: 0xb840ff, str: "#b840ff", label: "Central Complex" },
|
| 597 |
+
mushroom_body: { hex: 0xff2685, str: "#ff2685", label: "Mushroom Body" },
|
| 598 |
+
central_brain: { hex: 0xffbf1a, str: "#ffbf1a", label: "Central Brain" },
|
| 599 |
+
descending_left: { hex: 0x14ff33, str: "#14ff33", label: "Descending Left" },
|
| 600 |
+
descending_right: { hex: 0x14ff33, str: "#14ff33", label: "Descending Right" }
|
| 601 |
+
};
|
| 602 |
+
|
| 603 |
+
// Helper: Generate soft anti-aliased circular sprite for points (no harsh glare)
|
| 604 |
+
function createGlowSpriteTexture() {
|
| 605 |
+
const c = document.createElement("canvas");
|
| 606 |
+
c.width = 32;
|
| 607 |
+
c.height = 32;
|
| 608 |
+
const ctx = c.getContext("2d");
|
| 609 |
+
const grad = ctx.createRadialGradient(16, 16, 0, 16, 16, 16);
|
| 610 |
+
grad.addColorStop(0, "rgba(255, 255, 255, 0.9)");
|
| 611 |
+
grad.addColorStop(0.35, "rgba(255, 255, 255, 0.5)");
|
| 612 |
+
grad.addColorStop(1, "rgba(0, 0, 0, 0)");
|
| 613 |
+
ctx.fillStyle = grad;
|
| 614 |
+
ctx.fillRect(0, 0, 32, 32);
|
| 615 |
+
return new THREE.CanvasTexture(c);
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
// ==========================================
|
| 619 |
+
// Three.js 3D Interactive Scene Setup
|
| 620 |
+
// ==========================================
|
| 621 |
+
let scene, camera, renderer, controls;
|
| 622 |
+
let brainGroup = new THREE.Group();
|
| 623 |
+
let pointCloudObjects = {};
|
| 624 |
+
|
| 625 |
+
function init3DScene() {
|
| 626 |
+
const rect = container3D.getBoundingClientRect();
|
| 627 |
+
const w = container3D.clientWidth || 700;
|
| 628 |
+
const h = container3D.clientHeight || 525;
|
| 629 |
+
|
| 630 |
+
scene = new THREE.Scene();
|
| 631 |
+
scene.fog = new THREE.FogExp2(0x06080e, 0.003);
|
| 632 |
+
|
| 633 |
+
camera = new THREE.PerspectiveCamera(45, w / h, 1, 1000);
|
| 634 |
+
camera.position.set(0, 10, 135);
|
| 635 |
+
|
| 636 |
+
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
| 637 |
+
renderer.setSize(w, h);
|
| 638 |
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
| 639 |
+
container3D.appendChild(renderer.domElement);
|
| 640 |
+
|
| 641 |
+
// OrbitControls for 360-degree mouse interaction (with defensive check)
|
| 642 |
+
if (typeof THREE !== "undefined" && THREE.OrbitControls) {
|
| 643 |
+
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
| 644 |
+
controls.enableDamping = true;
|
| 645 |
+
controls.dampingFactor = 0.06;
|
| 646 |
+
controls.autoRotate = true;
|
| 647 |
+
controls.autoRotateSpeed = 0.6;
|
| 648 |
+
controls.maxDistance = 250;
|
| 649 |
+
controls.minDistance = 30;
|
| 650 |
+
} else {
|
| 651 |
+
console.warn("THREE.OrbitControls not found, using fallback controls");
|
| 652 |
+
controls = { update: () => {}, target: { set: () => {} }, autoRotate: false };
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
scene.add(brainGroup);
|
| 656 |
+
|
| 657 |
+
// Subtle lighting for crisp depth
|
| 658 |
+
const ambLight = new THREE.AmbientLight(0xffffff, 0.25);
|
| 659 |
+
scene.add(ambLight);
|
| 660 |
+
|
| 661 |
+
// Handle Resize
|
| 662 |
+
window.addEventListener("resize", onWindowResize);
|
| 663 |
+
|
| 664 |
+
// Reset View Button
|
| 665 |
+
const btnReset = document.getElementById("btnResetView");
|
| 666 |
+
if (btnReset) {
|
| 667 |
+
btnReset.onclick = () => {
|
| 668 |
+
camera.position.set(0, 10, 135);
|
| 669 |
+
if (controls && controls.target) controls.target.set(0, 0, 0);
|
| 670 |
+
if (controls && controls.update) controls.update();
|
| 671 |
+
};
|
| 672 |
+
}
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
function onWindowResize() {
|
| 676 |
+
if (!renderer || !camera || !container3D) return;
|
| 677 |
+
const w = container3D.clientWidth;
|
| 678 |
+
const h = container3D.clientHeight;
|
| 679 |
+
camera.aspect = w / h;
|
| 680 |
+
camera.updateProjectionMatrix();
|
| 681 |
+
renderer.setSize(w, h);
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
// Build 100% Complete Drosophila Connectome (139,248 Neurons) with Continuous Regional Shader
|
| 685 |
+
let wholeBrainPoints = null;
|
| 686 |
+
let synapticAttackFlash = 0.0;
|
| 687 |
+
|
| 688 |
+
// Live continuous target drives (smoothed exponentially in animate3D)
|
| 689 |
+
let targetDriveOptL = 0.0, liveOpticL = 0.0;
|
| 690 |
+
let targetDriveOptR = 0.0, liveOpticR = 0.0;
|
| 691 |
+
let targetDriveCX = 0.0, liveCX = 0.0;
|
| 692 |
+
let targetDriveKC = 0.0, liveKC = 0.0;
|
| 693 |
+
let targetDriveDN = 0.0, liveDN = 0.0;
|
| 694 |
+
|
| 695 |
+
// Mathematical biological excitation uniforms (Updated live from DOOM telemetry)
|
| 696 |
+
const brainUniforms = {
|
| 697 |
+
uOpticL: { value: 0.0 },
|
| 698 |
+
uOpticR: { value: 0.0 },
|
| 699 |
+
uCX: { value: 0.0 },
|
| 700 |
+
uKC: { value: 0.0 },
|
| 701 |
+
uDN: { value: 0.0 },
|
| 702 |
+
uWhole: { value: 0.23 },
|
| 703 |
+
uAttackFlash: { value: 0.0 }
|
| 704 |
+
};
|
| 705 |
+
|
| 706 |
+
const brainVertexShader = `
|
| 707 |
+
attribute vec3 color;
|
| 708 |
+
uniform float uOpticL;
|
| 709 |
+
uniform float uOpticR;
|
| 710 |
+
uniform float uCX;
|
| 711 |
+
uniform float uKC;
|
| 712 |
+
uniform float uDN;
|
| 713 |
+
uniform float uWhole;
|
| 714 |
+
uniform float uAttackFlash;
|
| 715 |
+
varying vec3 vColor;
|
| 716 |
+
varying float vAlpha;
|
| 717 |
+
|
| 718 |
+
void main() {
|
| 719 |
+
vec3 pos = position;
|
| 720 |
+
|
| 721 |
+
// Exact Continuous Anatomical Circuit Detectors (Zero If-Else!)
|
| 722 |
+
// 1. Optic Left (39,063 neurons)
|
| 723 |
+
float isOptL = clamp((color.g - 0.85) * 10.0, 0.0, 1.0) * clamp((0.15 - color.r) * 10.0, 0.0, 1.0);
|
| 724 |
+
|
| 725 |
+
// 2. Optic Right (38,478 neurons)
|
| 726 |
+
float isOptR = clamp((color.b - 0.90) * 15.0, 0.0, 1.0) * clamp((color.r - 0.15) * 10.0, 0.0, 1.0) * clamp((0.35 - color.r) * 10.0, 0.0, 1.0);
|
| 727 |
+
|
| 728 |
+
// 3. Central Complex Compass (2,875 neurons)
|
| 729 |
+
float isCX = clamp((color.b - 0.90) * 15.0, 0.0, 1.0) * clamp((color.r - 0.65) * 10.0, 0.0, 1.0) * clamp((0.65 - color.g) * 10.0, 0.0, 1.0);
|
| 730 |
+
|
| 731 |
+
// 4. Mushroom Body Memory (5,177 neurons)
|
| 732 |
+
float isKC = clamp((color.r - 0.88) * 10.0, 0.0, 1.0) * clamp((0.40 - color.g) * 10.0, 0.0, 1.0);
|
| 733 |
+
|
| 734 |
+
// 5. Descending Motor Pathway (1,303 neurons)
|
| 735 |
+
float isDN = clamp((color.g - 0.65) * 10.0, 0.0, 1.0) * clamp((0.15 - color.r) * 10.0, 0.0, 1.0) * clamp((0.60 - color.b) * 10.0, 0.0, 1.0);
|
| 736 |
+
|
| 737 |
+
// 6. Central Brain Interneurons (24,331 neurons)
|
| 738 |
+
float isCB = clamp((color.r - 0.90) * 10.0, 0.0, 1.0) * clamp((color.g - 0.65) * 10.0, 0.0, 1.0) * clamp((0.25 - color.b) * 10.0, 0.0, 1.0);
|
| 739 |
+
|
| 740 |
+
// High-contrast anatomical color tokens:
|
| 741 |
+
// Optic Left: Deep Royal Sapphire Blue (#0d61ff)
|
| 742 |
+
vec3 cOptL = vec3(0.05, 0.38, 1.00);
|
| 743 |
+
|
| 744 |
+
// Optic Right: Vivid Radiant Ice Cyan (#00faff)
|
| 745 |
+
vec3 cOptR = vec3(0.00, 1.00, 0.92);
|
| 746 |
+
|
| 747 |
+
// Central Complex Compass: Cosmic Violet Purple (#b840ff)
|
| 748 |
+
vec3 cCX = vec3(0.72, 0.25, 1.00);
|
| 749 |
+
|
| 750 |
+
// Mushroom Body Memory: Hot Rose Magenta Pink (#ff2685)
|
| 751 |
+
vec3 cKC = vec3(1.00, 0.15, 0.52);
|
| 752 |
+
|
| 753 |
+
// Descending Motor Pathway: Pure Laser Lime Green (#14ff33)
|
| 754 |
+
vec3 cDN = vec3(0.08, 1.00, 0.18);
|
| 755 |
+
|
| 756 |
+
// Central Brain Interneurons: Warm Golden Amber (#ffbf1a)
|
| 757 |
+
vec3 cCB = vec3(1.00, 0.75, 0.10);
|
| 758 |
+
|
| 759 |
+
// Sensory & Projection Interneurons: Soft Steel Slate (#8c99ae)
|
| 760 |
+
vec3 cSens = vec3(0.52, 0.58, 0.68);
|
| 761 |
+
|
| 762 |
+
// Continuous composite base color per neuron:
|
| 763 |
+
float isKnown = clamp(isOptL + isOptR + isCX + isKC + isDN + isCB, 0.0, 1.0);
|
| 764 |
+
vec3 baseColor = (isOptL * cOptL) +
|
| 765 |
+
(isOptR * cOptR) +
|
| 766 |
+
(isCX * cCX) +
|
| 767 |
+
(isKC * cKC) +
|
| 768 |
+
(isDN * cDN) +
|
| 769 |
+
(isCB * cCB) +
|
| 770 |
+
((1.0 - isKnown) * cSens);
|
| 771 |
+
|
| 772 |
+
// Dynamic regional excitation: Brightens and excites only the active region
|
| 773 |
+
float regBoost =
|
| 774 |
+
(isOptL * uOpticL * 1.6) +
|
| 775 |
+
(isOptR * uOpticR * 1.6) +
|
| 776 |
+
(isCX * uCX * 1.8) +
|
| 777 |
+
(isKC * uKC * 1.4) +
|
| 778 |
+
(isDN * uDN * 2.2);
|
| 779 |
+
|
| 780 |
+
// Gunshot Action Potential Burst: Cascades through Central Complex down to Descending Motor
|
| 781 |
+
float attackPulse = (isDN * 2.4 + isCX * 1.2) * uAttackFlash;
|
| 782 |
+
|
| 783 |
+
// Active neurons radiate their own vivid wavelength
|
| 784 |
+
vec3 glow = baseColor * (regBoost + attackPulse);
|
| 785 |
+
|
| 786 |
+
// White-hot electrical core glint ONLY at peak firing discharges
|
| 787 |
+
vec3 coreGlint = vec3(1.0, 0.98, 0.92) * clamp(regBoost * 0.25 + attackPulse * 0.7 - 0.35, 0.0, 0.75);
|
| 788 |
+
|
| 789 |
+
vColor = min(vec3(1.0), baseColor * 0.85 + glow + coreGlint);
|
| 790 |
+
|
| 791 |
+
float totalActivity = regBoost + attackPulse;
|
| 792 |
+
vAlpha = clamp(0.42 + totalActivity * 0.35, 0.35, 0.95);
|
| 793 |
+
|
| 794 |
+
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
|
| 795 |
+
gl_PointSize = (1.10 + clamp(totalActivity * 0.60, 0.0, 1.6)) * (140.0 / -mvPosition.z);
|
| 796 |
+
gl_Position = projectionMatrix * mvPosition;
|
| 797 |
+
}
|
| 798 |
+
`;
|
| 799 |
+
|
| 800 |
+
const brainFragmentShader = `
|
| 801 |
+
varying vec3 vColor;
|
| 802 |
+
varying float vAlpha;
|
| 803 |
+
|
| 804 |
+
void main() {
|
| 805 |
+
vec2 coord = gl_PointCoord - vec2(0.5);
|
| 806 |
+
float dist = length(coord);
|
| 807 |
+
if (dist > 0.5) discard;
|
| 808 |
+
float soft = smoothstep(0.5, 0.08, dist);
|
| 809 |
+
gl_FragColor = vec4(vColor, vAlpha * soft);
|
| 810 |
+
}
|
| 811 |
+
`;
|
| 812 |
+
|
| 813 |
+
function loadWholeConnectome139k() {
|
| 814 |
+
const ldr = document.getElementById("brain3DLoading");
|
| 815 |
+
Promise.all([
|
| 816 |
+
fetch("/brain_139k_pos.bin").then(r => {
|
| 817 |
+
if (!r.ok) throw new Error("Position binary fetch HTTP " + r.status);
|
| 818 |
+
return r.arrayBuffer();
|
| 819 |
+
}),
|
| 820 |
+
fetch("/brain_139k_col.bin").then(r => {
|
| 821 |
+
if (!r.ok) throw new Error("Color binary fetch HTTP " + r.status);
|
| 822 |
+
return r.arrayBuffer();
|
| 823 |
+
})
|
| 824 |
+
]).then(([posBuf, colBuf]) => {
|
| 825 |
+
const positions = new Float32Array(posBuf);
|
| 826 |
+
const rawColors = new Uint8Array(colBuf);
|
| 827 |
+
|
| 828 |
+
// Convert Uint8 colors to normalized Float32 [0.0, 1.0] for the WebGL shader
|
| 829 |
+
const colorsF32 = new Float32Array(rawColors.length);
|
| 830 |
+
for (let i = 0; i < rawColors.length; i++) {
|
| 831 |
+
colorsF32[i] = rawColors[i] / 255.0;
|
| 832 |
+
}
|
| 833 |
+
|
| 834 |
+
const geom = new THREE.BufferGeometry();
|
| 835 |
+
geom.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
| 836 |
+
geom.setAttribute("color", new THREE.BufferAttribute(colorsF32, 3));
|
| 837 |
+
|
| 838 |
+
const mat = new THREE.ShaderMaterial({
|
| 839 |
+
uniforms: brainUniforms,
|
| 840 |
+
vertexShader: brainVertexShader,
|
| 841 |
+
fragmentShader: brainFragmentShader,
|
| 842 |
+
transparent: true,
|
| 843 |
+
depthWrite: false,
|
| 844 |
+
blending: THREE.NormalBlending
|
| 845 |
+
});
|
| 846 |
+
|
| 847 |
+
wholeBrainPoints = new THREE.Points(geom, mat);
|
| 848 |
+
brainGroup.add(wholeBrainPoints);
|
| 849 |
+
console.log("Loaded all 139,248 FlyWire Drosophila neurons into 3D WebGL Shader!");
|
| 850 |
+
|
| 851 |
+
if (ldr) ldr.style.display = "none";
|
| 852 |
+
|
| 853 |
+
}).catch(err => {
|
| 854 |
+
console.error("Could not load 139k connectome binary", err);
|
| 855 |
+
if (ldr) {
|
| 856 |
+
ldr.innerText = "Connectome Load Failed: " + err.message;
|
| 857 |
+
ldr.style.color = "var(--accent-danger)";
|
| 858 |
+
}
|
| 859 |
+
});
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
// 3D Animation Render Loop (60 FPS WebGL)
|
| 863 |
+
function animate3D() {
|
| 864 |
+
requestAnimationFrame(animate3D);
|
| 865 |
+
|
| 866 |
+
if (controls && controls.update) {
|
| 867 |
+
if (controls.autoRotate !== undefined) {
|
| 868 |
+
controls.autoRotate = !isPaused;
|
| 869 |
+
}
|
| 870 |
+
controls.update();
|
| 871 |
+
}
|
| 872 |
+
|
| 873 |
+
// Smooth exponential biological interpolation towards dynamic drives
|
| 874 |
+
liveOpticL += (targetDriveOptL - liveOpticL) * 0.18;
|
| 875 |
+
liveOpticR += (targetDriveOptR - liveOpticR) * 0.18;
|
| 876 |
+
liveCX += (targetDriveCX - liveCX) * 0.18;
|
| 877 |
+
liveKC += (targetDriveKC - liveKC) * 0.18;
|
| 878 |
+
liveDN += (targetDriveDN - liveDN) * 0.18;
|
| 879 |
+
|
| 880 |
+
// Update shader uniforms live from smoothed telemetry
|
| 881 |
+
if (wholeBrainPoints && brainUniforms) {
|
| 882 |
+
brainUniforms.uOpticL.value = liveOpticL;
|
| 883 |
+
brainUniforms.uOpticR.value = liveOpticR;
|
| 884 |
+
brainUniforms.uCX.value = liveCX;
|
| 885 |
+
brainUniforms.uKC.value = liveKC;
|
| 886 |
+
brainUniforms.uDN.value = liveDN;
|
| 887 |
+
brainUniforms.uWhole.value = telemetry.whole_brain_firing || 0.23;
|
| 888 |
+
brainUniforms.uAttackFlash.value = synapticAttackFlash;
|
| 889 |
+
|
| 890 |
+
// Smooth decay for gunshot electrical flash
|
| 891 |
+
synapticAttackFlash *= 0.85;
|
| 892 |
+
}
|
| 893 |
+
|
| 894 |
+
if (renderer && scene && camera) {
|
| 895 |
+
renderer.render(scene, camera);
|
| 896 |
+
}
|
| 897 |
+
}
|
| 898 |
+
|
| 899 |
+
// Initialize 3D Engine & Load Connectome independently
|
| 900 |
+
try {
|
| 901 |
+
init3DScene();
|
| 902 |
+
} catch (err) {
|
| 903 |
+
console.error("init3DScene error:", err);
|
| 904 |
+
}
|
| 905 |
+
|
| 906 |
+
try {
|
| 907 |
+
loadWholeConnectome139k();
|
| 908 |
+
} catch (err) {
|
| 909 |
+
console.error("loadWholeConnectome139k error:", err);
|
| 910 |
+
}
|
| 911 |
+
|
| 912 |
+
try {
|
| 913 |
+
animate3D();
|
| 914 |
+
} catch (err) {
|
| 915 |
+
console.error("animate3D error:", err);
|
| 916 |
+
}
|
| 917 |
+
|
| 918 |
+
// ==========================================
|
| 919 |
+
// Live WebSocket Stream (ViZDoom + Telemetry)
|
| 920 |
+
// ==========================================
|
| 921 |
+
function connectWebSocket() {
|
| 922 |
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
| 923 |
+
const wsUrl = protocol + "//" + window.location.host + "/ws/game";
|
| 924 |
+
ws = new WebSocket(wsUrl);
|
| 925 |
+
|
| 926 |
+
ws.onopen = () => {
|
| 927 |
+
const cs = document.getElementById("connStatus");
|
| 928 |
+
if (cs) {
|
| 929 |
+
cs.innerHTML = '<div class="status-dot"></div><span>LIVE CONNECTOME</span>';
|
| 930 |
+
cs.style.color = 'var(--accent-emerald)';
|
| 931 |
+
}
|
| 932 |
+
};
|
| 933 |
+
|
| 934 |
+
ws.onclose = () => {
|
| 935 |
+
const cs = document.getElementById("connStatus");
|
| 936 |
+
if (cs) {
|
| 937 |
+
cs.innerHTML = '<div class="status-dot" style="background:var(--accent-danger); box-shadow:none;"></div><span>RECONNECTING...</span>';
|
| 938 |
+
cs.style.color = 'var(--accent-danger)';
|
| 939 |
+
}
|
| 940 |
+
setTimeout(connectWebSocket, 2000);
|
| 941 |
+
};
|
| 942 |
+
|
| 943 |
+
ws.onmessage = (event) => {
|
| 944 |
+
const data = JSON.parse(event.data);
|
| 945 |
+
|
| 946 |
+
// Update DOOM image
|
| 947 |
+
if (data.frame_b64) {
|
| 948 |
+
doomCanvas.src = "data:image/jpeg;base64," + data.frame_b64;
|
| 949 |
+
}
|
| 950 |
+
|
| 951 |
+
// Sync Running / Paused State with server
|
| 952 |
+
if (data.is_running !== undefined) {
|
| 953 |
+
isPaused = !data.is_running;
|
| 954 |
+
const ppBtn = document.getElementById("btnPlayPause");
|
| 955 |
+
if (ppBtn) {
|
| 956 |
+
ppBtn.innerText = isPaused ? "▶️ RESUME" : "⏸️ PAUSE";
|
| 957 |
+
ppBtn.style.background = isPaused ? "var(--accent-amber)" : "";
|
| 958 |
+
ppBtn.style.color = isPaused ? "#000" : "";
|
| 959 |
+
}
|
| 960 |
+
}
|
| 961 |
+
|
| 962 |
+
// Update HUD
|
| 963 |
+
document.getElementById("stepCounter").innerText = "Step: " + (data.step || 0);
|
| 964 |
+
document.getElementById("hudHealth").innerText = data.health || 0;
|
| 965 |
+
document.getElementById("hudAmmo").innerText = data.ammo || 0;
|
| 966 |
+
document.getElementById("hudKills").innerText = data.kills || 0;
|
| 967 |
+
document.getElementById("hudScore").innerText = (data.total_reward || 0).toFixed(1);
|
| 968 |
+
const fpsEl = document.getElementById("fpsVal");
|
| 969 |
+
if (fpsEl) fpsEl.innerText = data.instant_fps || "0.0";
|
| 970 |
+
|
| 971 |
+
// Update Action Probabilities
|
| 972 |
+
let pLeft = 0, pRight = 0, pAtk = 0;
|
| 973 |
+
if (data.action_probs) {
|
| 974 |
+
pLeft = Math.round(((data.action_probs.TURN_LEFT !== undefined ? data.action_probs.TURN_LEFT : data.action_probs.MOVE_LEFT) || 0) * 100);
|
| 975 |
+
pRight = Math.round(((data.action_probs.TURN_RIGHT !== undefined ? data.action_probs.TURN_RIGHT : data.action_probs.MOVE_RIGHT) || 0) * 100);
|
| 976 |
+
pAtk = Math.round((data.action_probs.ATTACK || 0) * 100);
|
| 977 |
+
|
| 978 |
+
document.getElementById("barLeft").style.width = pLeft + "%";
|
| 979 |
+
document.getElementById("pctLeft").innerText = pLeft + "%";
|
| 980 |
+
|
| 981 |
+
document.getElementById("barRight").style.width = pRight + "%";
|
| 982 |
+
document.getElementById("pctRight").innerText = pRight + "%";
|
| 983 |
+
|
| 984 |
+
document.getElementById("barAttack").style.width = pAtk + "%";
|
| 985 |
+
document.getElementById("pctAttack").innerText = pAtk + "%";
|
| 986 |
+
|
| 987 |
+
// Target lock badge
|
| 988 |
+
const lockBadge = document.getElementById("targetLockBadge");
|
| 989 |
+
if (pAtk > 40) {
|
| 990 |
+
lockBadge.style.display = "block";
|
| 991 |
+
} else {
|
| 992 |
+
lockBadge.style.display = "none";
|
| 993 |
+
}
|
| 994 |
+
}
|
| 995 |
+
|
| 996 |
+
// Update Telemetry & 3D WebGL Shader directly from the 139,248-neuron Connectome Engine
|
| 997 |
+
if (data.telemetry) {
|
| 998 |
+
telemetry = data.telemetry;
|
| 999 |
+
|
| 1000 |
+
const optL = telemetry.optic_left || 0;
|
| 1001 |
+
const optR = telemetry.optic_right || 0;
|
| 1002 |
+
const cx = telemetry.central_complex || 0;
|
| 1003 |
+
const kc = telemetry.mushroom_body || 0;
|
| 1004 |
+
const dnL = telemetry.descending_left || 0;
|
| 1005 |
+
const dnR = telemetry.descending_right || 0;
|
| 1006 |
+
const dnAvg = (dnL + dnR) / 2;
|
| 1007 |
+
const whole = telemetry.whole_brain_firing || 0;
|
| 1008 |
+
|
| 1009 |
+
// Pure Biological Firing Rate Drives (Directly mapped from Connectome Engine on GPU)
|
| 1010 |
+
// Baseline firing rate in Drosophila connectome is ~0.20; we map dynamic neural activity to visual excitation
|
| 1011 |
+
targetDriveOptL = Math.max(0.0, (optL - 0.20) * 4.0);
|
| 1012 |
+
targetDriveOptR = Math.max(0.0, (optR - 0.20) * 4.0);
|
| 1013 |
+
targetDriveCX = Math.max(0.0, (cx - 0.18) * 4.5);
|
| 1014 |
+
targetDriveKC = Math.max(0.0, (kc - 0.12) * 3.5);
|
| 1015 |
+
targetDriveDN = Math.max(0.0, (dnAvg - 0.20) * 4.0);
|
| 1016 |
+
|
| 1017 |
+
// Action Potential discharge on gunshot attack
|
| 1018 |
+
if (data.action_taken && data.action_taken.includes("ATTACK")) {
|
| 1019 |
+
synapticAttackFlash = 1.0;
|
| 1020 |
+
}
|
| 1021 |
+
|
| 1022 |
+
// Update Regional Numerical Gauges and Meters
|
| 1023 |
+
document.getElementById("rateOpticL").innerText = optL.toFixed(2);
|
| 1024 |
+
document.getElementById("meterOpticL").style.width = Math.min(100, optL * 100) + "%";
|
| 1025 |
+
|
| 1026 |
+
document.getElementById("rateOpticR").innerText = optR.toFixed(2);
|
| 1027 |
+
document.getElementById("meterOpticR").style.width = Math.min(100, optR * 100) + "%";
|
| 1028 |
+
|
| 1029 |
+
document.getElementById("rateCX").innerText = cx.toFixed(2);
|
| 1030 |
+
document.getElementById("meterCX").style.width = Math.min(100, cx * 100) + "%";
|
| 1031 |
+
|
| 1032 |
+
document.getElementById("rateKC").innerText = kc.toFixed(2);
|
| 1033 |
+
document.getElementById("meterKC").style.width = Math.min(100, kc * 100) + "%";
|
| 1034 |
+
|
| 1035 |
+
document.getElementById("rateDN").innerText = dnAvg.toFixed(2);
|
| 1036 |
+
document.getElementById("meterDN").style.width = Math.min(100, dnAvg * 100) + "%";
|
| 1037 |
+
|
| 1038 |
+
document.getElementById("rateWhole").innerText = whole.toFixed(2);
|
| 1039 |
+
document.getElementById("meterWhole").style.width = Math.min(100, whole * 100) + "%";
|
| 1040 |
+
|
| 1041 |
+
// Light up Circuit Pipeline nodes dynamically from genuine neural firing
|
| 1042 |
+
document.getElementById("pipeRetina").classList.toggle("active", true);
|
| 1043 |
+
document.getElementById("pipeOptic").classList.toggle("active", (optL > 0.25 || optR > 0.25));
|
| 1044 |
+
document.getElementById("pipeCompass").classList.toggle("active", (cx > 0.22));
|
| 1045 |
+
document.getElementById("pipeMotor").classList.toggle("active", (dnAvg > 0.22));
|
| 1046 |
+
document.getElementById("pipeAction").classList.toggle("active", (data.action_taken && data.action_taken.length > 0));
|
| 1047 |
+
}
|
| 1048 |
+
};
|
| 1049 |
+
}
|
| 1050 |
+
|
| 1051 |
+
// Play / Pause Toggle
|
| 1052 |
+
const playPauseBtn = document.getElementById("btnPlayPause");
|
| 1053 |
+
if (playPauseBtn) {
|
| 1054 |
+
playPauseBtn.onclick = () => {
|
| 1055 |
+
isPaused = !isPaused;
|
| 1056 |
+
playPauseBtn.innerText = isPaused ? "▶️ RESUME" : "⏸️ PAUSE";
|
| 1057 |
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
| 1058 |
+
ws.send(JSON.stringify({ type: "toggle_pause" }));
|
| 1059 |
+
}
|
| 1060 |
+
};
|
| 1061 |
+
}
|
| 1062 |
+
|
| 1063 |
+
// Speed Selector Buttons
|
| 1064 |
+
document.querySelectorAll(".speed-btn").forEach(btn => {
|
| 1065 |
+
btn.onclick = () => {
|
| 1066 |
+
document.querySelectorAll(".speed-btn").forEach(b => {
|
| 1067 |
+
b.style.background = "rgba(255, 255, 255, 0.06)";
|
| 1068 |
+
b.style.color = "var(--text-main)";
|
| 1069 |
+
});
|
| 1070 |
+
btn.style.background = "var(--accent-cyan)";
|
| 1071 |
+
btn.style.color = "#000";
|
| 1072 |
+
const spd = parseFloat(btn.dataset.speed);
|
| 1073 |
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
| 1074 |
+
ws.send(JSON.stringify({ type: "set_speed", speed: spd }));
|
| 1075 |
+
}
|
| 1076 |
+
};
|
| 1077 |
+
});
|
| 1078 |
+
|
| 1079 |
+
// Connect WebSocket stream immediately (zero delay, fully decoupled)
|
| 1080 |
+
connectWebSocket();
|
| 1081 |
+
</script>
|
| 1082 |
+
</body>
|
| 1083 |
+
</html>
|
web/js/OrbitControls.js
ADDED
|
@@ -0,0 +1,1045 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
( function () {
|
| 2 |
+
|
| 3 |
+
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
|
| 4 |
+
//
|
| 5 |
+
// Orbit - left mouse / touch: one-finger move
|
| 6 |
+
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
|
| 7 |
+
// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
|
| 8 |
+
|
| 9 |
+
const _changeEvent = {
|
| 10 |
+
type: 'change'
|
| 11 |
+
};
|
| 12 |
+
const _startEvent = {
|
| 13 |
+
type: 'start'
|
| 14 |
+
};
|
| 15 |
+
const _endEvent = {
|
| 16 |
+
type: 'end'
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
class OrbitControls extends THREE.EventDispatcher {
|
| 20 |
+
|
| 21 |
+
constructor( object, domElement ) {
|
| 22 |
+
|
| 23 |
+
super();
|
| 24 |
+
if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
|
| 25 |
+
if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
|
| 26 |
+
this.object = object;
|
| 27 |
+
this.domElement = domElement; // Set to false to disable this control
|
| 28 |
+
|
| 29 |
+
this.enabled = true; // "target" sets the location of focus, where the object orbits around
|
| 30 |
+
|
| 31 |
+
this.target = new THREE.Vector3(); // How far you can dolly in and out ( PerspectiveCamera only )
|
| 32 |
+
|
| 33 |
+
this.minDistance = 0;
|
| 34 |
+
this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only )
|
| 35 |
+
|
| 36 |
+
this.minZoom = 0;
|
| 37 |
+
this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits.
|
| 38 |
+
// Range is 0 to Math.PI radians.
|
| 39 |
+
|
| 40 |
+
this.minPolarAngle = 0; // radians
|
| 41 |
+
|
| 42 |
+
this.maxPolarAngle = Math.PI; // radians
|
| 43 |
+
// How far you can orbit horizontally, upper and lower limits.
|
| 44 |
+
// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
|
| 45 |
+
|
| 46 |
+
this.minAzimuthAngle = - Infinity; // radians
|
| 47 |
+
|
| 48 |
+
this.maxAzimuthAngle = Infinity; // radians
|
| 49 |
+
// Set to true to enable damping (inertia)
|
| 50 |
+
// If damping is enabled, you must call controls.update() in your animation loop
|
| 51 |
+
|
| 52 |
+
this.enableDamping = false;
|
| 53 |
+
this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
|
| 54 |
+
// Set to false to disable zooming
|
| 55 |
+
|
| 56 |
+
this.enableZoom = true;
|
| 57 |
+
this.zoomSpeed = 1.0; // Set to false to disable rotating
|
| 58 |
+
|
| 59 |
+
this.enableRotate = true;
|
| 60 |
+
this.rotateSpeed = 1.0; // Set to false to disable panning
|
| 61 |
+
|
| 62 |
+
this.enablePan = true;
|
| 63 |
+
this.panSpeed = 1.0;
|
| 64 |
+
this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
|
| 65 |
+
|
| 66 |
+
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
|
| 67 |
+
// Set to true to automatically rotate around the target
|
| 68 |
+
// If auto-rotate is enabled, you must call controls.update() in your animation loop
|
| 69 |
+
|
| 70 |
+
this.autoRotate = false;
|
| 71 |
+
this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
|
| 72 |
+
// The four arrow keys
|
| 73 |
+
|
| 74 |
+
this.keys = {
|
| 75 |
+
LEFT: 'ArrowLeft',
|
| 76 |
+
UP: 'ArrowUp',
|
| 77 |
+
RIGHT: 'ArrowRight',
|
| 78 |
+
BOTTOM: 'ArrowDown'
|
| 79 |
+
}; // Mouse buttons
|
| 80 |
+
|
| 81 |
+
this.mouseButtons = {
|
| 82 |
+
LEFT: THREE.MOUSE.ROTATE,
|
| 83 |
+
MIDDLE: THREE.MOUSE.DOLLY,
|
| 84 |
+
RIGHT: THREE.MOUSE.PAN
|
| 85 |
+
}; // Touch fingers
|
| 86 |
+
|
| 87 |
+
this.touches = {
|
| 88 |
+
ONE: THREE.TOUCH.ROTATE,
|
| 89 |
+
TWO: THREE.TOUCH.DOLLY_PAN
|
| 90 |
+
}; // for reset
|
| 91 |
+
|
| 92 |
+
this.target0 = this.target.clone();
|
| 93 |
+
this.position0 = this.object.position.clone();
|
| 94 |
+
this.zoom0 = this.object.zoom; // the target DOM element for key events
|
| 95 |
+
|
| 96 |
+
this._domElementKeyEvents = null; //
|
| 97 |
+
// public methods
|
| 98 |
+
//
|
| 99 |
+
|
| 100 |
+
this.getPolarAngle = function () {
|
| 101 |
+
|
| 102 |
+
return spherical.phi;
|
| 103 |
+
|
| 104 |
+
};
|
| 105 |
+
|
| 106 |
+
this.getAzimuthalAngle = function () {
|
| 107 |
+
|
| 108 |
+
return spherical.theta;
|
| 109 |
+
|
| 110 |
+
};
|
| 111 |
+
|
| 112 |
+
this.listenToKeyEvents = function ( domElement ) {
|
| 113 |
+
|
| 114 |
+
domElement.addEventListener( 'keydown', onKeyDown );
|
| 115 |
+
this._domElementKeyEvents = domElement;
|
| 116 |
+
|
| 117 |
+
};
|
| 118 |
+
|
| 119 |
+
this.saveState = function () {
|
| 120 |
+
|
| 121 |
+
scope.target0.copy( scope.target );
|
| 122 |
+
scope.position0.copy( scope.object.position );
|
| 123 |
+
scope.zoom0 = scope.object.zoom;
|
| 124 |
+
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
this.reset = function () {
|
| 128 |
+
|
| 129 |
+
scope.target.copy( scope.target0 );
|
| 130 |
+
scope.object.position.copy( scope.position0 );
|
| 131 |
+
scope.object.zoom = scope.zoom0;
|
| 132 |
+
scope.object.updateProjectionMatrix();
|
| 133 |
+
scope.dispatchEvent( _changeEvent );
|
| 134 |
+
scope.update();
|
| 135 |
+
state = STATE.NONE;
|
| 136 |
+
|
| 137 |
+
}; // this method is exposed, but perhaps it would be better if we can make it private...
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
this.update = function () {
|
| 141 |
+
|
| 142 |
+
const offset = new THREE.Vector3(); // so camera.up is the orbit axis
|
| 143 |
+
|
| 144 |
+
const quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
|
| 145 |
+
const quatInverse = quat.clone().invert();
|
| 146 |
+
const lastPosition = new THREE.Vector3();
|
| 147 |
+
const lastQuaternion = new THREE.Quaternion();
|
| 148 |
+
const twoPI = 2 * Math.PI;
|
| 149 |
+
return function update() {
|
| 150 |
+
|
| 151 |
+
const position = scope.object.position;
|
| 152 |
+
offset.copy( position ).sub( scope.target ); // rotate offset to "y-axis-is-up" space
|
| 153 |
+
|
| 154 |
+
offset.applyQuaternion( quat ); // angle from z-axis around y-axis
|
| 155 |
+
|
| 156 |
+
spherical.setFromVector3( offset );
|
| 157 |
+
|
| 158 |
+
if ( scope.autoRotate && state === STATE.NONE ) {
|
| 159 |
+
|
| 160 |
+
rotateLeft( getAutoRotationAngle() );
|
| 161 |
+
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
if ( scope.enableDamping ) {
|
| 165 |
+
|
| 166 |
+
spherical.theta += sphericalDelta.theta * scope.dampingFactor;
|
| 167 |
+
spherical.phi += sphericalDelta.phi * scope.dampingFactor;
|
| 168 |
+
|
| 169 |
+
} else {
|
| 170 |
+
|
| 171 |
+
spherical.theta += sphericalDelta.theta;
|
| 172 |
+
spherical.phi += sphericalDelta.phi;
|
| 173 |
+
|
| 174 |
+
} // restrict theta to be between desired limits
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
let min = scope.minAzimuthAngle;
|
| 178 |
+
let max = scope.maxAzimuthAngle;
|
| 179 |
+
|
| 180 |
+
if ( isFinite( min ) && isFinite( max ) ) {
|
| 181 |
+
|
| 182 |
+
if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
|
| 183 |
+
if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
|
| 184 |
+
|
| 185 |
+
if ( min <= max ) {
|
| 186 |
+
|
| 187 |
+
spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
|
| 188 |
+
|
| 189 |
+
} else {
|
| 190 |
+
|
| 191 |
+
spherical.theta = spherical.theta > ( min + max ) / 2 ? Math.max( min, spherical.theta ) : Math.min( max, spherical.theta );
|
| 192 |
+
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
} // restrict phi to be between desired limits
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
|
| 199 |
+
spherical.makeSafe();
|
| 200 |
+
spherical.radius *= scale; // restrict radius to be between desired limits
|
| 201 |
+
|
| 202 |
+
spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); // move target to panned location
|
| 203 |
+
|
| 204 |
+
if ( scope.enableDamping === true ) {
|
| 205 |
+
|
| 206 |
+
scope.target.addScaledVector( panOffset, scope.dampingFactor );
|
| 207 |
+
|
| 208 |
+
} else {
|
| 209 |
+
|
| 210 |
+
scope.target.add( panOffset );
|
| 211 |
+
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space
|
| 215 |
+
|
| 216 |
+
offset.applyQuaternion( quatInverse );
|
| 217 |
+
position.copy( scope.target ).add( offset );
|
| 218 |
+
scope.object.lookAt( scope.target );
|
| 219 |
+
|
| 220 |
+
if ( scope.enableDamping === true ) {
|
| 221 |
+
|
| 222 |
+
sphericalDelta.theta *= 1 - scope.dampingFactor;
|
| 223 |
+
sphericalDelta.phi *= 1 - scope.dampingFactor;
|
| 224 |
+
panOffset.multiplyScalar( 1 - scope.dampingFactor );
|
| 225 |
+
|
| 226 |
+
} else {
|
| 227 |
+
|
| 228 |
+
sphericalDelta.set( 0, 0, 0 );
|
| 229 |
+
panOffset.set( 0, 0, 0 );
|
| 230 |
+
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
scale = 1; // update condition is:
|
| 234 |
+
// min(camera displacement, camera rotation in radians)^2 > EPS
|
| 235 |
+
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
|
| 236 |
+
|
| 237 |
+
if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
|
| 238 |
+
|
| 239 |
+
scope.dispatchEvent( _changeEvent );
|
| 240 |
+
lastPosition.copy( scope.object.position );
|
| 241 |
+
lastQuaternion.copy( scope.object.quaternion );
|
| 242 |
+
zoomChanged = false;
|
| 243 |
+
return true;
|
| 244 |
+
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
return false;
|
| 248 |
+
|
| 249 |
+
};
|
| 250 |
+
|
| 251 |
+
}();
|
| 252 |
+
|
| 253 |
+
this.dispose = function () {
|
| 254 |
+
|
| 255 |
+
scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
|
| 256 |
+
scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
|
| 257 |
+
scope.domElement.removeEventListener( 'wheel', onMouseWheel );
|
| 258 |
+
scope.domElement.removeEventListener( 'touchstart', onTouchStart );
|
| 259 |
+
scope.domElement.removeEventListener( 'touchend', onTouchEnd );
|
| 260 |
+
scope.domElement.removeEventListener( 'touchmove', onTouchMove );
|
| 261 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
| 262 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
| 263 |
+
|
| 264 |
+
if ( scope._domElementKeyEvents !== null ) {
|
| 265 |
+
|
| 266 |
+
scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
|
| 267 |
+
|
| 268 |
+
} //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
|
| 269 |
+
|
| 270 |
+
}; //
|
| 271 |
+
// internals
|
| 272 |
+
//
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
const scope = this;
|
| 276 |
+
const STATE = {
|
| 277 |
+
NONE: - 1,
|
| 278 |
+
ROTATE: 0,
|
| 279 |
+
DOLLY: 1,
|
| 280 |
+
PAN: 2,
|
| 281 |
+
TOUCH_ROTATE: 3,
|
| 282 |
+
TOUCH_PAN: 4,
|
| 283 |
+
TOUCH_DOLLY_PAN: 5,
|
| 284 |
+
TOUCH_DOLLY_ROTATE: 6
|
| 285 |
+
};
|
| 286 |
+
let state = STATE.NONE;
|
| 287 |
+
const EPS = 0.000001; // current position in spherical coordinates
|
| 288 |
+
|
| 289 |
+
const spherical = new THREE.Spherical();
|
| 290 |
+
const sphericalDelta = new THREE.Spherical();
|
| 291 |
+
let scale = 1;
|
| 292 |
+
const panOffset = new THREE.Vector3();
|
| 293 |
+
let zoomChanged = false;
|
| 294 |
+
const rotateStart = new THREE.Vector2();
|
| 295 |
+
const rotateEnd = new THREE.Vector2();
|
| 296 |
+
const rotateDelta = new THREE.Vector2();
|
| 297 |
+
const panStart = new THREE.Vector2();
|
| 298 |
+
const panEnd = new THREE.Vector2();
|
| 299 |
+
const panDelta = new THREE.Vector2();
|
| 300 |
+
const dollyStart = new THREE.Vector2();
|
| 301 |
+
const dollyEnd = new THREE.Vector2();
|
| 302 |
+
const dollyDelta = new THREE.Vector2();
|
| 303 |
+
|
| 304 |
+
function getAutoRotationAngle() {
|
| 305 |
+
|
| 306 |
+
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
|
| 307 |
+
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
function getZoomScale() {
|
| 311 |
+
|
| 312 |
+
return Math.pow( 0.95, scope.zoomSpeed );
|
| 313 |
+
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
function rotateLeft( angle ) {
|
| 317 |
+
|
| 318 |
+
sphericalDelta.theta -= angle;
|
| 319 |
+
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
function rotateUp( angle ) {
|
| 323 |
+
|
| 324 |
+
sphericalDelta.phi -= angle;
|
| 325 |
+
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
const panLeft = function () {
|
| 329 |
+
|
| 330 |
+
const v = new THREE.Vector3();
|
| 331 |
+
return function panLeft( distance, objectMatrix ) {
|
| 332 |
+
|
| 333 |
+
v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
|
| 334 |
+
|
| 335 |
+
v.multiplyScalar( - distance );
|
| 336 |
+
panOffset.add( v );
|
| 337 |
+
|
| 338 |
+
};
|
| 339 |
+
|
| 340 |
+
}();
|
| 341 |
+
|
| 342 |
+
const panUp = function () {
|
| 343 |
+
|
| 344 |
+
const v = new THREE.Vector3();
|
| 345 |
+
return function panUp( distance, objectMatrix ) {
|
| 346 |
+
|
| 347 |
+
if ( scope.screenSpacePanning === true ) {
|
| 348 |
+
|
| 349 |
+
v.setFromMatrixColumn( objectMatrix, 1 );
|
| 350 |
+
|
| 351 |
+
} else {
|
| 352 |
+
|
| 353 |
+
v.setFromMatrixColumn( objectMatrix, 0 );
|
| 354 |
+
v.crossVectors( scope.object.up, v );
|
| 355 |
+
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
v.multiplyScalar( distance );
|
| 359 |
+
panOffset.add( v );
|
| 360 |
+
|
| 361 |
+
};
|
| 362 |
+
|
| 363 |
+
}(); // deltaX and deltaY are in pixels; right and down are positive
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
const pan = function () {
|
| 367 |
+
|
| 368 |
+
const offset = new THREE.Vector3();
|
| 369 |
+
return function pan( deltaX, deltaY ) {
|
| 370 |
+
|
| 371 |
+
const element = scope.domElement;
|
| 372 |
+
|
| 373 |
+
if ( scope.object.isPerspectiveCamera ) {
|
| 374 |
+
|
| 375 |
+
// perspective
|
| 376 |
+
const position = scope.object.position;
|
| 377 |
+
offset.copy( position ).sub( scope.target );
|
| 378 |
+
let targetDistance = offset.length(); // half of the fov is center to top of screen
|
| 379 |
+
|
| 380 |
+
targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed
|
| 381 |
+
|
| 382 |
+
panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
|
| 383 |
+
panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
|
| 384 |
+
|
| 385 |
+
} else if ( scope.object.isOrthographicCamera ) {
|
| 386 |
+
|
| 387 |
+
// orthographic
|
| 388 |
+
panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
|
| 389 |
+
panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
|
| 390 |
+
|
| 391 |
+
} else {
|
| 392 |
+
|
| 393 |
+
// camera neither orthographic nor perspective
|
| 394 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
|
| 395 |
+
scope.enablePan = false;
|
| 396 |
+
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
};
|
| 400 |
+
|
| 401 |
+
}();
|
| 402 |
+
|
| 403 |
+
function dollyOut( dollyScale ) {
|
| 404 |
+
|
| 405 |
+
if ( scope.object.isPerspectiveCamera ) {
|
| 406 |
+
|
| 407 |
+
scale /= dollyScale;
|
| 408 |
+
|
| 409 |
+
} else if ( scope.object.isOrthographicCamera ) {
|
| 410 |
+
|
| 411 |
+
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
|
| 412 |
+
scope.object.updateProjectionMatrix();
|
| 413 |
+
zoomChanged = true;
|
| 414 |
+
|
| 415 |
+
} else {
|
| 416 |
+
|
| 417 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
| 418 |
+
scope.enableZoom = false;
|
| 419 |
+
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
function dollyIn( dollyScale ) {
|
| 425 |
+
|
| 426 |
+
if ( scope.object.isPerspectiveCamera ) {
|
| 427 |
+
|
| 428 |
+
scale *= dollyScale;
|
| 429 |
+
|
| 430 |
+
} else if ( scope.object.isOrthographicCamera ) {
|
| 431 |
+
|
| 432 |
+
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
|
| 433 |
+
scope.object.updateProjectionMatrix();
|
| 434 |
+
zoomChanged = true;
|
| 435 |
+
|
| 436 |
+
} else {
|
| 437 |
+
|
| 438 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
| 439 |
+
scope.enableZoom = false;
|
| 440 |
+
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
} //
|
| 444 |
+
// event callbacks - update the object state
|
| 445 |
+
//
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
function handleMouseDownRotate( event ) {
|
| 449 |
+
|
| 450 |
+
rotateStart.set( event.clientX, event.clientY );
|
| 451 |
+
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
function handleMouseDownDolly( event ) {
|
| 455 |
+
|
| 456 |
+
dollyStart.set( event.clientX, event.clientY );
|
| 457 |
+
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
function handleMouseDownPan( event ) {
|
| 461 |
+
|
| 462 |
+
panStart.set( event.clientX, event.clientY );
|
| 463 |
+
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
function handleMouseMoveRotate( event ) {
|
| 467 |
+
|
| 468 |
+
rotateEnd.set( event.clientX, event.clientY );
|
| 469 |
+
rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
|
| 470 |
+
const element = scope.domElement;
|
| 471 |
+
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
|
| 472 |
+
|
| 473 |
+
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
|
| 474 |
+
rotateStart.copy( rotateEnd );
|
| 475 |
+
scope.update();
|
| 476 |
+
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
function handleMouseMoveDolly( event ) {
|
| 480 |
+
|
| 481 |
+
dollyEnd.set( event.clientX, event.clientY );
|
| 482 |
+
dollyDelta.subVectors( dollyEnd, dollyStart );
|
| 483 |
+
|
| 484 |
+
if ( dollyDelta.y > 0 ) {
|
| 485 |
+
|
| 486 |
+
dollyOut( getZoomScale() );
|
| 487 |
+
|
| 488 |
+
} else if ( dollyDelta.y < 0 ) {
|
| 489 |
+
|
| 490 |
+
dollyIn( getZoomScale() );
|
| 491 |
+
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
dollyStart.copy( dollyEnd );
|
| 495 |
+
scope.update();
|
| 496 |
+
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
function handleMouseMovePan( event ) {
|
| 500 |
+
|
| 501 |
+
panEnd.set( event.clientX, event.clientY );
|
| 502 |
+
panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
|
| 503 |
+
pan( panDelta.x, panDelta.y );
|
| 504 |
+
panStart.copy( panEnd );
|
| 505 |
+
scope.update();
|
| 506 |
+
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
function handleMouseUp( ) { // no-op
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
function handleMouseWheel( event ) {
|
| 513 |
+
|
| 514 |
+
if ( event.deltaY < 0 ) {
|
| 515 |
+
|
| 516 |
+
dollyIn( getZoomScale() );
|
| 517 |
+
|
| 518 |
+
} else if ( event.deltaY > 0 ) {
|
| 519 |
+
|
| 520 |
+
dollyOut( getZoomScale() );
|
| 521 |
+
|
| 522 |
+
}
|
| 523 |
+
|
| 524 |
+
scope.update();
|
| 525 |
+
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
function handleKeyDown( event ) {
|
| 529 |
+
|
| 530 |
+
let needsUpdate = false;
|
| 531 |
+
|
| 532 |
+
switch ( event.code ) {
|
| 533 |
+
|
| 534 |
+
case scope.keys.UP:
|
| 535 |
+
pan( 0, scope.keyPanSpeed );
|
| 536 |
+
needsUpdate = true;
|
| 537 |
+
break;
|
| 538 |
+
|
| 539 |
+
case scope.keys.BOTTOM:
|
| 540 |
+
pan( 0, - scope.keyPanSpeed );
|
| 541 |
+
needsUpdate = true;
|
| 542 |
+
break;
|
| 543 |
+
|
| 544 |
+
case scope.keys.LEFT:
|
| 545 |
+
pan( scope.keyPanSpeed, 0 );
|
| 546 |
+
needsUpdate = true;
|
| 547 |
+
break;
|
| 548 |
+
|
| 549 |
+
case scope.keys.RIGHT:
|
| 550 |
+
pan( - scope.keyPanSpeed, 0 );
|
| 551 |
+
needsUpdate = true;
|
| 552 |
+
break;
|
| 553 |
+
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
if ( needsUpdate ) {
|
| 557 |
+
|
| 558 |
+
// prevent the browser from scrolling on cursor keys
|
| 559 |
+
event.preventDefault();
|
| 560 |
+
scope.update();
|
| 561 |
+
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
function handleTouchStartRotate( event ) {
|
| 567 |
+
|
| 568 |
+
if ( event.touches.length == 1 ) {
|
| 569 |
+
|
| 570 |
+
rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
|
| 571 |
+
|
| 572 |
+
} else {
|
| 573 |
+
|
| 574 |
+
const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
|
| 575 |
+
const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
|
| 576 |
+
rotateStart.set( x, y );
|
| 577 |
+
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
function handleTouchStartPan( event ) {
|
| 583 |
+
|
| 584 |
+
if ( event.touches.length == 1 ) {
|
| 585 |
+
|
| 586 |
+
panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
|
| 587 |
+
|
| 588 |
+
} else {
|
| 589 |
+
|
| 590 |
+
const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
|
| 591 |
+
const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
|
| 592 |
+
panStart.set( x, y );
|
| 593 |
+
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
function handleTouchStartDolly( event ) {
|
| 599 |
+
|
| 600 |
+
const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
|
| 601 |
+
const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
|
| 602 |
+
const distance = Math.sqrt( dx * dx + dy * dy );
|
| 603 |
+
dollyStart.set( 0, distance );
|
| 604 |
+
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
function handleTouchStartDollyPan( event ) {
|
| 608 |
+
|
| 609 |
+
if ( scope.enableZoom ) handleTouchStartDolly( event );
|
| 610 |
+
if ( scope.enablePan ) handleTouchStartPan( event );
|
| 611 |
+
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
function handleTouchStartDollyRotate( event ) {
|
| 615 |
+
|
| 616 |
+
if ( scope.enableZoom ) handleTouchStartDolly( event );
|
| 617 |
+
if ( scope.enableRotate ) handleTouchStartRotate( event );
|
| 618 |
+
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
function handleTouchMoveRotate( event ) {
|
| 622 |
+
|
| 623 |
+
if ( event.touches.length == 1 ) {
|
| 624 |
+
|
| 625 |
+
rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
|
| 626 |
+
|
| 627 |
+
} else {
|
| 628 |
+
|
| 629 |
+
const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
|
| 630 |
+
const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
|
| 631 |
+
rotateEnd.set( x, y );
|
| 632 |
+
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
|
| 636 |
+
const element = scope.domElement;
|
| 637 |
+
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
|
| 638 |
+
|
| 639 |
+
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
|
| 640 |
+
rotateStart.copy( rotateEnd );
|
| 641 |
+
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
function handleTouchMovePan( event ) {
|
| 645 |
+
|
| 646 |
+
if ( event.touches.length == 1 ) {
|
| 647 |
+
|
| 648 |
+
panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
|
| 649 |
+
|
| 650 |
+
} else {
|
| 651 |
+
|
| 652 |
+
const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
|
| 653 |
+
const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
|
| 654 |
+
panEnd.set( x, y );
|
| 655 |
+
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
|
| 659 |
+
pan( panDelta.x, panDelta.y );
|
| 660 |
+
panStart.copy( panEnd );
|
| 661 |
+
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
function handleTouchMoveDolly( event ) {
|
| 665 |
+
|
| 666 |
+
const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
|
| 667 |
+
const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
|
| 668 |
+
const distance = Math.sqrt( dx * dx + dy * dy );
|
| 669 |
+
dollyEnd.set( 0, distance );
|
| 670 |
+
dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
|
| 671 |
+
dollyOut( dollyDelta.y );
|
| 672 |
+
dollyStart.copy( dollyEnd );
|
| 673 |
+
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
function handleTouchMoveDollyPan( event ) {
|
| 677 |
+
|
| 678 |
+
if ( scope.enableZoom ) handleTouchMoveDolly( event );
|
| 679 |
+
if ( scope.enablePan ) handleTouchMovePan( event );
|
| 680 |
+
|
| 681 |
+
}
|
| 682 |
+
|
| 683 |
+
function handleTouchMoveDollyRotate( event ) {
|
| 684 |
+
|
| 685 |
+
if ( scope.enableZoom ) handleTouchMoveDolly( event );
|
| 686 |
+
if ( scope.enableRotate ) handleTouchMoveRotate( event );
|
| 687 |
+
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
function handleTouchEnd( ) { // no-op
|
| 691 |
+
} //
|
| 692 |
+
// event handlers - FSM: listen for events and reset state
|
| 693 |
+
//
|
| 694 |
+
|
| 695 |
+
|
| 696 |
+
function onPointerDown( event ) {
|
| 697 |
+
|
| 698 |
+
if ( scope.enabled === false ) return;
|
| 699 |
+
|
| 700 |
+
switch ( event.pointerType ) {
|
| 701 |
+
|
| 702 |
+
case 'mouse':
|
| 703 |
+
case 'pen':
|
| 704 |
+
onMouseDown( event );
|
| 705 |
+
break;
|
| 706 |
+
// TODO touch
|
| 707 |
+
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
}
|
| 711 |
+
|
| 712 |
+
function onPointerMove( event ) {
|
| 713 |
+
|
| 714 |
+
if ( scope.enabled === false ) return;
|
| 715 |
+
|
| 716 |
+
switch ( event.pointerType ) {
|
| 717 |
+
|
| 718 |
+
case 'mouse':
|
| 719 |
+
case 'pen':
|
| 720 |
+
onMouseMove( event );
|
| 721 |
+
break;
|
| 722 |
+
// TODO touch
|
| 723 |
+
|
| 724 |
+
}
|
| 725 |
+
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
function onPointerUp( event ) {
|
| 729 |
+
|
| 730 |
+
switch ( event.pointerType ) {
|
| 731 |
+
|
| 732 |
+
case 'mouse':
|
| 733 |
+
case 'pen':
|
| 734 |
+
onMouseUp( event );
|
| 735 |
+
break;
|
| 736 |
+
// TODO touch
|
| 737 |
+
|
| 738 |
+
}
|
| 739 |
+
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
+
function onMouseDown( event ) {
|
| 743 |
+
|
| 744 |
+
// Prevent the browser from scrolling.
|
| 745 |
+
event.preventDefault(); // Manually set the focus since calling preventDefault above
|
| 746 |
+
// prevents the browser from setting it automatically.
|
| 747 |
+
|
| 748 |
+
scope.domElement.focus ? scope.domElement.focus() : window.focus();
|
| 749 |
+
let mouseAction;
|
| 750 |
+
|
| 751 |
+
switch ( event.button ) {
|
| 752 |
+
|
| 753 |
+
case 0:
|
| 754 |
+
mouseAction = scope.mouseButtons.LEFT;
|
| 755 |
+
break;
|
| 756 |
+
|
| 757 |
+
case 1:
|
| 758 |
+
mouseAction = scope.mouseButtons.MIDDLE;
|
| 759 |
+
break;
|
| 760 |
+
|
| 761 |
+
case 2:
|
| 762 |
+
mouseAction = scope.mouseButtons.RIGHT;
|
| 763 |
+
break;
|
| 764 |
+
|
| 765 |
+
default:
|
| 766 |
+
mouseAction = - 1;
|
| 767 |
+
|
| 768 |
+
}
|
| 769 |
+
|
| 770 |
+
switch ( mouseAction ) {
|
| 771 |
+
|
| 772 |
+
case THREE.MOUSE.DOLLY:
|
| 773 |
+
if ( scope.enableZoom === false ) return;
|
| 774 |
+
handleMouseDownDolly( event );
|
| 775 |
+
state = STATE.DOLLY;
|
| 776 |
+
break;
|
| 777 |
+
|
| 778 |
+
case THREE.MOUSE.ROTATE:
|
| 779 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
| 780 |
+
|
| 781 |
+
if ( scope.enablePan === false ) return;
|
| 782 |
+
handleMouseDownPan( event );
|
| 783 |
+
state = STATE.PAN;
|
| 784 |
+
|
| 785 |
+
} else {
|
| 786 |
+
|
| 787 |
+
if ( scope.enableRotate === false ) return;
|
| 788 |
+
handleMouseDownRotate( event );
|
| 789 |
+
state = STATE.ROTATE;
|
| 790 |
+
|
| 791 |
+
}
|
| 792 |
+
|
| 793 |
+
break;
|
| 794 |
+
|
| 795 |
+
case THREE.MOUSE.PAN:
|
| 796 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
| 797 |
+
|
| 798 |
+
if ( scope.enableRotate === false ) return;
|
| 799 |
+
handleMouseDownRotate( event );
|
| 800 |
+
state = STATE.ROTATE;
|
| 801 |
+
|
| 802 |
+
} else {
|
| 803 |
+
|
| 804 |
+
if ( scope.enablePan === false ) return;
|
| 805 |
+
handleMouseDownPan( event );
|
| 806 |
+
state = STATE.PAN;
|
| 807 |
+
|
| 808 |
+
}
|
| 809 |
+
|
| 810 |
+
break;
|
| 811 |
+
|
| 812 |
+
default:
|
| 813 |
+
state = STATE.NONE;
|
| 814 |
+
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
+
if ( state !== STATE.NONE ) {
|
| 818 |
+
|
| 819 |
+
scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
|
| 820 |
+
scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
|
| 821 |
+
scope.dispatchEvent( _startEvent );
|
| 822 |
+
|
| 823 |
+
}
|
| 824 |
+
|
| 825 |
+
}
|
| 826 |
+
|
| 827 |
+
function onMouseMove( event ) {
|
| 828 |
+
|
| 829 |
+
if ( scope.enabled === false ) return;
|
| 830 |
+
event.preventDefault();
|
| 831 |
+
|
| 832 |
+
switch ( state ) {
|
| 833 |
+
|
| 834 |
+
case STATE.ROTATE:
|
| 835 |
+
if ( scope.enableRotate === false ) return;
|
| 836 |
+
handleMouseMoveRotate( event );
|
| 837 |
+
break;
|
| 838 |
+
|
| 839 |
+
case STATE.DOLLY:
|
| 840 |
+
if ( scope.enableZoom === false ) return;
|
| 841 |
+
handleMouseMoveDolly( event );
|
| 842 |
+
break;
|
| 843 |
+
|
| 844 |
+
case STATE.PAN:
|
| 845 |
+
if ( scope.enablePan === false ) return;
|
| 846 |
+
handleMouseMovePan( event );
|
| 847 |
+
break;
|
| 848 |
+
|
| 849 |
+
}
|
| 850 |
+
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
function onMouseUp( event ) {
|
| 854 |
+
|
| 855 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
| 856 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
| 857 |
+
if ( scope.enabled === false ) return;
|
| 858 |
+
handleMouseUp( event );
|
| 859 |
+
scope.dispatchEvent( _endEvent );
|
| 860 |
+
state = STATE.NONE;
|
| 861 |
+
|
| 862 |
+
}
|
| 863 |
+
|
| 864 |
+
function onMouseWheel( event ) {
|
| 865 |
+
|
| 866 |
+
if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE && state !== STATE.ROTATE ) return;
|
| 867 |
+
event.preventDefault();
|
| 868 |
+
scope.dispatchEvent( _startEvent );
|
| 869 |
+
handleMouseWheel( event );
|
| 870 |
+
scope.dispatchEvent( _endEvent );
|
| 871 |
+
|
| 872 |
+
}
|
| 873 |
+
|
| 874 |
+
function onKeyDown( event ) {
|
| 875 |
+
|
| 876 |
+
if ( scope.enabled === false || scope.enablePan === false ) return;
|
| 877 |
+
handleKeyDown( event );
|
| 878 |
+
|
| 879 |
+
}
|
| 880 |
+
|
| 881 |
+
function onTouchStart( event ) {
|
| 882 |
+
|
| 883 |
+
if ( scope.enabled === false ) return;
|
| 884 |
+
event.preventDefault(); // prevent scrolling
|
| 885 |
+
|
| 886 |
+
switch ( event.touches.length ) {
|
| 887 |
+
|
| 888 |
+
case 1:
|
| 889 |
+
switch ( scope.touches.ONE ) {
|
| 890 |
+
|
| 891 |
+
case THREE.TOUCH.ROTATE:
|
| 892 |
+
if ( scope.enableRotate === false ) return;
|
| 893 |
+
handleTouchStartRotate( event );
|
| 894 |
+
state = STATE.TOUCH_ROTATE;
|
| 895 |
+
break;
|
| 896 |
+
|
| 897 |
+
case THREE.TOUCH.PAN:
|
| 898 |
+
if ( scope.enablePan === false ) return;
|
| 899 |
+
handleTouchStartPan( event );
|
| 900 |
+
state = STATE.TOUCH_PAN;
|
| 901 |
+
break;
|
| 902 |
+
|
| 903 |
+
default:
|
| 904 |
+
state = STATE.NONE;
|
| 905 |
+
|
| 906 |
+
}
|
| 907 |
+
|
| 908 |
+
break;
|
| 909 |
+
|
| 910 |
+
case 2:
|
| 911 |
+
switch ( scope.touches.TWO ) {
|
| 912 |
+
|
| 913 |
+
case THREE.TOUCH.DOLLY_PAN:
|
| 914 |
+
if ( scope.enableZoom === false && scope.enablePan === false ) return;
|
| 915 |
+
handleTouchStartDollyPan( event );
|
| 916 |
+
state = STATE.TOUCH_DOLLY_PAN;
|
| 917 |
+
break;
|
| 918 |
+
|
| 919 |
+
case THREE.TOUCH.DOLLY_ROTATE:
|
| 920 |
+
if ( scope.enableZoom === false && scope.enableRotate === false ) return;
|
| 921 |
+
handleTouchStartDollyRotate( event );
|
| 922 |
+
state = STATE.TOUCH_DOLLY_ROTATE;
|
| 923 |
+
break;
|
| 924 |
+
|
| 925 |
+
default:
|
| 926 |
+
state = STATE.NONE;
|
| 927 |
+
|
| 928 |
+
}
|
| 929 |
+
|
| 930 |
+
break;
|
| 931 |
+
|
| 932 |
+
default:
|
| 933 |
+
state = STATE.NONE;
|
| 934 |
+
|
| 935 |
+
}
|
| 936 |
+
|
| 937 |
+
if ( state !== STATE.NONE ) {
|
| 938 |
+
|
| 939 |
+
scope.dispatchEvent( _startEvent );
|
| 940 |
+
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
}
|
| 944 |
+
|
| 945 |
+
function onTouchMove( event ) {
|
| 946 |
+
|
| 947 |
+
if ( scope.enabled === false ) return;
|
| 948 |
+
event.preventDefault(); // prevent scrolling
|
| 949 |
+
|
| 950 |
+
switch ( state ) {
|
| 951 |
+
|
| 952 |
+
case STATE.TOUCH_ROTATE:
|
| 953 |
+
if ( scope.enableRotate === false ) return;
|
| 954 |
+
handleTouchMoveRotate( event );
|
| 955 |
+
scope.update();
|
| 956 |
+
break;
|
| 957 |
+
|
| 958 |
+
case STATE.TOUCH_PAN:
|
| 959 |
+
if ( scope.enablePan === false ) return;
|
| 960 |
+
handleTouchMovePan( event );
|
| 961 |
+
scope.update();
|
| 962 |
+
break;
|
| 963 |
+
|
| 964 |
+
case STATE.TOUCH_DOLLY_PAN:
|
| 965 |
+
if ( scope.enableZoom === false && scope.enablePan === false ) return;
|
| 966 |
+
handleTouchMoveDollyPan( event );
|
| 967 |
+
scope.update();
|
| 968 |
+
break;
|
| 969 |
+
|
| 970 |
+
case STATE.TOUCH_DOLLY_ROTATE:
|
| 971 |
+
if ( scope.enableZoom === false && scope.enableRotate === false ) return;
|
| 972 |
+
handleTouchMoveDollyRotate( event );
|
| 973 |
+
scope.update();
|
| 974 |
+
break;
|
| 975 |
+
|
| 976 |
+
default:
|
| 977 |
+
state = STATE.NONE;
|
| 978 |
+
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
}
|
| 982 |
+
|
| 983 |
+
function onTouchEnd( event ) {
|
| 984 |
+
|
| 985 |
+
if ( scope.enabled === false ) return;
|
| 986 |
+
handleTouchEnd( event );
|
| 987 |
+
scope.dispatchEvent( _endEvent );
|
| 988 |
+
state = STATE.NONE;
|
| 989 |
+
|
| 990 |
+
}
|
| 991 |
+
|
| 992 |
+
function onContextMenu( event ) {
|
| 993 |
+
|
| 994 |
+
if ( scope.enabled === false ) return;
|
| 995 |
+
event.preventDefault();
|
| 996 |
+
|
| 997 |
+
} //
|
| 998 |
+
|
| 999 |
+
|
| 1000 |
+
scope.domElement.addEventListener( 'contextmenu', onContextMenu );
|
| 1001 |
+
scope.domElement.addEventListener( 'pointerdown', onPointerDown );
|
| 1002 |
+
scope.domElement.addEventListener( 'wheel', onMouseWheel, {
|
| 1003 |
+
passive: false
|
| 1004 |
+
} );
|
| 1005 |
+
scope.domElement.addEventListener( 'touchstart', onTouchStart, {
|
| 1006 |
+
passive: false
|
| 1007 |
+
} );
|
| 1008 |
+
scope.domElement.addEventListener( 'touchend', onTouchEnd );
|
| 1009 |
+
scope.domElement.addEventListener( 'touchmove', onTouchMove, {
|
| 1010 |
+
passive: false
|
| 1011 |
+
} ); // force an update at start
|
| 1012 |
+
|
| 1013 |
+
this.update();
|
| 1014 |
+
|
| 1015 |
+
}
|
| 1016 |
+
|
| 1017 |
+
} // This set of controls performs orbiting, dollying (zooming), and panning.
|
| 1018 |
+
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
|
| 1019 |
+
// This is very similar to OrbitControls, another set of touch behavior
|
| 1020 |
+
//
|
| 1021 |
+
// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
|
| 1022 |
+
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
|
| 1023 |
+
// Pan - left mouse, or arrow keys / touch: one-finger move
|
| 1024 |
+
|
| 1025 |
+
|
| 1026 |
+
class MapControls extends OrbitControls {
|
| 1027 |
+
|
| 1028 |
+
constructor( object, domElement ) {
|
| 1029 |
+
|
| 1030 |
+
super( object, domElement );
|
| 1031 |
+
this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
|
| 1032 |
+
|
| 1033 |
+
this.mouseButtons.LEFT = THREE.MOUSE.PAN;
|
| 1034 |
+
this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
|
| 1035 |
+
this.touches.ONE = THREE.TOUCH.PAN;
|
| 1036 |
+
this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
|
| 1037 |
+
|
| 1038 |
+
}
|
| 1039 |
+
|
| 1040 |
+
}
|
| 1041 |
+
|
| 1042 |
+
THREE.MapControls = MapControls;
|
| 1043 |
+
THREE.OrbitControls = OrbitControls;
|
| 1044 |
+
|
| 1045 |
+
} )();
|
web/js/three.min.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
web/synapses_top5k.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:78dd4ffca1600f45e127a19978f83d043145fc8572bff7147a3b605eeaaaae68
|
| 3 |
+
size 120000
|