Files
bearstonem--openmohaa-vr/misc/android/autoexec.cfg
T
bearstonem d9968210aa fix(vr): correct visual glitches and reduce rendering overhead
Restore desktop HUD behavior and objective state, fix stereo text lifetimes, HUD placement and colors, sprite culling, and font shader passes. Handle swapchain timeouts and incomplete stereo frames safely, remove forced GPU waits, and correct rendering timing attribution.

Track client-game source additions in CMake. Validated desktop and Android GL1/GL2 builds plus targeted regression and sanitizer checks. Headset appearance and performance remain unverified.
2026-10-14 14:48:22 -07:00

407 lines
20 KiB
INI

// Deploy with:
//
// adb push misc/android/autoexec.cfg \
// /sdcard/Android/data/org.openmoh.openmohaa/files/main/autoexec.cfg
// adb shell am force-stop org.openmoh.openmohaa
//
// Then launch from inside the headset. Never adb shell am start.
//
// Not packaged into the APK - it goes next to the game data, which is where the
// filesystem looks for it. Reinstalling the APK keeps it; clearing app data or
// reflashing the game files loses it, which is why it lives here.
//
// autoexec.cfg is exec'd after omconfig.cfg, cannot be loaded from a pk3, and
// lands before R_Init - so it beats both the stored config and the renderer's
// own defaults, latched and archived cvars included. It is the only way to set
// a cvar on a device with no console. See docs/markdown/04-coding/04-gl4es.md.
// Where the weapon model sits in the hand:
//
// scale, right, up, forward, pitch, yaw, roll
//
// The view model is authored to sit at the camera with the arms reaching
// forward, so putting its origin at the controller leaves that built-in offset
// in place. This takes it back out. Right/up/forward are in engine units along
// the weapon's own axes after any rotation correction, so they mean what they
// say however the gun is pointing.
//
// To dial it in: push a new value, force-stop, relaunch. No rebuild needed.
// Negative forward pulls the model back towards you, negative up drops it,
// negative right moves it to the off hand's side.
//
// The reference keeps one of these per weapon (vr_weapon_adjustment_<id>);
// this is a single global one until the numbers for each are known.
// The offsets are back to zero: the -10 right and -6 up were pulling the model
// back from an origin anchored on the model itself, and the weapon is now
// anchored on tag_weapon_right, so the gun's own attachment point lands on the
// controller and there is nothing left for those to cancel. Retune from here
// rather than from the old numbers.
//
// The roll is not zero, and is not a fudge. Anchoring forces the *bone* to take
// the controller's orientation, and a wrist bone's local axes do not run the way
// a controller is held - the mesh inherits that mismatch as a constant twist.
// 180 is the first thing to try because upside down is what a half turn about
// the aim axis looks like. If the model ends up on its side instead, the
// mismatch is in pitch or yaw; those are the two fields before this one.
set vr_weaponAdjust "1,0,0,0,0,0,180"
// The same seven fields for the hand that is not holding the weapon, which is
// drawn as a second copy of the view model on the off hand's controller.
//
// The offsets are zero because this hand is anchored on its own bone
// (Bip01 L Hand, measured 26.7 units out from the model origin), so there is no
// built-in offset left to cancel - only the wrist's own rotation convention,
// which is what the roll is for. Same reasoning as the weapon above.
//
// The two hands are mirrored, so do not assume the correction is identical:
// if one comes out right at 180 and the other is still wrong, try -180 here.
//
// If the hand is nowhere near the controller rather than merely turned, read the
// log instead of tuning: the candidate lines name every anchor considered and
// how far out it sits, which says whether the bone was found at all.
set vr_offHandAdjust "1,0,0,0,0,0,180"
// How many game units make a metre. Not a feel value - it is this game's own
// scale, and getting it wrong makes everything measured against the player the
// wrong size.
//
// MOHAA's art is authored at 16 units per foot; its tik files say so where they
// convert from centimetres (models/weapons/bar.tik: "16/30.5 since world is in
// 16 units per foot"). 16 / 0.3048 = 52.5, and the player dimensions agree -
// MAXS_Z 94 and DEFAULT_VIEWHEIGHT 82 are 1.79 m standing, 1.56 m to the eye.
//
// This is set here as well as changed in code because vr_worldscale is
// CVAR_ARCHIVE: the value already stored in omconfig.cfg wins over any new
// default, so a code change alone would do nothing on a device that has run
// before.
set vr_worldscale 52.5
// How long the weapon hand grip may be held and still count as a tap for
// reload, rather than being ignored.
set vr_reloadTapMs 400
// The HUD and the objectives live on a panel on the player's left wrist, shown
// by turning the wrist up to look at it, and the laser pointer already reaches
// it. Flat content in the eye buffers has no stereo disparity to fuse while
// each eye still warps it through its own asymmetric frustum, which is the
// problem the quad layers exist to solve.
//
// The compass is deliberately not on it - it is drawn heads up in both eyes
// every frame instead, because it is how the player knows which way the next
// objective is and a heading you have to ask for is one you navigate without.
// See UI_DrawVRCompass in cl_ui.cpp.
//
// Set here as well as defaulted in code because it is CVAR_ARCHIVE: a value
// already in omconfig.cfg would otherwise win over the new default.
// The wrist panel is off. The HUD - compass, ammo, health, objectives - is
// drawn into both eye buffers instead and floated in front of the player by
// R_VRAdjust2DRect, at vr_hudDepth metres. That is the same thing RTCWQuest
// does with cg_hudDepth and cg_hudScale, and it is what "a floating 2D panel
// half a metre away" actually is.
//
// It was on for a while, which put the HUD on a quad strapped to the left
// wrist. That works, but it is a glance away rather than always there, and the
// compass has to be always there to be worth having.
set vr_wristPanel 0
// The gathered VR HUD: compass, objectives, health, ammo, weapon strip, drawn
// as one group in the middle of the view by CG_DrawVRHud.
//
// The retail HUD is five menus loaded from ui/hud_*.urc, each aligned to a
// different corner of a 4:3 screen. On a monitor those corners are a glance
// away; through a lens they are the far edge, where all five were reported as
// present and unreadable. This draws the same numbers again in one place,
// reading them from cl.snap.ps.stats[] - the same array the widgets read - so
// nothing has to be recovered out of the UI.
//
// 0 puts the retail corner HUD back, without a rebuild. 1 keeps the group
// hidden and brings it up on the off hand's X button for vr_hudTime seconds
// before it fades - almost everything it carries is only wanted at the moment
// it is asked for, and a HUD that is always there is always in the way. 2 pins
// it up, which is what to use while arguing with the layout.
set vr_hud 1
set vr_hudTime 3
// Where the group sits and how big it is. X and Y are fractions of the display,
// so they mean the same thing at any eye resolution; scale is a multiplier on a
// cluster authored against a 1080 line display.
//
// Y is not where it looks like it is, and it is worth knowing why before
// dialling it. It places the cluster in the flat frame, and the renderer then
// takes that frame, scales it to vr_hudScale about the middle of the display,
// and lifts it by the eye's vertical frustum asymmetry - 170 pixels of 1760 on
// this headset. Both of those pull the number towards the middle of the view,
// so the range that reaches the eye at all is about 0.13 to 0.68 of it:
//
// 0.62 -> 0.47 of the eye, just above the middle
// 0.73 -> 0.53, just below it
// 0.85 -> 0.60
// 0.92 -> 0.63
// 1.00 -> 0.68, as low as it goes
//
// 0.92 puts it well down the view, which is where something you check without
// breaking your aim belongs.
set vr_hudGroupX 0.5
set vr_hudGroupY 0.92
set vr_hudGroupScale 1.0
// How solid the group is over the world. Worth pulling down once the layout
// settles - a HUD you can see through is less in the way of a game that is
// mostly about looking at things.
set vr_hudAlpha 1.0
// Where the retail compass widget sits, for vr_hud 0. Unused while the gathered
// HUD is up, since that draws its own compass ring.
//
// hud_compass.urc aligns it left top, which is 0,0. On a monitor that is a
// corner you can glance at; in a headset it is the far edge of the lens.
set vr_compassX 0.5
set vr_compassY 0.62
// How far in front of the player the flat HUD floats, in metres. Back to the
// reference's default: at 0.5 the eyes have to converge hard on something
// filling the middle of the view, and it came back as double vision that would
// not fuse.
set vr_hudDepth 2.0
// How much of the frustum centring to apply to the flat HUD. 0 centres it on
// the buffer, 1 centres it on the eye's optical axis.
//
// Back to 1, and it has to be 1. This was set to 0 on the reading that the
// centring was overshooting - a marker box came out at (595,530) of a 1680x1760
// eye instead of (840,880) - but that measurement was of the old bug, which
// scaled the correction to whichever widget's ortho window it was handed rather
// than to the display. See R_VRAdjust2DRect.
//
// 0 is the one setting that cannot fuse at all. The centre of the left eye's
// buffer is about seven degrees left of straight ahead and the centre of the
// right eye's is about seven degrees right, because the runtime hands out a
// frustum that is wider towards each temple. Putting the HUD at both buffer
// centres therefore asks the eyes to diverge by fourteen degrees, and eyes do
// not diverge - they hold two images instead, which is the double vision. 1
// puts it on both optical axes, where the only disparity left is the half IPD
// the parallax deliberately adds.
set vr_hudOffset 1
// One line per eye naming the tangents, the centring, the parallax and the
// resulting shift in pixels. Convergence cannot be read off a screenshot - the
// headset capture is one reprojected eye, so the two images that have to fuse
// are never both in it. The two lines have to report shifts that are equal and
// opposite; if they do not, nothing downstream of them will fuse.
set vr_traceHud 1
// How much of the display the flat HUD is drawn across. This and vr_hudDepth
// are the mechanism RTCWQuest uses for its in-game HUD (cg_hudScale,
// cg_hudDepth) and R_VRAdjust2DRect is the same maths: the 2D is scaled into
// the readable middle of the view, recentred per eye to undo the asymmetric
// frustum, then given a horizontal parallax of half an IPD at vr_hudDepth so
// the two eyes fuse it at that range instead of at infinity.
//
// Everything flat in the eye buffers rides on this - compass, ammo, health,
// objectives.
set vr_hudScale 0.55
// The wrist panel's geometry, in metres. All archived rather than forced from
// code, so these can be dialled in with a push and a force-stop instead of a
// rebuild - which matters, because every one of them is a feel value that can
// only be judged with the thing on your arm.
//
// vr_wristSize how big the quad is
// vr_wristDistance how far it stands off the back of the hand
// vr_wristBack how far back along the forearm, towards where a watch
// sits - the controller's own pose is out at the fingers
//
// It was 0.26 across held 0.16 out, which is a billboard floating above the
// arm rather than something worn on it. Smaller and closer now.
set vr_wristSize 0.24
set vr_wristDistance 0.05
set vr_wristBack 0.10
// Which part of the 1680x1760 screen the panel shows, as fractions. The HUD is
// laid out for a whole screen and uses a small share of it, so showing less is
// what makes what is left big enough to read - the compositor crops for free.
// The default is the top band at full width, which is where the compass and
// the objectives are.
// Framed on the objectives block. CG_DrawObjectives puts it at x 25..475, and
// its top comes from UI_GetObjectivesTop, which now keeps it clear of the
// enlarged compass - so at vr_compassScale 3 it starts at y 389 and runs to
// about 490 of the 1680x1760 buffer.
//
// At full width it was a twentieth of the panel and far too small to read;
// this fills the panel with it. The quad takes the shape of the crop, so a wide
// short crop is a wide short panel and nothing is stretched.
//
// If vr_compassScale changes, this has to follow it: the objectives start
// directly below the compass, at 128 times that scale.
set vr_wristCropX 0
set vr_wristCropY 0.21
set vr_wristCropW 0.32
set vr_wristCropH 0.10
// One line every two seconds naming why the objectives are or are not on the
// panel: which pass drew them, the alpha after the panel override, and how many
// the level has actually set. Zero set means the level never sent any, which a
// map entered straight through vr_startMap - past the scripting that hands them
// out - is a very good way to arrange.
set vr_traceObjectives 0
// The objectives stay up instead of being a hold-the-key overlay. There is no
// key to hold in a headset.
set vr_objectivesAlways 1
// The reticle, off. It is drawn at the centre of the screen, which in a headset
// is the centre of the player's head - and the weapon is aimed by the
// controller, not by where they are looking, so it marks a spot the gun is not
// pointing at and follows the face around. 1 puts it back.
set vr_crosshair 0
// Dumps the left eye to main/vrshotN.tga every two seconds. Pull them one at a
// time - pulling the directory drags the pk3s with it.
// The per frame timing report is three lines a second, which rolls anything
// useful out of logcat within a minute or two. Off while hunting anything that
// prints once.
set vr_traceFrame 0
// Opt-in synchronous GPU timing. This waits for GPU completion after each eye
// and changes frame performance; leave it off for normal play and CPU profiling.
set vr_traceGpu 0
set vr_captureEye 0
// One line per use press, naming which of the three ways it can come up empty
// actually happened: the body's eye, the eye the trace really started from
// after the play space offset, how far the trace got, and what the box at the
// end of it caught. A press that prints nothing never reached the game at all,
// which points at the binding rather than at the reach.
set vr_traceUse 1
// Weapons live on the body: reach to a hip or over a shoulder and squeeze the
// grip to draw, the way Half-Life: Alyx does it. Right hip is the pistol, left
// hip grenades, and the long guns are slung over the shoulders - rifle right,
// smg left. Either hand can draw from any of them.
set vr_holsters 1
// One line per grip squeeze, giving where the hand was relative to the head
// when it closed and which zone that landed in - or "nothing", which is the
// interesting case. The zone boxes in vr_openxr.c are a first guess at where
// people actually reach; this is how to replace them with measurements.
set vr_traceHolster 1
// One line per upload of the dynamic lightmap - which is how a muzzle flash or
// an explosion lights the world in this engine: it rewrites the lightmap of
// every surface the light reaches rather than adding a pass over them.
//
// At 1, "patch peak" is the brightest texel uploaded, out of 765. Measured on
// the device: peak 548, over half the texels lit, no GL error. So the patch the
// CPU builds is bright and it reaches GL without complaint, and whatever turns
// the walls black is downstream of both.
//
// At 2 it stops measuring and starts staining: the whole atlas goes white
// before the front end touches it, and every texel a light reaches is painted
// magenta over the top. Read off the walls, not out of the log. White can only
// brighten, so anything reading the atlas outside a written patch is
// unmissable. A dark green stain was tried first and was useless in a level
// made of hedgerows.
//
// 3 additionally skips ProjectDlightTexture, Quake 3's second-pass dynamic
// light. Measured: it never runs here, so this changes nothing.
//
// Off, because none of it is needed to play. Turn it on to resume the hunt.
set vr_traceDlight 0
// One line per font shader loaded, naming what the UI text is actually drawn
// with - whether the lookup found gfx.shader's entry or fell back to an opaque
// default, and whether the atlas kept its alpha through the upload. Both would
// produce the black boxes the UI currently draws instead of text.
set vr_traceFont 1
// The world does not respond to dynamic lights. This is a workaround for an
// unsolved bug and not a preference: a muzzle flash makes the ground about
// four times *darker* rather than lighting it, over roughly a sixth of the
// screen, for as long as the trigger is held. Losing flashes lighting the
// scenery is the smaller loss.
//
// Lights still exist and still light models and sprites - only the world's
// lightmap is left alone. Set it back to 1 to see the bug.
//
// docs/markdown/04-coding/05-dlights.md has the measurements, the seven
// theories that were wrong, and where to pick it up.
set r_worldDlights 0
// One line per throw: the peak speed of the swing and the direction it was
// going, in the play space's own frame. The scale and clamp applied to that in
// weapon.cpp are guesses about how hard a person throws in a living room, and
// this is what replaces them with a measurement.
set vr_traceThrow 1
// Grenade throwing, all four archived - which is exactly why they are set here
// as well as defaulted in code. An archived cvar keeps whatever omconfig.cfg
// already has, so changing a default in the source does nothing at all on a
// device that has run before. That trap cost a build here: the floor stayed at
// its old 0.45 while the curve moved under it, and every throw came out
// clamped to the same 45% regardless of how hard it was thrown.
//
// vr_throwFull hand speed, in units a second, counting as a full throw.
// Measured: hard overhand 278-338, gentle lob 145-192.
// vr_throwCurve above 1 bends the gentle end down without touching the top,
// which is what stops a 45 degree lob outrunning a flat throw.
// vr_throwMin floor, so a polite swing still leaves the hand.
// vr_throwDamp how much of the sideways drift to take out of a slow throw,
// scaled by how gentle it is. 0 is honest physics and throws
// lobs where the arm actually went, which is not where the
// player meant. The only deliberate lie in here.
set vr_throwFull 340
set vr_throwCurve 1.5
set vr_throwMin 0.2
set vr_throwDamp 0.8
// --- weapon test harness ---
//
// Playing through to wherever the game hands over a gun, on every build, is not
// a workable way to tune weapon alignment. There is no console in a headset to
// type "give" into either, and autoexec runs before there is a map - let alone
// a player to give anything to.
//
// All three are off on purpose. Filled in, they skip the game: cheats on,
// every weapon handed over, and a map loaded straight past the menu. That is a
// harness for tuning alignment, not how anyone should be handed the game.
//
// set cheats 1
// set vr_testStart "give all"
// set vr_startMap "m4l1"
//
// Each needs its own timing, which is why they are three cvars and not one.
// vr_testStart runs once, a second and a half after the player is actually in
// the world - autoexec is far too early, there is no map yet, let alone a
// player to give anything to. vr_startMap waits until the client is sitting
// idle at the menu, because a map loaded from autoexec is torn back down by the
// game's own startup.
set cheats 1
set vr_testStart "give all"
// Drop straight into a map on launch. Empty is the menu, which is how the game
// is meant to start.
//
// Not "map m4l1" directly: autoexec runs during Com_Init, and the game's own
// startup - the intro stages, then the menu - comes afterwards and tears any
// map loaded here straight back down. This waits until the client is sitting
// idle at the menu and then loads it.
//
// m4l1 is the weapon test map, chosen off the game's own data rather than by
// reputation:
//
// - its script calls itself THE BOCAGE, and it opens on the Normandy
// hedgerows with Manon, who is spawned with her AI disabled and set
// nodamage - so nothing is shooting at you while you look at your hands;
// - it has 2 spawn references in 538 lines of script, against m1l1's crowd
// and m1l2a's 59 in 3660. Nearly nothing is scripted to happen at the
// start;
// - global/loadout.scr hands the player a colt45 and an m1_garand and selects
// the rifle, so there is a weapon in hand at spawn without waiting for one.
//
// That last point is what makes it the right map for the hands specifically:
// the Garand is the weapon that swaps the left hand surface to "garandhand",
// so this level exercises both hand meshes rather than just the common one.
// Switch to the pistol to see the other.
set vr_startMap "m4l1"