Metadata-Version: 2.4
Name: tau-av
Version: 0.1.0
Summary: Distributed multi-projector rendering for the AlloSphere: state replication over UDP, OSC parameter sync, and per-projection warp/blend. For authoring multimedia work with no dependency on allolib.
Author-email: Ryan Millett <rmillett@ucsb.edu>
License: MIT
Project-URL: Homepage, https://github.com/kr4g/Tau
Project-URL: Repository, https://github.com/kr4g/Tau
Keywords: allosphere,audiovisual,distributed-rendering,immersive,opengl,osc,simulation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.26
Requires-Dist: numba>=0.59
Requires-Dist: moderngl>=5.10
Requires-Dist: glfw>=2.7
Requires-Dist: python-osc>=1.8
Requires-Dist: imgui-bundle>=1.5
Requires-Dist: PyOpenGL>=3.1
Requires-Dist: tomli>=2.0; python_version < "3.11"
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Provides-Extra: publish
Requires-Dist: build>=1.0; extra == "publish"
Requires-Dist: twine>=5.0; extra == "publish"
Dynamic: license-file

# Tau

Distributed multi-projector rendering for the AlloSphere: state replication
over UDP, OSC parameter sync, and per-projection warp/blend. For authoring
multimedia work with no dependency on allolib.

In quantum physics, τ (tau) denotes the *tangle*, a measure of entanglement.
For three or more parts it is what is left once every pairwise correlation
has been accounted for: a property of the whole system, belonging to no pair
within it.

The import package is `tau`; it is published to PyPI as `tau-av`.

## Install

Tau is used from a clone — the examples, the tests, and the deploy scripts
live in the repository tree.

```bash
git clone https://github.com/kr4g/Tau.git
cd Tau
python3 -m venv .venv
.venv/bin/python -m pip install -e .
```

It is also installable as a dependency in a tree of your own:

```bash
pip install tau-av
```

## Machine self-check

```bash
.venv/bin/python -m tau.preflight
```

Verifies Python version, GL context, shader compilation, numba JIT,
calibration presence, port availability, and UDP send/receive — run it after
installing, and on any machine before it joins a cluster.

## Run

The launcher lists the apps under `examples/` and `apps/`, switches between the
two, and runs the selection as a subprocess; picking another app stops the
first:

```bash
.venv/bin/python -m tau.launcher        # window; --tui for a terminal menu
```

Further directories are listed with `--apps-dir`. When node agents are running
(see "Running in the AlloSphere"), a launch or stop also fans out to them, so
the selection changes what the whole cluster shows.

A single app is targeted directly; every app has the same entry point:

```bash
# perspective window (default view)
.venv/bin/python -m examples.<name>.app

# start in a different view: pov, cross (cubemap box), or equirect panorama
.venv/bin/python -m examples.<name>.app --view equirect
```

Each app adds its own flags on top of the shared renderer flags below; run it
with `--help`, or read the module docstring in its `app.py`.

