Knowledge base

Runtime access: RED4ext, redscript, CET and the XF bridge

Research in progress: some of this may be wrong. These notes record what XF Studio’s research has worked out so far about how Cyberpunk 2077 and its modding frameworks behave. Most of it comes from reading source code and game resources offline; not all of it has been confirmed in the running game, and claims graded hypothesis are unproven. If you spot a mistake or know more, please open an issue or suggest an edit.

Evidence grades

source
Read in engine/framework/tool source or decompiled data.
resource
Observed in extracted game or mod resources.
wiki
Documented in the Modding Docs, text or image.
runtime
Observed in the running game.
hypothesis
Not yet established.

Maturity: Draft. This page covers how each Cyberpunk 2077 mod type gets code into the running game, where each one logs, and how XF Studio's local bridge reaches the game. It is consolidated from source reading of RED4ext 1.30.0, RED4ext.SDK 1.0.0, redscript 0.5.31, CET 1.37.1, TweakXL 1.11.4 and psiberx's plugins, plus the decompiled 2.31 scripts and the offline build, self-test and tests of projects/xf-runtime-bridge. Claims seen in the game (the bridge's first in-game run, 26 September 2026) are marked runtime; their evidence is the test card's script-call check and first-session results, and later sessions' experiment pages (session 4, sessions 5 and 6). The rest wait for the test card. Crash fingerprints are on game crashes. The full citations, capability matrix and phase-2 plan are in the runtime bridge design.

1. Load order and entry points

Mod typeHow the game picks it upEntry pointGrade
RED4ext pluginFound at red4ext/plugins/<dir>/<name>.dll (at most one folder deep). Needs the exports Supports (API version), Query (name, version, runtime, SDK) and Main(Load/Unload). Refused if its runtime doesn't match the game build, or its SDK is older than 0.5.0-compat.Main(Load) runs before the game starts, and the plugin registers RTTI callbacks theresource RED4ext PluginSystem.cpp:89-154, 223-321
Global natives for scriptsCRTTISystem::AddPostRegisterCallback, then CGlobalFunction::Create with flags {isNative, isStatic}, then RegisterFunctionCalled by redscript as a declared native func, and by CET as Game.Name(...)source SDK examples/native_globals_redscript/Main.cpp:60-83; CET RTTIHelper.cpp:305-354
redscriptr6/scripts/**.reds, plus any path a plugin adds with sdk->scripts->Add (resolved against the plugin folder)Class bodies, @wrapMethod/@addMethod, ScriptableSystem callbackssource RED4ext v1/Funcs.cpp:123-139, ScriptCompilationSystem.cpp:101-134; redscript unit.rs
TweakXL datar6/tweaks/**.yaml|.yml|.tweak, imported right after TweakDB loadsnone (data)source TweakXL Environment.hpp:13-16, TweakService.cpp:25-59
CET modbin/x64/plugins/cyber_engine_tweaks/mods/<name>/init.luaregisterForEvent at top level only; Game and Observe are available from onInitsource CET ScriptStore.cpp:31-64, ScriptContext.cpp:46-65, 194-196

2. Rules learned the hard way (from source)

  • Declare natives outside a module. Redscript prefixes the module onto global natives (XFRuntimeBridge.XFBridge_Ping), but the plugin registers the plain name. Declare them in a module-less file, as Codeware does source redscript unit.rs:1023, symbol.rs:222-246.
  • Ship .reds that declare natives through the plugin (scripts->Add), not r6/scripts. A redscript compile error blocks every mod's scripts behind a message box, and a declared native without its DLL is such an error source scc/lib/src/lib.rs:86-94.
  • Call game and script functions from a plugin the way script code does: with a context and a caller frame. Script calls some natives as statics that the engine registers as member functions (every TweakDBInterface function, TDBID.ToStringDEBUG); the VM hands them the calling script's context, and with none the game dereferences null in rtti::Function::InternalCallNative and crashes (exe+0x1e28769 on 2.31; game crashes §2). RED4ext::ExecuteFunction(nullptr, …) starts a script with no context. Use Cyber Engine Tweaks' and RedLib's recipe instead: the engine's internal execute (SDK hash CBaseFunction_InternalExecute) with a caller frame passing the arguments as ExternalVar instructions, a dummy caller function, and a dummy entEntity context for statics runtime crash and probe, first in-game run; source CET RTTIHelper.cpp:765-812, Codeware lib/Red/TypeInfo/Invocation.hpp:9-107, SDK Functions-inl.hpp ExecuteNative; the fix itself [offline] (design §3.5).
  • A redscript class's static functions are global functions, named <Class>::<Name>;<ParamTypes>; CClass::GetFunction on the class finds nothing, so look them up among CRTTISystem::GetGlobalFunctions runtime.
  • Return false from a Running OnUpdate. RED4ext removes a state callback that returns true, even though the SDK comment says the Running result does not matter source StateSystem.cpp:128-160.
  • Include RED4ext/RED4ext.hpp before any RED4ext/Api header. The SDK becomes header-only only when Common.hpp is included first; otherwise CreateSemVer/CreateFileVer fail to link [offline].
  • CET cannot open network connections. Its sandbox exposes no sockets, HTTP, ffi or process spawning, and file access is confined to the mod folder source CET LuaSandbox.cpp:10-79, 152-161, 695-719. Any external link must be native.
  • CET API details: CET has spdlog.warning, not spdlog.warn; its Lua is LuaJIT (5.1), so use unpack; onDraw runs every frame even with the overlay closed source CET LuaSandbox.cpp:641-666, LuaVM.cpp:50-56.
  • Retail Log/LogChannel print nowhere unless CET (gamelog.log, scripting.log) or Red Hot Tools hooks them. Route script logs through a plugin native instead source CET LuaVM_Hooks.cpp:192-250.
  • Bound JSON nesting before handing it to nlohmann/json, and serialise with error_handler_t::replace.
    • Parsing and destruction are iterative in 3.12, but copying, comparing and dump() recurse. A 10 KB value nested 5,000 deep overflowed a 1 MB stack, and the game thread's 2 MB would take only about twice that [offline].
    • dump() throws type_error.316 on invalid UTF-8 unless given a replace or ignore handler source json 3.12.0 serializer::dump_escaped.
  • Don't FlushFileBuffers a named pipe before disconnecting. It waits until the client reads, which a client can simply not do [offline: held a stop 12 s]. DisconnectNamedPipe discards unread data, so give a client a short, bounded window to read a final reply instead [doc] Microsoft DisconnectNamedPipe.
  • node:net can't choose a pipe client's impersonation level or query the server PID. Use CreateFileW with SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION and GetNamedPipeServerProcessId (through bun:ffi in Bun), or .NET's NamedPipeClientStream with TokenImpersonationLevel.Identification [offline].
  • redscript-cli lint exits 0 even on errors, so parse its output for them. It also doesn't check @wrapMethod parameter lists on cb methods [offline].
  • Check a pre-2.3 RTTI dump against the 2.31 script bundle before relying on a signature. TimeSystem.SetPausedState has no parameters in 2.31, although the dump lists two source.
  • PhotoModeMenuListItem.ForceValue silently selects the first option for an option value it doesn't know. Validate against the option data the menu set up (OnSetupOptionSelector) before calling it source 2.31 photo-mode scripts.
  • A photo-mode expression is selected by its option data (the faceId), not its position in the menu source; the next session's expression checks R2 tell option data and menu position apart in game (test card).
  • Game scripts can't read the engine's render options (GameOptions such as Editor/Characters/Hair/GlobalLight/R): no script type exposes them. Cyber Engine Tweaks can, because it hooks the options' initialiser at start-up and keeps the list (GameOptions.Get(category, name), returning text, empty when unknown) source CET src/patches/OptionsPatch.cpp, src/scripting/GameOptions.cpp. The player's user settings (graphics, display) are script-readable through inkISystemRequestsHandler.GetUserSettings() → UserSettings.GetGroup(path).GetVars(false) and the ConfigVar… types; their groups and names are the game's r6/config/settings/platform/pc/options.json source 2.31.
  • A mod can add its own event to a game menu scenario with @addMethod(<scenario>) protected cb func On…() -> Bool; menu instance events are dispatched to the current scenario by name, as Mod Settings' pause-menu entry shows source Mod Settings Extensions/MenuScenario_PauseMenu.reds. From anywhere in script, MenuEventBlackboard.MenuEventToTrigger becomes such an event through the in-game menu controller, which is how the game's devices open menus (GameObject.TriggerMenuEvent, set to None first so the same event fires again) source 2.31 gameObject.script:2480-2490, inGameMenuGameController.script:76, 294.
  • PrintWindow with PW_RENDERFULLCONTENT returns black for a window that is entirely off-screen, while legacy WM_PRINT works there but not for DirectX content; capture the game on-screen [offline].
  • RED4ext loads DLLs only from red4ext/plugins/<dir>/ and that folder's direct files. It skips anything deeper and silently ignores a folder with no DLL. sdk->scripts->Add takes an absolute path or one relative to the calling plugin, so one plugin can add another folder's .reds to compilation. The proposed XF Core relies on both to gate its plugins' scripts source RED4ext v1.30.0 PluginSystem.cpp, ScriptCompilationSystem.cpp:101-134 (XF Core architecture §1).
  • A redscript compile error switches off every mod's scripts for that session. The game still starts, with none of them in effect source redscript v0.5.31 scc/lib/src/lib.rs.
  • CET runs each mod in its own context and keeps hotkey bindings per mod folder.
    • A mod reaches another mod's exported table with GetMod(name).
    • Bindings live in CET's own bindings.json, keyed by the mod's folder name and the hotkey id.
    • By default CET deletes a mod's bindings once its folder is gone, so renaming a CET mod's folder loses its players' keys source CET v1.37.1 ScriptStore.cpp, Paths.cpp:76, VKBindings.cpp.

3. Logging locations

LayerFile (MO2 writes new files to overwrite/)Bound
RED4ext and its pluginsred4ext/logs/red4ext-<ts>.log, red4ext/logs/<dll stem>-<ts>.logmax_file_size (10 MB) × max_files (5) per plugin, older ones pruned source RED4ext Utils.cpp:62-76, Config.hpp:24-27
psiberx plugins (ArchiveXL, TweakXL, Codeware)Their own red4ext/plugins/<Name>/<Name>-<ts>.log100 MiB × 2, 10 logs (ArchiveXL 5) source SpdlogProvider.hpp:61-63
redscript compilerr6/logs/redscript_rCURRENT.logdaily, 4 kept source scc/lib/src/lib.rs:242-250
CET mod…/mods/<name>/<name>.log5 MiB × 3, appended; info lines flushed only on warnings or at exit source CET Utils.cpp:105-118
XF bridge (all layers)red4ext/logs/xfruntimebridge-<ts>.log, sid= lvl= layer= cid= evt= linesRED4ext's bound

4. The XF bridge

  • Transport: a Windows named pipe owned by the RED4ext plugin, off unless [bridge] enabled = true. It is local only (PIPE_REJECT_REMOTE_CLIENTS, user-only DACL, first-instance flag, random name), and uses newline-delimited JSON, protocol 1.

  • Security boundary: the Windows user at medium integrity. Other users, remote machines, browsers and low-integrity or AppContainer processes can't reach the pipe. Same-user processes can, but they could already read or change the game's memory (design §4).

  • Security measures:

    • A 256-bit token per session, published in %LOCALAPPDATA%\XFStudio\runtime-bridge\session.json. The plugin has no override for this folder.
    • Only allowlisted methods exist, and writes are gated by allow_writes. bridge.kill is a control method, which only changes the bridge.
    • Input is bounded: 64 KiB per line and 32 levels of nesting, and only scalar id values are echoed. Deep nesting would otherwise overflow the stack when a value is copied or serialised [offline: a 5,000-deep request crashed the host before the fix].
    • A rate limit covers unauthenticated requests too. Five malformed or unauthenticated requests drop the connection. Every refusal is logged.
    • A kill switch covers the CET hotkey, bridge.kill and a KILL file. Stopping is bounded and never waits for a client to read.
    • Every request is logged with a correlation ID. No C++ exception can unwind into the game.
    • A named pipe was chosen over loopback HTTP because browsers and other network clients cannot reach it (design §3.1).
  • Threading: the pipe thread never touches the game. Game work is queued and drained from the plugin's Running OnUpdate (at most four tasks per tick), which runs on the game's main thread, in the same state tick as CET's Lua onUpdate [runtime; source CET GameHooks.cpp:93-104].

    • A task not started before its timeout is cancelled and never runs.
    • A task already running at the timeout gets a 1 s grace. If it still hasn't finished, the client gets timeout_after_start and the late completion is logged.
  • Game access: by RTTI name at run time, plus a check of the static flag, parameter types and return type before each call. A missing function fails with rtti_missing and a changed signature with rtti_signature. A function whose signature is unchanged but whose behaviour changed is not caught; the runtime pin is the main guard. Two routes are in use:

    • GetPlayer;GameInstance → entEntity.GetWorldPosition;
    • ScriptGameInstance.GetPhotoModeSystem → gamePhotoModeSystem.IsPhotoModeActive.

    Both worked in the game runtime. Phase-2 game access goes through our redscript actions layer instead (plugin → redscript statics by name), which lints against the installed 2.31 bundle. Every call, native or script, goes through one routine (plugin/ScriptCall.cpp) that calls with a caller frame and a context (rule above).

  • Clients: Bun (bun:ffi calls kernel32, [offline] Bun 1.4.2) and PowerShell 7 (NamedPipeClientStream). Both open the pipe at the Identification impersonation level. Both check the server PID against session.json before sending the token. node:net can do neither, which is why the Bun client doesn't use it.

5. Phase 2: commands, writes and captures

  • One catalogue, many frontends. Every command (name, plain text, JSON Schema input, permission class, undo note) is defined once in tools/api/catalogue.ts; the MCP server, the CLI's run and the session runner derive from it, so a command added once appears everywhere [offline]. Permission classes: read, write-photo, write-world, write-character, control.

  • Writes are gated twice. The tools can withhold classes (--read-only or --allow, never both), and the game refuses every write unless allow_writes = true in the plugin's config, which only the -writes build sets for the test profile, and then each class (photo, world, character) unless allow_write_classes lists it [offline].

  • Every write is reversible and recorded. It takes a save lock first, logs write.done … undo= and returns undo {method, params} for exactly what it changed (null when nothing changed or an earlier value is unknown); the kill switch undoes a freeze and keeps the save lock until a save is loaded runtime (test card S7), and restores a hidden photo-mode menu [unverified]; no queued write can run after it [offline].

  • Photo mode is driven through its own menu. GetMenuItem(key) and ForceValue reach OnAttributeUpdated, with values checked against the ranges the menu set up; keys come from four photo-mode mods' source and matched the first session's menu dump (design §3.2) runtime. Codeware's QuestsSystem.ExecuteNode with questOpenPhotoMode_NodeType opens only a restricted photo mode (first person, no V tab), so the full photo mode still needs the player's key runtime; the routes and why are in photo mode §2. The bridge's photo.open sends that key, the player's own binding, to the game window only: the one allowlisted input, approved for the test profile [offline].

  • Framing needs measuring, not fixed values. Where photo mode's drone camera spawns decides what fixed offsets show runtime. photo.frame reads V's head slot and the game's own projection (CameraSystem.ProjectPoint) and corrects V's placement and the field of view in a few steps; test-profile camera presets (attribute 23) can place the camera first [offline].

  • Character options change only in the open creator, through the option's own row (SetSelected…(info, index, true), as its arrows do, so the label follows; the bare ApplyChangeToOption the first session used left the row's label stale runtime). An option can be named by its internal name, its on-screen label or its UI slot (the one active option there, e.g. piercings_color), and a value by index, internal name or on-screen label (batch 3) [offline]. Confirm and Back go through the menu's own ConfirmCustomizedCharacter() and ConfirmBackConfirmation(), behind their own config switch (the -writes build only) source.

  • Opening the creator from normal play (cc.open, behind the same switch): the redscript layer checks the moment (phase gameplay; not in combat, a vehicle or a scene tier above 2; photo mode allowed; no combat, scene, tier or moving-platform save lock), takes the save lock and waits until the game reports saving locked. Then, since 0.4.2, it takes Character Customization Anywhere's route: it raises the pause menu's own event (OnOpenPauseMenu) through the menu-event blackboard, and a wrap of MenuScenario_PauseMenu.OnEnterScenario switches to MenuScenario_CharacterCustomizationMirror with a MorphMenuUserData carrying the edit tag, instead of opening the pause menu, only for the bridge's own request (photo mode §3.1) source. In game this route works away from any mirror runtime (29 September 2026, sessions 5 and 6; experiment 032):

    • from the street and a megabuilding corridor, with the edit tag NewGame (edit_mode: "new_game") and with the default HairDresser tag alike, the creator opened fully drawn, not busy, and cc.back closed it cleanly back to gameplay; the player's own Esc still opens the ordinary pause menu;
    • the game ignores the tag on this route: with HairDresser the menu still reported NewGame and made the same 860 of 1,209 options editable, skin tone and face shape included, which the mirror's tag doesn't allow (CC file chain);
    • it needs no Character Customization Anywhere: with CCA disabled both opens worked (session 6, T8).

    Session 4's half-open creator came from the older route (0.3.0–0.4.1), which raised an added event on the idle scenario without the pause menu, not from the edit tag.

  • The world clock and a freeze: SetGameTimeByHMS / SetGameTimeBySeconds, and time dilation 0 on the world and V as the mirror screen does source. The clock may also be set with the appearance screen open: the game's own time-skip menu sets it while its full-screen menu is open (timeSkipPopup.script:274), and the screen only freezes the world with time dilation source; whether the screen's light follows the new time is the next session's check [unverified].

  • Settings for the record: game.options.read (read) returns the user settings of the graphics and display groups and, through the CET layer (two natives, XFBridge_OptionsWanted and XFBridge_OptionsReport, polled four times a second; the plugin checks every answer), the engine's character render options [offline].

  • Captures are external (PrintWindow or the screen), cropped to named regions sized in window heights so they frame the same area on 16:9 and 21:9, saved at full resolution and returned downscaled with an exact area filter [offline].

APIGradeStatus
gameuiPhotoModeMenuController.GetMenuItem / PhotoModeMenuListItem.ForceValue / OnAttributeUpdatedruntimeUsed for camera, placement, lights (on/off, type and shadow [unverified]), expression, grain, aberration, camera preset
gameuiPhotoModeMenuController.OnExitConfirmed(Bool)runtimeUsed for photo.exit
Protected OnFadeVisibility(Float) through an added methodruntimeUsed for photo.hud.hide (hides the menu, not the cursor)
CursorGameController.ProcessCursorContext wrapped to play Hide while a flag is setsource 2.31; AMM overrides the same functionphoto.hud.hide's cursor [unverified]
CameraSystem.GetActiveCameraWorldTransform, GetActiveCameraForward/Right/Up, GetActiveCameraFOV, GetAspectRatio, ProjectPoint; SlotComponent.GetSlotTransform(n"Head") on the photo-mode stand-in (caught in PhotoModePlayerEntityComponent.SetupInventory)source 2.31; GetActiveCameraFOV equalled the photo-mode FOV runtimephoto.subject, photo.frame [unverified]
Codeware QuestsSystem.ExecuteNode + questOpenPhotoMode_NodeTypesource; opens a restricted photo mode runtimephoto.enter {route: "quest"}, research only
The player's photo-mode key (TogglePhotoMode; the UserSettings.json binding or IK_N), SendInput to the game windowresource game input config; [doc] Microsoftphoto.open [unverified]
gameuiICharacterCustomizationSystem.ApplyChangeToOption, GetUnitedOptionsruntimeUsed in the creator; now through the option's row (characterCreationBodyMorphOption.SetSelected…) [unverified]
ConfirmCustomizedCharacter (keeps: ReFinalizeState), ConfirmBackConfirmation (discards: CancelFinalizedStateUpdate) on the creator menusource 2.31cc.confirm, cc.back behind allow_creator_leave (the -writes build only) [unverified]
MenuEventBlackboard.MenuEventToTrigger → the pause menu's OnOpenPauseMenu → a wrapped MenuScenario_PauseMenu.OnEnterScenario → SwitchToScenario(n"MenuScenario_CharacterCustomizationMirror", MorphMenuUserData)source 2.31; runtime sessions 5–6cc.open behind allow_creator_leave; the edit tag is ignored (the menu reports NewGame)
The base creator menu's protected RequestCameraChange(slot) through an added method; slots from GetSlotName (UI_Eyes, UI_Hairs, …)source 2.31cc.page [unverified]
GameInstance.IsSavingLocked(game, out locks) and gameSaveLockReason (Combat, Scene, Tier, PlayerOnMovingPlatform, …); PlayerPuppet.IsInCombat, VehicleComponent.IsMountedToVehicle, the PSM blackboard's SceneTiersource 2.31cc.open's safe-moment check [unverified]
UserSettings.GetGroup(path).GetVars(false) and ConfigVarBool/Int/Float/Name/ListInt/ListFloat/ListString/ListName getterssource 2.31game.options.read's settings [unverified]
CET GameOptions.Get(category, name)source CET 1.37.1game.options.read's render options, through the CET layer [unverified]
TimeSystem.SetGameTimeByHMS, SetGameTimeBySeconds, SetTimeDilation, SetTimeDilationOnLocalPlayerZerosource 2.31Used for the clock (also with the appearance screen open, batch 3 [unverified]) and the freeze
TimeSystem.SetPausedState()source 2.31, no parametersNot used
SaveLocksManager.RequestSaveLockAdd/Removesource 2.31Held from the first write until a save is loaded (the kill switch keeps it); blocking autosaves in practice [unverified]
Autosave triggers: vendor, ripperdoc and perk exits (MenuUIUtils.RequestAutoSave), fast travel, legendary loot, drop points, quest checkpoints, the timed autosave settingsource 2.31Why sessions start with a manual save and end by loading it

6. What agents can and cannot do yet

  • Seen working in game: reads, the photo-mode camera, placement, light values, fade and expression, cc.apply, the clock, the freeze and the kill switch's restore, through the CLI and a session script (the MCP server shares the same catalogue and code path but was first registered with a client on 30 September 2026; its own layer is tested offline) runtime. Sessions 4–6 (28–29 September) added runtime (experiment 029, experiment 032): photo.open (only with the game window in front: keys posted to a background window don't reach it), photo.frame without drift, lights placed about V, photo mode's own clock and exposure, expressions by label and by table index, cc.open and cc.back from the street, cc.apply by label and through a row's slot, game.load and game.wait through a load, and spawning, lighting, turning and clearing XF Finish Showroom heads. Every other command works with the game in the background.
  • Found not to work in game: placing photo mode's camera directly (photo.camera.place: photo mode puts its camera back, held: false), and the vanilla-wardrobe writes while an outfit hides every area (worn clothing) runtime.
  • Built, not yet confirmed in game: light type and shadow, the cursor hidden with the menu, the clock with the creator open, game.options.read, the full-body camera preset, and the refusal paths that need a missing engine address (script_calls_unavailable); the session-7 ink demos (bridge 0.5.3) (design, test card).
  • Verified to exist (still untested in game):
    • reading photo-mode state, camera transform and FOV;
    • time of day, pause and dilation, teleport;
    • weather (with Codeware);
    • listing and loading saves by ID;
    • the character-customisation system calls;
    • quitting through ExitGame.
  • No API found:
    • taking a game screenshot to a chosen file;
    • opening the full photo mode without input (Codeware's quest-node route opens a restricted one; photo mode);
    • saving to a chosen slot.
  • Driving V (design only): teleport with facing, the camera turned by the game's own look-at, navmesh paths, crouch, weapons, consumables, menus and the HUD's interaction choices all have script routes; walking with the game's locomotion, jumping, sprinting and choosing dialogue lines need input, and the retail build carries CD PROJEKT RED's functional-test input natives (FunctionalTestsGameSystem.FakeInput*, parameters stripped) as the lead for input without window focus (player control, design).
  • Capture paths: capture is external for now (window capture after post-processing). The lossless, before-effects route is an optional ReShade add-on (6.7.x headers, full add-on build only), which must never change the user's preset.

Details and citations: design §7.

Open questions

  1. Are script calls from RED4ext's Running OnUpdate, made with a context, safe during loading screens? No: one made after the scripting layer detached for a load crashed the game (session 5); the bridge now refuses script calls from the detach until the loaded session's player attaches, and the same load passed in session 6 runtime (game crashes §2.3). Main menu, gameplay and photo mode are safe with a context runtime (script-call check).
  2. Can CET call a redscript class that a plugin added with scripts->Add, as the Lua global Module_Class? Yes (XFRuntimeBridge_XFBridgeQuery) runtime.
  3. When does a ScriptableSystem's OnAttach first run: at the main menu or on the first save load?
  4. Do the RTTI names from the pre-2.3 dump still match on 2.31? Settle this with a fresh RTTIDumper run.
  5. Does window capture return the game image in its fullscreen mode, or only in borderless windowed mode?
  6. Does the Codeware quest node open photo mode outside a quest? Yes, but restricted to first person runtime. Does the save lock hold off autosaves?
  7. Do the photo-mode keys from mod source match this install? Yes; light on/off is 44, chromatic aberration 13, grain 25 runtime (photo mode §8.1).
  8. Does GameInstance.IsSavingLocked report the bridge's save lock within the three ticks cc.open waits (the lock is a queued scriptable-system request)? If not, cc.open answers save_lock_not_held and a retry works.
  9. Which screen space does CameraSystem.ProjectPoint answer in, and is the photo-mode camera the active camera for it? photo.frame detects the space at run time; the next session records it.

Runtime bridge design · Player control · Test card · Photo mode and the creator from script · Mod loading · Game crashes · Validation · Toolchain · The game's UI (ink)

Corrections and credit

This page is generated from knowledge/runtime-access.md in the repository. If something here is wrong or missing, please open an issue or suggest an edit; links to sources, captures or tests help most.

It builds on the Cyberpunk 2077 modding community’s work. The inline citations link to the wiki pages, mods, tools and repositories each finding came from, and the credits acknowledge everyone we learned from. Citing a source does not mean its authors endorse these notes.