360 Dish Lab
The isolated prototype for a third game camera β π 3rd person and π 1st person meet a new πͺ¬ 360 POV rendered by dash-pannellum 0.3.0. The sphere is a live canvas the lab redraws from the specimen's position, so swimming moves the world; an auto-pilot directs the camera like a nature documentary (framing and following focus subjects, with camera emotes on impacts, deaths and respawns), and manual mode steers gaze-relative from a joystick.
---
.. toc::
A third point of view, isolated
The game already has two cameras: π 3rd person (the observer's dish, scroll/drag) and π 1st person (the specimen's eye, rendered by CV.renderArena with the eyespot tier deciding what you see). This lab prototypes the third β πͺ¬ 360: standing *inside* the dish at the specimen's eye height, looking around a full sphere. Like the [Tileset Dish Lab](/tileset-dish), it is deliberately isolated from /petri-dish-evolution so the POV can get polished before it earns a place in the game.
.. exec::docs.pano_dish.pano_dish
Movement for real: the dynamic canvas
[dash-pannellum](https://pypi.org/project/dash-pannellum/) 0.3.0 lets a tour scene bind to a live <canvas> (panoramaCanvasId + dynamicUpdate) β redraw the canvas and the sphere updates in place: *no rebuild, no flash, no camera reset*. The lab's renderer (assets/pano_dish.js, window.P360R) composes the dish interior from the specimen's position every other tick:
- the fluid sky comes from the ray-traced 4096Γ2048 panorama bake (position-
independent art: the condenser light, bokeh plankton, light shafts);
- the agar floor is re-projected per pixel from a top-down texture (the
arena360 ground-ray technique, at half resolution) β your membrane ring and shadow ride along at the nadir;
- the glass wall is drawn per column from its true distance along every bearing,
so the meniscus glow line rises as you approach the glass and recedes as you leave;
- the contacts are tinted sprites sized by distance (darkfield = glowing,
brightfield = classic silhouettes), with a pulse on threats and a focus ring on the camera's subject.
Swim and the whole world moves. Illumination (π/π‘) is now just a palette swap inside the renderer β the viewer is never rebuilt after boot, which is the three-tier design the package docs preach: *look* every frame (lookAt/drag), *move* at game cadence (redraw the canvas), *world* rarely (re-output tour).
The documentary director
The jitter fix is structural, not cosmetic β both the body and the camera move on slow signals:
- the auto-pilot steers through a turn-rate limit (β€2.4Β°/tick) toward *waypoints*,
*subjects* and *escape vectors* β never a per-tick random heading;
- the camera issues sparse
lookAtwrites (at most ~2/s, with 5Β° / 4-hfov
deadbands) whose duration grows with the angle β constant angular speed reads like a held camera, and Pannellum's own easing does the in-between;
- subject tracking: when the pilot commits to a specimen it becomes the *focus* β
the camera frames it (hfov eases 95 β 78), follows it as both drift, and the pilot circles rather than bowls it over. Click any contact in the sphere to make it the focus yourself; threats steal focus with a wider, warier shot.
Camera emotes (assets/camera_emotes.js β the dash-pannellum quick sheet of keyframed lookAt timelines) punctuate the loop: a *flinch* on wall bumps and threat contact, a decaying *shake* when the daphnia closes in, a *knockdown* (slam β stunned beat β slow rise toward the swim heading) on exhaustion, a waking *scan* on respawn, an hfov *lunge* punch when the pilot commits to a subject. The director yields while an emote plays (CAM_EMOTES.isBusy()), so reflexes never fight the framing.
Manual: the gaze is the steering frame
πΉ Manual arms the joystick (and WASD). In the top-down POVs input is world-absolute, but in the 360 view it is gaze-relative β stick up = swim where you're looking β which is the only mapping that feels right from inside a sphere. The pannellum zoom chrome is hidden (showZoomCtrl: false rides the tour config into the viewer; scroll / pinch / keyboard zoom all still work), and the compass docks under the joystick where a pilot's instruments belong.
Coordinates: one bearing contract
World x = east, y = south (SVG convention). A contact's yaw is atan2(dx, βdy), so yaw 0Β° = north = "up" on the dish map, clockwise β the placement.js contract the adaptation wedges use. Pitch comes from the floor geometry: a contact at distance *d* sits at βatan(eye/d) β exactly where the live floor puts it, so sprites stand on the agar. rc-joystick reports math-convention angles (0Β° = right, counter-clockwise); bearing = 90 β angle.
Tilesets: the multiRes dish
The second viewer serves the ray-traced dish as a Pannellum multires pyramid β six cube faces cut from an 8192Γ4096 master with the exact libpannellum face conventions, tiled into 126 tiles across 3 levels (cube 2048, tile 512, plus a 1024 fallback per face), streamed level-by-level as you zoom:
``python multiRes={ "basePath": "/assets/pano/multires/dish", "path": "/%l/%s%y_%x", "fallbackPath": "/fallback/%s", "extension": "jpg", "tileResolution": 512, "maxLevel": 3, "cubeResolution": 2048, } ``
Scorecard against the 0.1.0 wishlist
This lab's first build ran turn-based purely because 0.1.0 had no imperative API, and its wishlist drove the next two releases:
1. ~~Imperative hotspots~~ β shipped 0.2.0 (callbackHotspots diffs in place). The lab uses them as sparse click-zones; the *visual* contacts moved into the canvas texture, which suits per-tick motion even better. 2. ~~Camera writes~~ β shipped 0.2.0 (lookAt, plus hfov reporting). The director and the emote sheet are built on it. 3. ~~Canvas/data-URI panorama sources~~ β shipped 0.3.0 (panoramaCanvasId + dynamicUpdate). Position moves through the world now. 4. Hotspots + viewer config in multiRes mode β still open (the tiled dish above remains config-only). 5. Bundled (non-CDN) runtime β still open.
---
*Source: /360-dish*
Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs:
- /360-dish/llms.txt β LLM-friendly documentation
- /sitemap.xml
- /robots.txt