Controls: arrow keys look around (yaw/pitch), WASD moves in the view plane,
`E`/`C` move up and down, `` ` `` (backtick) stops all motion and returns the
camera to its home pose, `v` cycles the view (pov → cross → equirect), `Esc`
quits. The view is local to each machine and can be changed while the app
runs. A second instance started while the first is open auto-elects as a
replica and syncs over the network.

The camera home pose is the origin with identity orientation; an app may
capture a different one with `self.nav().set_home()`.

Every start prints a role banner — host, role, primary/replica, broadcast
target, renderer, calibration — so a process that resolved to the wrong role
is visible at startup.

### Renderer flags

| Flag | Purpose |
|---|---|
| `--view {pov,cross,equirect}` | starting desktop view (`v` cycles at runtime) |
| `--omni` | alias for `--view cross` |
| `--sim` | elect this machine as the simulator (any host) |
| `--broadcast ADDR` | state broadcast address override |
| `--windowed` | force a windowed AlloSphere renderer (disable auto-fullscreen) |
| `--fullscreen` | force a spanned undecorated window |
| `--capture-res N` | per-projector capture resolution |
| `--calibration-dir DIR` | calibration directory (`<host>.txt` + `projN.bin`) |
| `--no-gui` | suppress the control panel |

### Environment

| Variable | Effect |
|---|---|
| `TAU_PRIMARY_HOST` | hostname that elects as primary (default `ar01`) |
| `TAU_NO_GUI=1` | never open the control panel |
| `TAU_NO_NUMBA=1` | force the numpy path even where Numba is installed |
| `TAU_PARALLEL_MIN` | population below which the parallel neighbor kernel runs serially (default `1500`); thread spin-up costs more than it saves for small counts |
| `TAU_CACHE_DIR` | where precomputed data is cached; point it at a shared path so every node loads one copy (default: XDG user cache) |

## Writing an app

Apps you write go in `apps/`. An app is a Python module that subclasses
`DistributedApp` and overrides a few methods: `on_init`, `on_create`,
`on_animate(dt)`, `on_draw(g)`, and optionally `on_gui(panel)` and
`on_keys(keys)`. The same file runs as a window on one machine and across the
cluster in the AlloSphere, where the runtime elects a primary, replicates state
to the renderers, and warps and blends each projector from its calibration.

The apps under `examples/` all follow one layout: a model module holding the
algorithm as vectorized numpy, a `state.py` declaring the replicated dtype, and
an `app.py` with a `main()` that calls `tau.run`.

`on_animate(dt)` runs once per frame; `on_draw(g)` runs once per projector per
eye. Build and upload geometry in `on_animate`, and issue only draw calls in
`on_draw`. An app that uploads inside `on_draw` renders correctly in a window
and differently on each projector.

### Where your code goes

`tau/` and `examples/` change upstream. Two directories are yours:

| Directory | Contents |
|---|---|
| `apps/` | apps you write; the launcher switches between these and the examples |
| `ext/` | code your apps import: helpers, kernels, shaders |

Both ship empty and their contents are ignored by git, so a pull does not
affect them. An app in `apps/<name>/` runs as `python -m apps.<name>.app`, the
same shape as an example; anything under `ext/` is imported directly, as
`from ext.<module> import ...`. Nothing under `ext/` is a launch target — the
node agent runs modules from `apps.`, `examples.`, and `tau.` only.

### Working with a coding agent

[AGENTS.md](AGENTS.md) carries the conventions and the reasons for them, in a
form meant to be read by an agent as much as by a person; agents working in a
clone pick it up from the repository root without being asked. The apps under
`examples/` are the patterns to work from.

The checks below cover what can be checked automatically. Whether an app reads
from inside the AlloSphere is not one of those things — it can pass everything
and still be composed for a rectangle. Look at it in `--view equirect` and
`--view pov`.

## Control panel

A second window with a view selector, a slider or field for every registered
parameter, and the cluster roster. Apps add custom UI by overriding
`on_gui(panel)`.

It opens only on the simulator/primary; render nodes never open one. Disable it
with `--no-gui` or `TAU_NO_GUI=1`.

## Shaders

The renderer supplies `tau_ModelViewMatrix`, `tau_ProjectionMatrix`, and
`tau_ViewMatrix`. Declare whichever the shader uses:

```glsl
uniform mat4 tau_ModelViewMatrix;
uniform mat4 tau_ProjectionMatrix;
```

Shaders loaded through `ShaderManager` reload on file change while the app runs.

## Checking an app

The suite is layered so that a pass on one machine predicts behavior on the
cluster. Every app is drawn twice for the same frame through the
multi-projector path and must produce identical output. Every shader must
compile at `#version 410`, the strictest version the renderers accept. The
warp composite is checked pixel-for-pixel against an independent
reimplementation, and a two-process rehearsal runs election and transport over
loopback.

```bash
.venv/bin/python -m pytest
```

`bash scripts/test-py310.sh` runs the same suite on the interpreter the
renderers use. Broadcast delivery on a subnet, driver differences, the spanned
X screen, and quad-buffer stereo are checked on site.

For cost rather than correctness, `python -m tests.bench_sphere` reports
simulation and render timings; `--save before.json` and `--compare
before.json` bracket a change. Set `TAU_CALIBRATION_DIR` to profile against a
real calibration set.

## Running in the AlloSphere

Stage the app on the shared `/alloshare` mount and build the venv there once,
from a renderer (Linux/x86_64): `bash deploy/build_venv.sh`. Then either start
the same app on every machine by hand:

```bash
# identical on the simulator and on every renderer
# (e.g. broadcast to all panes in an iTerm2 multishell)
.venv/bin/python -m examples.<name>.app
```

or start the node agents once and switch content from the simulator alone:

```bash
bash deploy/launch_sphere.sh start      # an agent on every renderer
.venv/bin/python -m tau.launcher        # pick an app; the cluster follows
```

Role and renderer are auto-resolved by hostname and calibration, so no flags
are needed. The rank-0 host is the primary and sends state; the renderers are
replicas that receive it. A renderer with a
`/home/sphere/calibration-current/<host>.txt` manifest applies warp and blend
and runs fullscreen automatically. To drive the cluster from a machine whose
hostname the cluster does not know, add `--sim`.

State ships as full snapshots fragmented over UDP, latest-wins, with 16 MB
socket buffers on both ends.

### Cluster status

Every node broadcasts a heartbeat once a second. The primary's control panel
shows the roster (host, role, app, frame rate), and

```bash
.venv/bin/python -m tau.heartbeat
```

is the same table in a terminal. Two simultaneous primaries — for example
`--sim` on a laptop while the resident simulator is up — are reported on both.

### Projector identification

Setting the `/tau/pattern` parameter (a slider in every app's control panel;
number keys `0`–`5` in `examples.calibration`) switches all renderers into a
diagnostic pattern without stopping content: per-projector labels, solid hash
colors, viewport outlines, checkerboards, or an orientation glyph that
distinguishes a physically rotated projector from bad warp data. `0` returns
to normal rendering. Degraded states — no calibration, state not arriving —
are drawn on the output itself.
