22 KiB
Persistent configuration (questshock.config.json)
QuestShock can load a persistent, human-editable configuration file at
startup, in addition to (and independent of) the engine's own prefs.txt.
-
Location:
/sdcard/questshock/questshock.config.json- the same folder you already copyres/data/res/soundinto (see README.md section 5.1). -
Format: plain JSON, grouped by concern. Each top-level key is a group; each key inside a group is one option.
-
Loading: read automatically at app startup, before the splash screen, if the file exists. If it doesn't,
questshock.config.default.json(same folder) is used instead - a copy bundled with the app, refreshed on every launch, never overwriting your own savedquestshock.config.json. If neither file exists, the engine behaves as it did before this file existed (driven byprefs.txtas usual). -
Saving: never automatic. In-headset, open the menu (controller menu button) and press Save Configuration to write the file from your current live settings, overwriting it if present. Press Load Configuration to re-read the file and apply it without restarting the app.
-
Live vs. restart-required: every option below is tagged as either live-appliable (applying it while already playing takes effect immediately) or restart-required. Pressing Load Configuration applies every live-appliable change straight away; if the file also changes something restart-required, a dialog lists exactly which option(s) need it and offers:
- Dismiss - keep playing with the live-appliable changes already applied; the restart-required option(s) stay as they were.
- Restart Now - relaunches the app, which re-runs this same startup load and so applies every group in the file, including the restart-required ones.
(Every option currently documented below is live-appliable, so this dialog won't actually appear yet - it's built in for options added in a future version that do need a restart.)
-
Unrecognized groups/keys are ignored (harmless if you're editing the file in advance for a future version). An invalid value for a specific option (see each option's possible values below) is logged and that one option is skipped - every other valid option in the file still applies. A file that isn't valid JSON at all is ignored entirely, with nothing applied.
-
questshock.config.jsonandprefs.txtare fully independent - loading or saving one never reads or writes the other.
Example
{
"video_options": {
"renderer": "opengl",
"resolution": "1024x768",
"initial_screen_width_m": 1.6,
"initial_screen_distance_m": 2.0,
"game_screen_width_m": 2.8,
"game_screen_distance_m": 2.0,
"screen_width_offset_m": 1.0,
"screen_width_step_m": 0.6,
"screen_distance_offset_m": 1.0,
"screen_distance_step_m": 0.5
},
"general": {
"language": "english"
},
"player_position": {
"x": 0.0,
"z": 0.0,
"yaw_degrees": 0.0
},
"player_controls": {
"move_forward": "left_thumbstick_up",
"move_back": "left_thumbstick_down",
"strafe_left": "left_thumbstick_left",
"strafe_right": "left_thumbstick_right",
"turn_left": "right_thumbstick_left",
"turn_right": "right_thumbstick_right",
"jump": "left_thumbstick_click",
"mouse_click": "right_a",
"mouse_double_click": "right_b",
"stand": "right_thumbstick_up",
"prone": "right_thumbstick_down",
"map_view": "right_thumbstick_click",
"fire_weapon": "right_trigger"
}
}
Video Options (video_options)
renderer
Which renderer the game uses to draw the 3D view.
- Type: enum
- Possible values:
"software","opengl" - Restart required?: No (live-appliable)
resolution
The game's internal rendering resolution.
- Type: enum
- Possible values:
"320x200","320x400","640x400","640x480","1024x768" - Restart required?: No (live-appliable)
initial_screen_width_m, initial_screen_distance_m, game_screen_width_m, game_screen_distance_m
The virtual screen's physical width and distance from the player (both in meters) - the game shows two independently configurable screen "profiles", switched automatically based on what's currently on screen:
- Initial Screen (
initial_screen_*) - the splash screen, intro cutscenes, and the game's own main menu. - Game Screen (
game_screen_*) - actual gameplay (the 3D view, automap, cyberspace, etc.).
Switching happens automatically - no player action needed - the instant the game itself transitions between these (e.g. starting/loading a game, or returning to the menu).
- Type: number (meters)
- Possible values: the in-headset Video Options tab offers 5 presets
each - widths
1.0,1.6(the original, pre-this-feature default),2.2,2.8,3.4; distances1.0,1.5,2.0(the original, pre-this-feature default),2.5,3.0- but a hand-edited value doesn't need to match a preset exactly (it's clamped to a sane range - width[0.2, 10.0], distance[0.5, 10.0]- rather than snapped or rejected; the Video Options tab just shows no button selected for an in-between value). - Restart required?: No (live-appliable)
- Note: the screen is always flat - there's no curve option.
screen_width_offset_m, screen_width_step_m, screen_distance_offset_m, screen_distance_step_m
Reshapes the 5 preset buttons the Video Options tab offers for width and distance (above) - config-file-only, there's no in-headset control for these. Shared by both Initial Screen and Game Screen (both columns' buttons show the same 5 values per row) - this only changes what the buttons offer, not either profile's current value.
Preset button index (0-4) shows offset + step * index. For example,
a distance offset of 0 and step of 2 shows buttons 0, 2, 4,
6, 8; the defaults below reproduce this feature's original launch
presets exactly.
- Type: number (meters)
- Default values: width offset
1.0, step0.6(buttons1.0,1.6,2.2,2.8,3.4); distance offset1.0, step0.5(buttons1.0,1.5,2.0,2.5,3.0). - Restart required?: No (live-appliable)
- Note: not clamped - an unusual offset/step (e.g. negative step) is accepted as-is. A resulting preset value outside the current value's own clamp range (see above) still clamps normally when that preset button is actually pressed.
General (general)
language
The game's UI/text language - both the game's own resource strings and this project's own menu/keyboard chrome text (title bar, buttons, etc.).
- Type: enum
- Possible values:
"english","french","german" - Restart required?: No (live-appliable) - applies the same way the game's own in-game options menu applies a language change (reloads the language-specific resource files and refreshes the inventory/MFD/side-icon UI immediately).
- Note: French and German both need their own full resource pack
(
frnstrng.res/mfdfrn.res,gerstrng.res/mfdger.resunderres/data/) - if any of those files aren't present, the switch is refused (logged,questshock.config.jsonand the current language are left unchanged) rather than leaving the game with no valid resource file open. See README.md section 3 ("Game assets") for wheremfdfrn.res/mfdger.rescome from - the GOG Enhanced Edition installer alone doesn't include them. - The intro cutscene is also played in the selected language when its
voice-over file (
svfrintr.res/svgeintr.resunderres/data/) is present; otherwise the English intro plays.
Player Position (player_position)
Where the player is virtually standing/facing, independent of - and not
reset by - starting a new game or loading a save (same independence from
prefs.txt/savegames as every other group here). Set by moving around
in-headset (menu open, left thumbstick snap-turns, right thumbstick smooth-
moves) to line yourself up with the game quad ("virtual screen"). Height is
never included here - it always comes from your real tracked headset
height, not this file.
Startup fallback: only applied at startup if all three fields (x,
z, yaw_degrees) are present - a config file with none of them (or none
at all), or a partial/hand-edited one missing any of the three, falls back
to an automatic recenter instead (see Reset Position below), using
your real head pose at that moment. A complete saved position is applied
as-is, with no such recenter.
Reset Position (Main Menu tab) is a live-only action, not a config
value: it moves you to the centre in front of the game quad, based on where
your head actually is right now. It resets only your position, not which
way you're facing, so the screen does not rotate to follow your gaze - press
it while looking off to one side and the screen stays off to the other side.
The automatic recenter at startup (when there's no saved position) does
face the screen towards where you're looking. Reset Position never reads or
writes questshock.config.json.
x, z
Horizontal position, in meters, on the play space's floor plane.
- Type: number
- Restart required?: No (live-appliable)
yaw_degrees
Which way the player is virtually facing, in degrees.
- Type: number
- Restart required?: No (live-appliable)
Player Controls (player_controls)
Maps each of 57 in-game actions to a physical controller input: 9 movement/click actions, 2 free-view actions, 11 cyberspace actions, and 35 for the engine's own keyboard hotkeys and firing (posture, save/load, weapon select, MFD panels, etc. - see the hotkey table below). Some actions are bound by default (see the default-input columns); the rest are unbound and can be bound on demand. Assign them in the Controller Options menu tab: tap an action's binding button, then press the button or stick direction to use ("Press a button…"); tap it again, or wait 8 seconds, to cancel without changing it.
- Type: one enum-string field per action (below)
- Restart required?: No (live-appliable) - a rebind takes effect immediately, and persists across restarts once saved (Save Configuration, same as every other group here).
- Menu movement: while the Controller Options tab is open, the menu's own
thumbstick snap-turn and smooth-move (see
player_position) are disabled, so pressing inputs to assign them doesn't move you away from the screen. - Not included here: opening/closing the VR menu itself is a fixed,
non-remappable button (see "Menu button" below) - there's no
menu_togglefield, and the Controller Options tab never lets you reassign it. This guarantees you can always reach this tab and press Reset to Defaults to recover from any bad rebind, no matter what else you've reassigned. - Reset to Defaults: restores the "Default input" column below for
every action not overridden by a
player_controlsgroup inquestshock.config.default.json- if that file defines one (it doesn't, out of the box), the values it specifies are applied on top of the table below instead, so you can customize what "reset" means without a rebuild. This is independent of your ownquestshock.config.json- resetting doesn't read or write that file, just the live bindings (Save Configuration afterward to persist them).
Actions
| Action key | Default input | Effect |
|---|---|---|
move_forward |
left_thumbstick_up |
Walk forward |
move_back |
left_thumbstick_down |
Walk backward |
strafe_left |
left_thumbstick_left |
Step sideways, left |
strafe_right |
left_thumbstick_right |
Step sideways, right |
turn_left |
right_thumbstick_left |
Turn left |
turn_right |
right_thumbstick_right |
Turn right |
jump |
left_thumbstick_click |
Jump |
mouse_click |
right_a |
Click - operates the keyboard/game quad's cursor, whichever the pointing hand's ray is over |
mouse_double_click |
right_b |
Double-click - game quad only (e.g. using an inventory item); has no effect over the menu or keyboard |
free_left_thumbstick_view |
left_squeeze |
Hold to look around with the left thumbstick (see below) |
free_right_thumbstick_view |
none |
Hold to look around with the right thumbstick (see below) |
Movement actions bound to a thumbstick direction are suppressed while the
VR menu is open, since the same physical stick already snap-turns/smooth-
moves the player in that state (see player_position above) - this is the
one case where a binding's effect depends on whether the menu is open, and
it only ever affects the 8 thumbstick-direction inputs below.
Free view (free_left_thumbstick_view, free_right_thumbstick_view) is
the engine's mouse look driven by a thumbstick. While the bound input is held,
that stick looks around instead of doing its normal job: left/right turns the
view, up/down looks up and down, in proportion to how far the stick is pushed.
The stick's own direction bindings (move, strafe, turn, stand/prone) are
suspended while it is held, and the cursor stays centred in the view. Release
the input to return to normal. It has no effect while the VR menu is open.
Cyberspace actions
Cyberspace has its own set of movement actions, active only while the player
is in cyberspace. While in cyberspace the normal movement actions
(move_*, strafe_*, turn_*, jump, stand, crouch, prone) are
inactive, and the cyberspace actions are inactive everywhere else. Each set
has its own bindings, so one input can be bound in both (for example the left
stick up is move_forward on foot and cyber_climb in cyberspace);
the Controller Options tab only reports a conflict between actions that can
be active at the same time. Free view, clicking and firing work in both.
Pitch and bank (cyber_climb, cyber_dive, cyber_bank_* and the four
diagonals) are proportional when bound to a thumbstick direction: pushing the
stick further turns faster, from 20% of full speed just past the trigger point
to 60% at full deflection. Bound to a button they run at full speed. Thrust and
roll are always full speed.
| Action key | Default input | Keyboard equivalent | Effect |
|---|---|---|---|
cyber_thrust |
right_thumbstick_up |
s |
Thrust forward |
cyber_climb |
left_thumbstick_up |
w |
Pitch up |
cyber_dive |
left_thumbstick_down |
x |
Pitch down |
cyber_bank_left |
left_thumbstick_left |
a |
Bank left |
cyber_bank_right |
left_thumbstick_right |
d |
Bank right |
cyber_roll_left |
right_thumbstick_left |
e |
Roll left |
cyber_roll_right |
right_thumbstick_right |
q |
Roll right |
cyber_climb_left |
none |
Keypad 7 | Pitch up and bank left |
cyber_climb_right |
none |
Keypad 9 | Pitch up and bank right |
cyber_dive_left |
none |
Keypad 1 | Pitch down and bank left |
cyber_dive_right |
none |
Keypad 3 | Pitch down and bank right |
mouse_click additionally behaves differently depending on the menu:
while the VR menu is open, its "click" is always whichever hand's trigger
is used to interact with the menu (so you can never lock yourself out of
this settings screen by rebinding mouse_click elsewhere); once the menu
is closed, both the on-screen keyboard (if it's still open) and the game
quad use your actual mouse_click binding. The default (right_a) is a
specific button on the right controller, so only the right hand's ray clicks;
bind the trigger identifier (see below) for either hand to click.
Hotkey actions
The engine's own keyboard hotkeys (HotKeyLookup[] in Prefs.c) plus
firing, as 35 bindable actions. stand (right_thumbstick_up), prone
(right_thumbstick_down), map_view (right_thumbstick_click) and
fire_weapon (right_trigger) are bound by default; the rest are unbound
(none). Each fires once per press, like pressing the key on a real
keyboard (there is no hold behavior).
| Action key | Keyboard equivalent | Effect |
|---|---|---|
stand |
t |
Stand up |
crouch |
g |
Crouch |
prone |
b |
Go prone |
pause |
p |
Pause the game |
full_view |
Ctrl+F | Switch to the fullscreen view loop |
normal_view |
Ctrl+D | Switch to the normal gameplay view |
map_view |
Ctrl+A | Switch to the automap view |
save_game |
Ctrl+S | Save the game |
load_game |
Ctrl+L | Load the game |
select_grenade |
Ctrl+' | Select the next grenade type |
arm_grenade |
Alt+' | Arm the selected grenade |
select_drug |
Ctrl+; | Select the next drug/patch |
use_drug |
Alt+; | Use the selected drug/patch |
toggle_online_help |
Ctrl+H | Toggle the online help overlay |
show_help |
Alt+O | Show the help screen |
toggle_music |
Ctrl+M | Toggle music |
quit |
Ctrl+Q | Quit the game - opt-in only; nothing binds this by default |
cycle_weapon_next |
Tab | Ready the next weapon |
cycle_weapon_prev |
Shift+Tab | Ready the previous weapon |
cycle_detail |
Ctrl+1 | Cycle the graphics detail level |
reload_weapon_primary |
Ctrl+Backspace | Reload the current weapon's primary ammo |
reload_weapon_secondary |
Alt+Backspace | Reload the current weapon's secondary ammo |
hud_color |
Alt+H | Cycle the HUD color scheme |
clear_fullscreen |
Backspace | Close a fullscreen view (e.g. a datapad or email) |
mfd_left_1-mfd_left_5 |
F1-F5 | Set the left MFD panel to Weapon/Item/Map/Target/Info |
mfd_right_1-mfd_right_5 |
F6-F10 | Set the right MFD panel to Weapon/Item/Map/Target/Info |
fire_weapon |
Enter | Fire the readied weapon at the cursor (the engine's default fire key). Clicking in the 3D view with mouse_click also fires; this is a separate button for it |
Not exposed (other HotKeyLookup[] entries):
keypad 0-keypad 9: a controller button per digit is not useful; use the on-screen keyboard.toggle_freelook(F): the free-view actions cover mouse look.- The 4
cheat_*entries: debug cheats. They are on the menu's Cheats tab instead, along with four switches for unlimited health, energy, ammo and items. Those are not saved to the config. mac_help(disabled) andaudiolog_cancel(its build flag is not defined): they are not real hotkeys in this build.toggle_opengl(Ctrl+G): the Video Options tab's Renderer buttons already toggle the same setting.- The 10 numeric hardware-item hotkeys (bio scan, fullscreen, 360 view, lantern, shield, infrared, nav unit, data reader, booster, jumpjets): their registered keys never match a real keypress in the engine.
Raw input identifiers
Every raw physical input available to bind an action to, with its generic
OpenXR interaction-profile component name and its Meta Quest Touch control
name. Two symmetric identifiers (trigger, squeeze) are also accepted, but
only for mouse_click/mouse_double_click: they mean "either hand's own
trigger/grip, whichever hand is pointing". Every other identifier below
refers to one specific control.
| Identifier | OpenXR component (Touch) | Meta Quest control |
|---|---|---|
left_trigger |
/user/hand/left/input/trigger/value |
Left Trigger |
right_trigger |
/user/hand/right/input/trigger/value |
Right Trigger |
left_squeeze |
/user/hand/left/input/squeeze/value |
Left Grip |
right_squeeze |
/user/hand/right/input/squeeze/value |
Right Grip |
left_x |
/user/hand/left/input/x/click |
X Button |
left_y |
/user/hand/left/input/y/click |
Y Button |
right_a |
/user/hand/right/input/a/click |
A Button |
right_b |
/user/hand/right/input/b/click |
B Button |
left_thumbstick_click |
/user/hand/left/input/thumbstick/click |
Left Thumbstick Click |
right_thumbstick_click |
/user/hand/right/input/thumbstick/click |
Right Thumbstick Click |
left_thumbstick_up/down/left/right |
/user/hand/left/input/thumbstick (digitized) |
Left Thumbstick, tilted |
right_thumbstick_up/down/left/right |
/user/hand/right/input/thumbstick (digitized) |
Right Thumbstick, tilted |
trigger |
either hand's trigger | Either Trigger (mouse_click/mouse_double_click only) |
squeeze |
either hand's grip | Either Grip (mouse_click/mouse_double_click only) |
none |
- | Unbound - the action never fires |
Menu button (left controller, Touch/Vive/generic alike) is not in this list - it always opens/closes the VR menu and can never be reassigned.
Other supported controllers: HTC Vive wands have no A/B/X/Y and no
analog grip - left_x/left_y/right_a/right_b simply never fire on
that profile, and left/right_squeeze are a plain click rather than
analog. Their trackpad stands in for the thumbstick identifiers above
(same digitization, just a trackpad instead of a stick). A bare-minimum
OpenXR controller (khr/simple_controller, used as a fallback when
neither Touch nor Vive bindings are suggested) only has left_trigger/
right_trigger (via its select/click component) and the menu button -
every other identifier above simply never fires on it, which is why the
Controller Options tab shows a note and hides irrelevant rows when this
profile is active.
Controller profile files (controller_profiles/)
The Controller Options tab shows a labeled illustration of whichever
controller is actually active, driven by res/controller_profiles/*.json
(bundled with the app, copied to /sdcard/questshock/controller_profiles/
on every launch, refreshing the known bundled files without touching any
extra ones you add there yourself). Each file:
{
"name": "Meta Quest Touch",
"interaction_profile": "/interaction_profiles/oculus/touch_controller",
"image": "touch.png",
"controls": [
{"id": "right_b", "label": "B", "anchor_x": 0.76, "anchor_y": 0.22}
]
}
interaction_profile- the OpenXR interaction profile path this file describes; matched against whichever one is actually bound at runtime. Falls back togeneric.jsonif nothing matches.image- filename (same folder) of the controller illustration.controls- one entry per labeled point on that illustration:idis a raw input identifier from the table above (thumbstick directions share oneleft_thumbstick/right_thumbstickentry, since they're the same physical stick);anchor_x/anchor_yare 0-1 fractions of the image for positioning that label's pointer line.
This is presentation data only - adding real support for a new controller still needs matching bindings in the app itself; a profile file with no corresponding code-level support just never gets matched.
Fatal startup error
If the OpenXR runtime rejects any of the three supported controller profiles' bindings at launch (an app bug, not something you did), a non-cancelable dialog explains this and exits - this should never happen in a normal build.