diff --git a/data/battle_arenas.lua b/data/battle_arenas.lua index 680cece..33f7164 100644 --- a/data/battle_arenas.lua +++ b/data/battle_arenas.lua @@ -63,14 +63,14 @@ return { -- height along the sightline and a hedge in the apron row is not terrain. -- So the choice is where in the clearing, and this is its west end: tree -- line square behind the pair, nothing crossing either of them. - ["ROUTE_1"] = { x = 4, y = 14, shape = "narrow" }, + ["ROUTE_1"] = { x = 10, y = 16, shape = "narrow", turn = 90 }, ["ROUTE_2"] = { x = 1, y = 49, shape = "wide" }, - ["ROUTE_3"] = { x = 57, y = 1, shape = "wide" }, - ["ROUTE_4"] = { x = 46, y = 7, shape = "wide" }, - ["ROUTE_5"] = { x = 13, y = 24, shape = "wide" }, - ["ROUTE_6"] = { x = 5, y = 17, shape = "narrow" }, - ["ROUTE_7"] = { x = 8, y = 8, shape = "narrow" }, - ["ROUTE_8"] = { x = 25, y = 7, shape = "wide" }, + ["ROUTE_3"] = { x = 52, y = 10, shape = "wide", turn = 90 }, + ["ROUTE_4"] = { x = 46, y = 4, shape = "wide" }, + ["ROUTE_5"] = { x = 7, y = 0, shape = "wide", turn = 270 }, + ["ROUTE_6"] = { x = 4, y = 15, shape = "narrow" }, + ["ROUTE_7"] = { x = 4, y = 3, shape = "narrow", cam = "wide" }, + ["ROUTE_8"] = { x = 24, y = 5, shape = "wide", turn = 90 }, -- the whole route admits six bare wide arenas, all in the west cliff -- corridor; this is the best of them. A flower cluster crosses the far -- mon's hind legs, which the brief allows -- every alternative put a @@ -78,13 +78,13 @@ return { ["ROUTE_9"] = { x = 1, y = 11, shape = "wide", cam = "wide" }, ["ROUTE_10"] = { x = 7, y = 40, shape = "wide" }, ["ROUTE_11"] = { x = 9, y = 6, shape = "wide" }, - ["ROUTE_12"] = { x = 0, y = 73, shape = "wide" }, - ["ROUTE_13"] = { x = 50, y = 8, shape = "narrow" }, - ["ROUTE_14"] = { x = 11, y = 25, shape = "wide" }, - ["ROUTE_15"] = { x = 9, y = 10, shape = "wide" }, - ["ROUTE_16"] = { x = 6, y = 10, shape = "wide" }, + ["ROUTE_12"] = { x = 10, y = 71, shape = "wide", turn = 90 }, + ["ROUTE_13"] = { x = 51, y = 5, shape = "narrow" }, + ["ROUTE_14"] = { x = 11, y = 24, shape = "wide" }, + ["ROUTE_15"] = { x = 30, y = 10, shape = "wide", turn = 90 }, + ["ROUTE_16"] = { x = 8, y = 10, shape = "wide", turn = 90 }, ["ROUTE_17"] = { x = 14, y = 70, shape = "wide" }, - ["ROUTE_18"] = { x = 11, y = 4, shape = "wide" }, + ["ROUTE_18"] = { x = 11, y = 3, shape = "wide" }, -- ------- buildings and caves -- @@ -92,10 +92,10 @@ return { -- or gravestones in it rarely holds a 3x6 clearing whose whole width is -- also SEEN, and giving up the apron is usually the difference between a -- fight in the open and one behind a console. - ["POKEMON_MANSION_1F"] = { x = 4, y = 12, shape = "wide" }, - ["POKEMON_MANSION_2F"] = { x = 15, y = 17, shape = "wide" }, - ["POKEMON_MANSION_3F"] = { x = 23, y = 2, shape = "narrow", cam = "wide" }, - ["POKEMON_MANSION_B1F"] = { x = 19, y = 10, shape = "wide" }, + ["POKEMON_MANSION_1F"] = { x = 4, y = 12, shape = "wide", cam = "wide" }, + ["POKEMON_MANSION_2F"] = { x = 10, y = 1, shape = "wide", cam = "wide" }, + ["POKEMON_MANSION_3F"] = { x = 24, y = 3, shape = "narrow", cam = "wide" }, + ["POKEMON_MANSION_B1F"] = { x = 6, y = 19, shape = "wide", turn = 90 }, ["POKEMON_TOWER_2F"] = { x = 4, y = 7, shape = "narrow" }, ["POKEMON_TOWER_3F"] = { x = 4, y = 6, shape = "wide" }, -- every one of 4F's thirty candidate spots puts a gravestone through a @@ -103,73 +103,79 @@ return { -- is shot there ["POKEMON_TOWER_4F"] = { map = "POKEMON_TOWER_3F", x = 4, y = 6, shape = "wide" }, - ["POKEMON_TOWER_5F"] = { x = 10, y = 1, shape = "narrow" }, - ["POKEMON_TOWER_6F"] = { x = 14, y = 6, shape = "narrow" }, + ["POKEMON_TOWER_5F"] = { x = 8, y = 9, shape = "narrow", cam = "wide" }, + ["POKEMON_TOWER_6F"] = { x = 14, y = 6, shape = "narrow", cam = "wide" }, ["POKEMON_TOWER_7F"] = { x = 9, y = 5, shape = "wide" }, ["POWER_PLANT"] = { x = 18, y = 5, shape = "narrow" }, ["ROCK_TUNNEL_1F"] = { x = 14, y = 15, shape = "wide" }, ["ROCK_TUNNEL_B1F"] = { x = 20, y = 17, shape = "wide" }, - ["ROCKET_HIDEOUT_B1F"] = { x = 11, y = 6, shape = "narrow" }, - ["ROCKET_HIDEOUT_B2F"] = { x = 19, y = 7, shape = "narrow" }, - ["ROCKET_HIDEOUT_B3F"] = { x = 22, y = 11, shape = "narrow" }, - ["ROCKET_HIDEOUT_B4F"] = { x = 17, y = 3, shape = "narrow" }, + ["ROCKET_HIDEOUT_B1F"] = { x = 20, y = 18, shape = "narrow" }, + ["ROCKET_HIDEOUT_B2F"] = { x = 20, y = 10, shape = "narrow", cam = "wide" }, + ["ROCKET_HIDEOUT_B3F"] = { x = 24, y = 15, + shape = "narrow", turn = 90, cam = "wide" }, + ["ROCKET_HIDEOUT_B4F"] = { x = 19, y = 17, shape = "wide", cam = "wide" }, ["SAFARI_ZONE_CENTER"] = { x = 1, y = 8, shape = "wide" }, - ["SAFARI_ZONE_EAST"] = { x = 21, y = 8, shape = "wide" }, - ["SAFARI_ZONE_NORTH"] = { x = 19, y = 14, shape = "wide" }, - ["SAFARI_ZONE_WEST"] = { x = 18, y = 3, shape = "wide" }, + ["SAFARI_ZONE_EAST"] = { x = 16, y = 8, + shape = "wide", turn = 90, cam = "wide" }, + ["SAFARI_ZONE_NORTH"] = { x = 22, y = 12, shape = "wide", turn = 90 }, + ["SAFARI_ZONE_WEST"] = { x = 20, y = 2, shape = "wide" }, ["SEAFOAM_ISLANDS_1F"] = { x = 14, y = 7, shape = "wide" }, - ["SEAFOAM_ISLANDS_B1F"] = { x = 11, y = 1, shape = "wide" }, - ["SEAFOAM_ISLANDS_B2F"] = { x = 16, y = 2, shape = "wide" }, - ["SEAFOAM_ISLANDS_B3F"] = { x = 25, y = 7, shape = "wide" }, - ["SEAFOAM_ISLANDS_B4F"] = { x = 12, y = 6, shape = "narrow" }, + ["SEAFOAM_ISLANDS_B1F"] = { x = 9, y = 8, + shape = "wide", turn = 90, cam = "wide" }, + ["SEAFOAM_ISLANDS_B2F"] = { x = 15, y = 9, shape = "wide", turn = 90 }, + ["SEAFOAM_ISLANDS_B3F"] = { x = 26, y = 7, shape = "wide", turn = 180 }, + ["SEAFOAM_ISLANDS_B4F"] = { x = 9, y = 7, shape = "narrow" }, -- Silph Co is office floors partitioned into small rooms, so the long lens -- often lands outside the walls it is meant to be looking between; the -- floors that could not be framed any other way ask for the wide one. - ["SILPH_CO_2F"] = { x = 16, y = 8, shape = "narrow" }, - ["SILPH_CO_4F"] = { x = 24, y = 2, shape = "narrow" }, + ["SILPH_CO_2F"] = { x = 4, y = 9, shape = "narrow", turn = 270 }, + ["SILPH_CO_4F"] = { x = 14, y = 14, shape = "narrow", turn = 90 }, ["SILPH_CO_5F"] = { x = 16, y = 7, shape = "wide" }, - ["SILPH_CO_6F"] = { x = 10, y = 8, shape = "narrow" }, + ["SILPH_CO_6F"] = { x = 19, y = 2, + shape = "narrow", turn = 90, cam = "wide" }, ["SILPH_CO_7F"] = { x = 1, y = 2, shape = "wide", cam = "wide" }, ["SILPH_CO_8F"] = { x = 8, y = 6, shape = "narrow" }, ["SILPH_CO_9F"] = { x = 20, y = 11, shape = "wide", cam = "wide" }, - ["SS_ANNE_1F_ROOMS"] = { x = 10, y = 1, shape = "narrow", cam = "wide" }, + ["SS_ANNE_1F_ROOMS"] = { x = 11, y = 1, shape = "narrow", cam = "wide" }, -- the ship is all two-cell corridors, so the wide arena shape fits nowhere -- aboard and the long lens always lands outside the hull ["SS_ANNE_2F"] = { x = 36, y = 8, shape = "narrow", cam = "wide" }, -- these two decks are byte-identical geometry, so they take the same spot - ["SS_ANNE_2F_ROOMS"] = { x = 11, y = 12, shape = "narrow" }, - ["SS_ANNE_B1F_ROOMS"] = { x = 11, y = 12, shape = "narrow" }, + ["SS_ANNE_2F_ROOMS"] = { x = 11, y = 12, shape = "narrow", cam = "wide" }, + ["SS_ANNE_B1F_ROOMS"] = { x = 11, y = 12, shape = "narrow", cam = "wide" }, ["SS_ANNE_BOW"] = { x = 8, y = 3, shape = "wide" }, - ["VICTORY_ROAD_2F"] = { x = 16, y = 6, shape = "wide" }, - ["VICTORY_ROAD_3F"] = { x = 20, y = 1, shape = "wide" }, + ["VICTORY_ROAD_2F"] = { x = 16, y = 6, shape = "wide", cam = "wide" }, + ["VICTORY_ROAD_3F"] = { x = 20, y = 1, shape = "wide", cam = "wide" }, -- ------- towns and the last interiors ["CERULEAN_CITY"] = { x = 15, y = 16, shape = "wide" }, - ["GAME_CORNER"] = { x = 8, y = 7, shape = "wide" }, + ["GAME_CORNER"] = { x = 8, y = 5, shape = "wide" }, -- the lab is ten cells by twelve, so the long lens is always off-map, and -- on it a desk clipped one mon and a pillar the other - ["OAKS_LAB"] = { x = 3, y = 2, shape = "narrow", cam = "wide" }, + ["OAKS_LAB"] = { x = 1, y = 3, shape = "narrow", turn = 90, cam = "wide" }, -- Saffron's gym is a grid of small walled cells: no wide shape exists -- anywhere in it, and the long lens sits inside a divider ["SAFFRON_GYM"] = { x = 9, y = 7, shape = "narrow", cam = "wide" }, ["SILPH_CO_3F"] = { x = 18, y = 11, shape = "wide", cam = "wide" }, - ["SILPH_CO_10F"] = { x = 1, y = 2, shape = "wide" }, - ["SILPH_CO_11F"] = { x = 1, y = 11, shape = "wide", cam = "wide" }, + ["SILPH_CO_10F"] = { x = 1, y = 1, shape = "wide", cam = "wide" }, + ["SILPH_CO_11F"] = { x = 10, y = 6, + shape = "wide", turn = 180, cam = "wide" }, -- the upper corridor (cols 4-5, rows 1-4) is sealed at runtime by the -- gym's barrier, so arenas there silently fail the fit test - ["VERMILION_GYM"] = { x = 4, y = 11, shape = "narrow" }, - ["VICTORY_ROAD_1F"] = { x = 11, y = 2, shape = "narrow" }, + ["VERMILION_GYM"] = { x = 3, y = 2, + shape = "narrow", turn = 90, cam = "wide" }, + ["VICTORY_ROAD_1F"] = { x = 11, y = 5, shape = "narrow", cam = "wide" }, ["VIRIDIAN_FOREST"] = { x = 16, y = 34, shape = "narrow" }, -- ------- the remaining routes - ["ROUTE_19"] = { x = 8, y = 6, shape = "narrow" }, + ["ROUTE_19"] = { x = 8, y = 31, shape = "narrow" }, -- the two surf routes fight AFLOAT, in the middle of their own sea rather -- than on the rim of beach the land search would otherwise find ["ROUTE_20"] = { x = 23, y = 7, shape = "wide" }, ["ROUTE_21"] = { x = 8, y = 46, shape = "wide" }, - ["ROUTE_22"] = { x = 35, y = 7, shape = "wide" }, - ["ROUTE_23"] = { x = 4, y = 36, shape = "wide" }, - ["ROUTE_24"] = { x = 13, y = 15, shape = "wide" }, + ["ROUTE_22"] = { x = 35, y = 7, shape = "wide", cam = "wide" }, + ["ROUTE_23"] = { x = 2, y = 34, shape = "wide", cam = "wide" }, + ["ROUTE_24"] = { x = 6, y = 9, shape = "wide", turn = 270, cam = "wide" }, ["ROUTE_25"] = { x = 32, y = 2, shape = "wide", cam = "wide" }, -- ------- caves, gyms and the Elite Four @@ -177,25 +183,29 @@ return { -- None of these tilesets has a grass tile at all, so the no-grass rule -- constrained nothing here; what constrains them is furniture, rock -- pillars and how small the rooms are. - ["AGATHAS_ROOM"] = { x = 2, y = 1, shape = "narrow", cam = "wide" }, - ["BRUNOS_ROOM"] = { x = 3, y = 1, shape = "narrow" }, - ["CELADON_GYM"] = { x = 0, y = 3, shape = "narrow" }, - ["CERULEAN_CAVE_1F"] = { x = 1, y = 7, shape = "narrow" }, + ["AGATHAS_ROOM"] = { x = 4, y = 2, shape = "narrow", cam = "wide" }, + ["BRUNOS_ROOM"] = { x = 2, y = 1, shape = "wide", turn = 90 }, + ["CELADON_GYM"] = { x = 3, y = 4, + shape = "narrow", turn = 90, cam = "wide" }, + ["CERULEAN_CAVE_1F"] = { x = 12, y = 8, shape = "narrow", cam = "wide" }, -- 2F is a maze of one-cell rock corridors; all 24 of its candidate spots -- hide a mon, so it borrows the floor below -- the same cave ["CERULEAN_CAVE_2F"] = { map = "CERULEAN_CAVE_B1F", x = 2, y = 0, shape = "wide" }, - ["CERULEAN_CAVE_B1F"] = { x = 2, y = 0, shape = "wide" }, - ["CERULEAN_GYM"] = { x = 0, y = 1, shape = "narrow" }, + ["CERULEAN_CAVE_B1F"] = { x = 2, y = 0, shape = "wide", cam = "wide" }, + ["CERULEAN_GYM"] = { x = 4, y = 2, shape = "wide" }, ["CHAMPIONS_ROOM"] = { x = 2, y = 2, shape = "narrow", cam = "wide" }, - ["CINNABAR_GYM"] = { x = 18, y = 10, shape = "narrow" }, - ["DIGLETTS_CAVE"] = { x = 19, y = 16, shape = "wide" }, - ["FIGHTING_DOJO"] = { x = 4, y = 1, shape = "narrow" }, - ["LANCES_ROOM"] = { x = 5, y = 15, shape = "wide" }, - ["LORELEIS_ROOM"] = { x = 5, y = 2, shape = "narrow" }, - ["MT_MOON_1F"] = { x = 24, y = 17, shape = "wide" }, - ["MT_MOON_B1F"] = { x = 5, y = 12, shape = "wide" }, - ["MT_MOON_B2F"] = { x = 2, y = 16, shape = "wide" }, + ["CINNABAR_GYM"] = { x = 9, y = 16, + shape = "narrow", turn = 90, cam = "wide" }, + ["DIGLETTS_CAVE"] = { x = 14, y = 26, + shape = "wide", turn = 90, cam = "wide" }, + ["FIGHTING_DOJO"] = { x = 3, y = 5, + shape = "narrow", turn = 90, cam = "wide" }, + ["LANCES_ROOM"] = { x = 4, y = 2, shape = "wide", cam = "wide" }, + ["LORELEIS_ROOM"] = { x = 4, y = 2, shape = "narrow" }, + ["MT_MOON_1F"] = { x = 25, y = 16, shape = "wide" }, + ["MT_MOON_B1F"] = { x = 4, y = 11, shape = "wide" }, + ["MT_MOON_B2F"] = { x = 8, y = 20, shape = "wide" }, -- The three gyms the default rig cannot stand back from. Five blocks is -- further than these rooms are wide, so the eye landed outside the map and @@ -204,7 +214,7 @@ return { -- BattleCam), which fits inside the room; the mons come out smaller and all -- three became stageable. It is asked for HERE, per map, so every area that -- does not ask keeps the long lens it was framed for. - ["FUCHSIA_GYM"] = { x = 7, y = 6, shape = "narrow", cam = "wide" }, - ["PEWTER_GYM"] = { x = 4, y = 8, shape = "narrow", cam = "wide" }, + ["FUCHSIA_GYM"] = { x = 8, y = 6, shape = "narrow", cam = "wide" }, + ["PEWTER_GYM"] = { x = 4, y = 1, shape = "narrow", turn = 90, cam = "wide" }, ["VIRIDIAN_GYM"] = { x = 10, y = 8, shape = "narrow", cam = "wide" }, } diff --git a/lib/Diorama.lua b/lib/Diorama.lua index c63b3b1..7df22b4 100644 --- a/lib/Diorama.lua +++ b/lib/Diorama.lua @@ -113,6 +113,41 @@ function Diorama.reset() Diorama.release() end +-- ------- which way a staged fight lies on the table +-- +-- The disc's bearing while a battle is up: the player's own hand-turn, with +-- the ARENA's quarter turn taken back out of it. +-- +-- An arena may be laid down any of the four ways (BattleArena's `turn`), and +-- the promise that field makes everywhere else is that turning it changes the +-- GROUND under the fight and never the fight itself -- the two Pokemon land +-- on the same marks, seen the same way round. Every other camera keeps that +-- promise by construction: the flat shot and the standard VR mount are both +-- built from BattleCam's eye, which turns with the arena, so the composition +-- follows it round. +-- +-- This one is not built from that eye. It is a disc of map lifted onto the +-- table, and its bearing is the arena's bearing in the WORLD -- so a fight +-- staged on a turned arena arrived on the table lying across the head that +-- was looking at it, while the same fight on an unturned one faced properly. +-- Same fight, same composition everywhere else, sideways here. +-- +-- So the turn comes back out. Subtracted, matching the sign the standard +-- mount already lands on: its yaw is atan2 of (eye - focus), and rotating +-- that pair by +turn takes the bearing to (bearing - turn). One rule, two +-- seats. +-- +-- The hand-turn stays on top of it, because that is the player moving the +-- model and is theirs to keep. +function Diorama.battleYaw(arena) + local turn = (arena and arena.turn) or 0 + if turn == 0 then return Diorama.yaw end + local yaw = Diorama.yaw - math.rad(turn) + -- kept in (-pi, pi] like the grips leave it, so nothing downstream has to + -- care which way round it came + return (yaw + math.pi) % (2 * math.pi) - math.pi +end + -- Open a diorama frame. `mode` is VR.mode()'s answer; anything that is -- not a diorama mode closes it. function Diorama.begin(mode) diff --git a/lib/VR.lua b/lib/VR.lua index 3682f20..c95ccea 100644 --- a/lib/VR.lua +++ b/lib/VR.lua @@ -371,7 +371,11 @@ local function renderWorld(views, ctl) end anchor = VRRig.dioramaAnchor(Voxel.angle, Diorama.offset) scale = VRRig.dioramaScale(frame, Voxel.FOCAL) / zoom - if Diorama.yaw ~= 0 then mountYaw = Diorama.yaw end + -- the hand-turn, and -- while a fight is staged -- the arena's own + -- quarter turn taken back out, so a turned arena arrives on the table + -- facing the head rather than lying across it (Diorama.battleYaw) + local dioYaw = battle and Diorama.battleYaw(battle) or Diorama.yaw + if dioYaw ~= 0 then mountYaw = dioYaw end elseif battle then -- the over-the-shoulder seat the flat battle shot stands in, pulled -- close enough for a headset's own lens (see VRRig.battleMount), at diff --git a/tests/dramatic_shape_test.lua b/tests/dramatic_shape_test.lua index 977333f..4a7ecfa 100644 --- a/tests/dramatic_shape_test.lua +++ b/tests/dramatic_shape_test.lua @@ -5027,6 +5027,68 @@ local eyaw = select(2, VRRig.battleMount({ 150, 10, 200 }, { 100, 10, 200 })) T.check(near(eyaw, math.pi / 2), "a camera east of its focus turns the mapping a quarter toward west") +-- ------- both VR seats follow the arena's quarter turn +-- +-- `turn` promises the same composition on different ground, and every camera +-- has to keep it or the promise is only true on the flat screen. The two VR +-- seats keep it in different ways and both are pinned here. +-- +-- THE STANDARD MOUNT keeps it by construction: it is built from the flat +-- battle camera's own eye and aim, which turn with the arena, so the seat +-- goes round with them. That is asserted rather than assumed, because the +-- day someone builds this seat from the arena's cells instead, the picture +-- stays plausible and starts pointing the wrong way. +-- +-- THE DIORAMA disc does NOT get it for free: a fight arrives there as a +-- pillar of map about the arena's midpoint, whose bearing is its bearing in +-- the WORLD, so a turned arena landed on the table lying across the head. +-- Diorama.battleYaw takes the turn back out; the hand-turn rides on top. +-- +-- In its own scope: the suite's main chunk is at LuaJIT's local ceiling. +;(function() + local Arena = run.loader.exports.DRAMATIC_SHAPE.lib.require("BattleArena") + local Cam = run.loader.exports.DRAMATIC_SHAPE.lib.require("BattleCam") + local Dio = run.loader.exports.DRAMATIC_SHAPE.lib.require("Diorama") + + local function mountYawFor(turn) + Cam.reset() + local a = Arena.at(6, 6, "wide", turn) + local rig = Cam.rig(a, 0, true) + return select(2, VRRig.battleMount(rig.eye, rig.focus)) + end + + local base = mountYawFor(0) + for _, turn in ipairs({ 90, 180, 270 }) do + -- rotating (eye - focus) by +turn takes atan2(dx, dz) to (bearing - turn) + local want = (base - math.rad(turn) + math.pi) % (2 * math.pi) - math.pi + local got = (mountYawFor(turn) + math.pi) % (2 * math.pi) - math.pi + T.check(near(got, want, 1e-6), + ("the standard VR mount turns with a %d-degree arena: %.4f, wanted %.4f") + :format(turn, got, want)) + end + + -- the disc: the turn comes back out, so the fight lies the same way on the + -- table however the ground under it is laid + local hand = Dio.yaw + Dio.yaw = 0 + T.eq(Dio.battleYaw({ turn = 0 }), 0, + "an unturned arena leaves the disc where the hands left it") + T.check(near(Dio.battleYaw({ turn = 90 }), -math.pi / 2, 1e-9), + "a quarter-turned arena counter-turns the disc a quarter") + T.check(near(Dio.battleYaw({ turn = 270 }), math.pi / 2, 1e-9), + "and three quarters comes round the other way, inside (-pi, pi]") + T.eq(Dio.battleYaw(nil), 0, "no arena is no turn, not an error") + -- and the same rule the standard mount lands on, so the two agree + T.check(near(Dio.battleYaw({ turn = 90 }) - Dio.battleYaw({ turn = 0 }), + mountYawFor(90) - mountYawFor(0), 1e-6), + "the disc and the standard mount answer a turn by the same amount") + -- the player's own hand-turn still rides on top of it + Dio.yaw = math.rad(30) + T.check(near(Dio.battleYaw({ turn = 90 }), math.rad(30) - math.pi / 2, 1e-9), + "the hands keep whatever they did to the model, on top of the turn") + Dio.yaw = hand +end)() + -- an eye seated with that yaw really faces the arena: an identity head at -- the seat comes out looking WEST, and the view agrees to the metre local seated = VRRig.eyeCamera(pose, fov, { 150, 10, 200 }, { 0, 0, 0 },