# 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 ``** (`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 `lookAt` writes** (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*