neuro-cube-brain
by smart-vick
README.md
# Neuro-Cube
**A 3D Rubik's Cube you solve with your attention.** Sustained mental focus —
measured live from EEG — rotates the selected face. Ships with a
hardware-agnostic **BrainFlow → MCP bridge** so any AI assistant can read your
real-time focus state.
Runs fully without an EEG headset (`--mock`), so you can try it right now.

---
## Why this exists
Consumer EEG splits into two camps: polished but closed focus-training apps
(Muse, FocusCalm), and open developer platforms with no real application on top
(OpenBCI, Neurosity). Neuro-Cube sits in the gap — **open, complete, and
readable**:
| | Neuro-Cube | Closed apps | Dev platforms |
|---|---|---|---|
| Works with any BrainFlow headset | ✅ | ❌ one vendor | partial |
| Complete interactive 3D app | ✅ | ❌ | ❌ |
| Readable, tested source | ✅ 136 checks | ❌ closed | varies |
| Runs with **no hardware** | ✅ `--mock` | ❌ | ❌ |
| Exposes brain state to AI (MCP) | ✅ any headset | ❌ | one vendor, $999 device |
---
## Quick start (no hardware needed)
```bash
git clone https://github.com/smart-vick/neuro-cube.git
cd neuro-cube
pip install -r requirements.txt
python main.py --mock
```
| Key | Action |
|-----|--------|
| `←` / `→` | Select which face to turn (highlighted on the cube) |
| `Space` | Simulate a focus event → turns the selected face |
| `u d l r f b` | Direct moves (`+Shift` = counter-clockwise) |
| `s` | Scramble, and reset the solve timer |
Scramble it, solve it, and the timer + move counter tell you how you did.
**With a headset** — no code changes, just drop the `--mock`:
```bash
python main.py # BrainFlow synthetic board (simulated signal)
```
Focus is computed as `beta / (beta + alpha + theta)` band power. Sustained focus
above threshold turns the selected face; hysteresis and a cooldown stop it
firing repeatedly.
---
## The MCP bridge
A standalone MCP server that exposes your live focus state to **any** MCP client
(Claude Desktop, etc.) — the same idea as Neurosity's built-in MCP support, but
for any BrainFlow-supported headset instead of one $999 device.
```bash
pip install -r requirements-mcp.txt
python -m mcp_bridge.server # synthetic board
python -m mcp_bridge.server --board-id 38 --serial-port COM3 # real headset
python -m mcp_bridge.demo_client # see it working
```
Register it with an MCP client:
```json
{ "mcpServers": { "neuro-cube-brain": {
"command": "python", "args": ["-m", "mcp_bridge.server"],
"cwd": "/absolute/path/to/neuro-cube" } } }
```
It exposes one tool, `get_brain_state`:
```json
{ "state": "focused", "focus": 0.7926, "focused": true,
"has_data": true, "age_seconds": 0.056, "board": "synthetic" }
```
`state` is `focused`, `unfocused`, or `no_signal`. Readings older than 2 s
degrade to `no_signal`, so a stalled poll loop or a dropped headset can never
report a stale "focused".
> **One headset, one process.** The bridge and the cube each open their own
> BrainFlow session, so pointing both at the *same physical device* will fail —
> the second `prepare_session()` is rejected, or one process takes the stream and
> the other goes quiet with no visible error. Run one at a time, or run the
> bridge on the synthetic board while the cube uses the headset.
---
## How it works
```
BrainFlowPoller (daemon thread) CubeState (lock-guarded) App (main thread)
poll EEG -> filter -> focus magiccube + threading.Lock Panda3D task, per frame
|- event_queue.put(event) --------> execute_move() validates ------> drain queues, animate
|- read_state() (snapshot) --> MCP bridge (separate process)
```
Four rules keep it race-free, and they are not negotiable:
1. **One-way queues.** The EEG thread never imports Panda3D, never touches cube
state, never calls the view. It only puts events on a queue.
2. **One source of truth.** Every mutation goes through `CubeState.execute_move()`
under a lock; consumers only ever see immutable snapshots.
3. **The render loop never blocks.** Every queue read is `get_nowait()`. Heavy
signal processing happens only on the daemon thread.
4. **Moves are serialized.** One face rotation at a time; bursts queue up rather
than tearing the scene graph.
The MCP bridge reads a separate lock-guarded `FocusState` snapshot rather than
the event queue, so it observes brain state without stealing events from the cube.
---
## Testing
```bash
pip install -r requirements-dev.txt
pytest tests/ # 9 suites, ~136 checks (~50s)
pytest tests/ -m "not slow" # skip board-driven suites (~15s)
python tests/test_directions.py # any suite standalone
```
Suites run in subprocesses because Panda3D's `ShowBase` is a process-level
singleton. Notable coverage:
- **`test_directions.py`** — all 18 moves verified against *independent* vector
algebra. An inverse-to-solved check can't catch a consistently mirrored
renderer; this can.
- **`test_focus_metric.py`** — signal processing driven by crafted sine waves:
20 Hz (beta) → focus 1.0, 10 Hz (alpha) → 0.0.
- **`test_focus_gate.py`** — hysteresis and cooldown with an injected clock,
including a flapping storm rate-limited from ~100 triggers down to ~10.
- **`render_snapshot.py`** — headless rendering, verified by screenshot.
The suite has caught four real bugs, including a flat/disconnected electrode
reading as focus ≈ 0.5 (dangerously near the trigger threshold) and BrainFlow's
logger corrupting the MCP server's stdio JSON-RPC channel.
---
## Hardware
Any [BrainFlow-supported board](https://brainflow.readthedocs.io/en/stable/SupportedBoards.html)
— OpenBCI, Muse, Neurosity, Ganglion, and others — via `--board-id`. Nothing in
the code is vendor-specific.
**Note:** the default thresholds (`FOCUS_ON_THRESHOLD = 0.65`,
`FOCUS_OFF_THRESHOLD = 0.45`) are placeholders. Real EEG varies per person and
per headset, so calibrate against your own baseline before expecting good
control.
## Status
Cube, renderer, retention hook, and MCP bridge are complete and tested.
Outstanding: real-hardware threshold calibration, and polish (camera orbit,
counter-clockwise moves via focus duration).
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues