Knowledge base

The game's UI system (ink): building, injecting and drawing XF UI in game

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. How Cyberpunk 2077 draws its UI (ink), how a mod builds widgets from script, matches the game's look, takes input, adds to every standard screen, draws over the world, adds map pins and markers, and renders 3D into UI; plus the answer on an XF-owned ImGui overlay (§8, from the 30 September feasibility study). Consolidated on 29 September 2026 from the decompiled 2.31 script bundle (r6/cache/final.redscripts, SHA-256 2119046f…ee86, redscript-cli 0.5.31 decompile -f into a private scratch folder; paths below are that tree's), the source of Codeware v1.20.4 (613a1cb8), Red Hot Tools (b4d3415), Equipment-EX (3208ff4), Cyber Engine Tweaks 1.37.1 (9a8522f), Mod Settings (e904815) and Let There Be Flight (6d7c1b7), and the installed script files of mods that inject UI (Dark Future, Vehicle Navigation System, Sheng Noods Ramenshop; read-only in the MO2 mods folder). No claim on this page has been seen in game yet except where marked runtime; the XF Runtime Bridge 0.5.3 demos (§9) are the first in-game test, on the ink demo rows (group F of the session-7 checks). The route map for the in-game ambitions is in-game UI design. Grades follow the knowledge rules.

1. The short answers

QuestionAnswerGrade
What is ink?The engine's retained-mode UI: a tree of widgets (inkWidget: canvases, panels, text, images, shapes, video) in layers (inkHUDLayer, inkMenuLayer, inkGameNotificationsLayer, inkWorldLayer, inkLoadingLayer, inkWatermarksLayer and more), driven by game controllers (inkGameController, one per screen or HUD module) and logic controllers (inkLogicController, per widget). Layouts normally come from .inkwidget library resources; styles from .inkstyle; fonts from .inkfontfamily; images from .inkatlassource 2.31 core/ui/baseWidgets/abstractWidgets.script; layer classes in Codeware scripts/Base/Imports; Red Hot Tools src/App/UI/InkWidgetCollector.cpp
Can a mod build widgets from script alone?Yes, in vanilla redscript: new inkText(), new inkCanvas()… then Reparent(parent) or AddChildWidget. What vanilla lacks is (a) a way to reach a layer or screen you don't already hook, and (b) initialising logic controllers attached to script-built widgets: Codeware adds both (GameInstance.GetInkSystem(), inkComponent, inkCustomController, and a hook after Reparent/AddChild from script that attaches the widget tree to the game systems)source 2.31 abstractWidgets.script:225-236; Codeware src/App/UI/WidgetBuildingService.hpp, scripts/UI/Core/*, wiki "User Interface"
How do we match the game's look?Its font family base\gameplay\gui\fonts\raj\raj.inkfontfamily (styles Regular, Medium, Semi-Bold, Bold) and its colour tokens: SetStyle(r"base\gameplay\gui\common\main_colors.inkstyle") then BindProperty(n"tintColor", n"MainColors.Red") (also Blue, ActiveRed, MildRed, PanelRed, Green, Yellow, Grey, White…; fontSize tokens MainColors.ReadableSmall and friends). Bound properties follow the game's own themesource 2.31 scripts (e.g. BindProperty(n"tintColor", n"MainColors.Red") 14 times) and installed mods (main_colors 251 uses, raj 184)
How does a HUD element attach?The HUD layer spawns its HUD entries (each a window with a slot, from the HUD's entry list) with their game controllers (inkHUDGameController subclasses). Mods either wrap an existing HUD controller and add widgets under its root, or (Codeware) add widgets to the HUD layer's own Rootsource Red Hot Tools InkWidgetCollector.cpp:207-221 (hudLayer->entries, hudEntryName, slotParams.slotID); Dark Future DFHUDSystem.reds, VNS VNS.reds
World-space UI?Two kinds. Render-to-texture on a mesh: a worlduiWidgetComponent on an entity (terminals, screens, ads, vehicle dashboards) hosts an ink game controller whose window is drawn onto the mesh's UI material. Screen-space, world-anchored: projected HUD controllers (inkProjectedHUDGameController: nameplates, damage digits, mappins) register world points and the engine places widgets every framesource 2.31 core/components/uiComponents.script, orphans.script inkProjectedHUDGameController, inkScreenProjectionData
Custom map pins?MappinSystem.RegisterMappin(MappinData, position) or RegisterMappinWithObject(data, object, slot, offset) with type Mappins.DefaultStaticMappin and a variant; the variant decides the map filter and icon. A custom icon means wrapping the mappin controllers' UpdateIcon and replacing the icon (an atlas of your own, or ink-drawn)source 2.31 dropPointSystem.script:380-389, mappin scripts; Sheng Noods Ramenshop's mappinShengNoodsRamen.reds
3D into UI?Preview game controllers render a puppet or item from a separate preview scene into a widget: inkInventoryPuppetPreviewGameController (the paperdoll), inkItemPreviewGameController, character-creation and gender-selection puppets, inkEntityPreviewGameController, the 3D world map (inkWorldMapPreviewGameController). The puppet is an ordinary entity a script can reach (gameuiInGameMenuGameController.OnPuppetReady, Codeware PlayerSystem.GetInventoryPuppet())source 2.31 core/ui/baseControllers/entityPreviewGameController.script; Equipment-EX scripts/Overrides/gameuiInGameMenuGameController.reds
An own ImGui overlay?Feasible and recommended for development tools: chain a hook on the engine's own present (GpuApi::Present, where CET draws), submit on the engine's direct queue from the SDK's device data, and take the mouse through a window-procedure subclass in a hotkey interact mode that yields to CET. Not built; about two weeks for three stages (§8)source CET src/d3d12/D3D12_Hooks.cpp:11-111, src/window/window.cpp:11-95; RED4ext SDK GpuApi/DeviceData.hpp

2. Widgets from script

2.1 The widget classes

ClassKindNotesGrade
inkCanvascompoundabsolute placement by anchor, anchor point, margin and translationsource orphans.script:35976
inkVerticalPanel, inkHorizontalPanel, inkFlex, inkUniformGrid, inkScrollArea (Codeware)compoundstacking layouts; SetChildMargin, SetFitToContentsource
inkTextleafSetText, SetLocalizedText(locKey), SetFontFamily(path, style), SetFontStyle, SetFontSize, SetLetterCase, SetTracking, SetWrappingAtPosition, EnableAutoScroll; Codeware adds wrapping, overflow, justification and line height setterssource core/ui/baseWidgets/textWidget.script; Codeware scripts/UI/Core/inkText.reds
inkImageleafSetAtlasResource(atlas) + SetTexturePart(part), tile and mirror modes, RequestSetImage(iconReference) (async), SetDynamicTextureUVsource orphans.script:31606-31657
inkRectangle, inkCircle, inkShapeleaf shapesa tinted rectangle is the usual panel background, divider and diamond; inkShape takes vertex lists (Codeware documents its fields)source
inkMaskleafmasks siblings by texture or by a dynamic widgetsource orphans.script:51050
inkVideoleafBink (.bk2): SetVideoPath, Play, Stop, SetLoop, JumpToTime, SetAudioEventsource orphans.script:44099-44129
inkVectorGraphicWidget, inkLinePattern, inkBorder, inkCacheWidgetleaf / compoundvector art, patterned lines, borders; the cache widget renders its subtree once into a texture (cheap for static, complex panels)source

Every widget: SetName, SetVisible, SetOpacity, SetTintColor(HDRColor), SetAnchor(inkEAnchor), SetAnchorPoint, SetMargin, SetPadding, SetSize, SetHAlign/VAlign, SetScale, SetRotation, SetShear, SetTranslation, SetRenderTransformPivot, SetInteractive, SetStyle, BindProperty, SetState, PlayAnimation(WithOptions), RegisterToCallback(event, object, function), SetEffectEnabled/SetEffectParamValue (per-widget effects such as blur, glitch and colour correction), and Reparent(parent, opt index) source abstractWidgets.script:2-227. Compound widgets add AddChild(typeName), AddChildWidget, GetWidgetByPathName, RemoveChild, ReorderChild, SetChildOrder source :230-276.

Library widgets. A controller spawns items from its own library (SpawnFromLocal) or another .inkwidget (SpawnFromExternal, async variants), which is how every game screen is built source core/ui/baseControllers/widgetController.script. ArchiveXL's ink-spawner extension (and Codeware's) accept item:Controller to attach a script controller class to the spawned root source (terminals §4). A mod that ships its own .inkwidget gets designer layouts; one that builds in script needs no archive (the XF demos build in script).

2.2 Reaching layers and screens

  • Vanilla: only from inside a controller you already hook (this.GetRootCompoundWidget() in a wrapped OnInitialize), or through objects a system hands you source.
  • Codeware: GameInstance.GetInkSystem().GetLayers(), GetLayer(n"inkHUDLayer"), GetWorldWidgets(); a layer wrapper's GetVirtualWindow(), GetGameController(), GetGameControllers() source Codeware scripts/UI/inkSystem.reds, src/App/UI/inkSystem.hpp. Its wiki's own example reparents a text straight into the HUD layer's virtual window source Codeware wiki/Home.md "Layers and windows".
  • Installed practice: Dark Future and Vehicle Navigation System reparent a 3840×2160 inkCanvas into GetLayer(n"inkHUDLayer").GetVirtualWindow().GetWidgetByPathName(n"Root"), check first whether their named canvas is already there (a layer outlives a session), and scale it with Codeware's VirtualResolutionWatcher source DFHUDSystem.reds (CreateFullScreenSlot), VNS.reds (MinimapContainerController.InitializePlayer wrap). The watcher scales by screen size / 3840×2160 per axis, which implies the HUD root is in screen pixels [hypothesis from runtime-proven mods]; per-axis scaling squashes text on ultrawide screens, so the XF overlay scales uniformly by height (2160 units high, as wide as the aspect makes it).

2.3 Styles, fonts and colours

  • SetStyle(styleResPath) attaches an .inkstyle; BindProperty(propertyName, styleVariable) binds tintColor, fontSize, fontStyle/fontWeight or opacity to a named value in it source abstractWidgets.script:24, 221.
  • The shared style sheets: base\gameplay\gui\common\main_colors.inkstyle (the palette), base\gameplay\gui\fullscreen\fullscreen_main_colors.inkstyle, …\common\dialogs_popups.inkstyle, …\common\tooltip\tooltip_menu.inkstyle and tooltip_hud.inkstyle, …\fullscreen\hub_menu\hub_menu_style.inkstyle, …\common\components\toggles_style.inkstyle and slots_style.inkstyle, …\widgets\notifications\notification.inkstyle source path counts in the 2.31 scripts and installed mods.
  • The HUD's grammar in practice: headers in MainColors.Red (upper case, Semi-Bold), body text in MainColors.Blue (the cyan), warnings ActiveRed, success Green/ActiveGreen, prompts Yellow, dark translucent panels with a thin red edge source token use in 2.31 scripts; the look itself hypothesis until compared with captures.
  • Codeware keeps a few literal HDR colours (ThemeColors.ElectricBlue, Bittersweet, Dandelion…) and recommends binding main_colors.inkstyle instead source scripts/UI/Styles/ThemeColors.reds. HDR colours go above 1.0 for glow (the game's red is about 1.176, 0.381, 0.348).

2.4 Animation

inkAnimDef holds interpolators (inkAnimTransparency, inkAnimTranslation, inkAnimScale, inkAnimRotation, inkAnimColor, inkAnimSize, inkAnimMargin, inkAnimPadding, inkAnimShear, inkAnimTextInterpolator, inkAnimEffect), each with duration, delay, easing (SetType, SetMode) and direction; PlayAnimationWithOptions(def, inkAnimOptions) takes loop type (Cycle, PingPong), loop count or infinite, markers and whether it follows time dilation; the proxy's RegisterToCallback(inkanimEventType.OnFinish | OnEndLoop | …, object, function) calls back source orphans.script:6512-6524, 31496-31589, 35211-35229. Library animations play by name (PlayLibraryAnimation). An endless looping animation is a UI-clock tick: the game itself drives text scrolling and minigame buffers from OnEndLoop source core/ui/textScrollingAnimController.script:91, networkMinigameBufferController.script:57. With dependsOnTimeDilation false it keeps running while the world is paused [source; in game hypothesis]. The XF overlay ticks this way (about 30 Hz), which ties the tick's lifetime to its widget.

2.5 Input on ink

  • Pointer and buttons: a widget set interactive receives OnHoverOver, OnHoverOut, OnPress, OnRelease (with inkPointerEvent.IsAction(n"click")), OnAxis through RegisterToCallback source e.g. pauseMenu.script:251-260. Game controllers hear everything through RegisterToGlobalInputCallback(n"OnPostOnRelease" | n"OnPostOnPress" | n"OnPostOnAxis" | n"OnPostOnRelative", …) (66, 22, 21 and 14 uses in 2.31) source.
  • The cursor belongs to the layer: a menu layer shows the game's cursor (CursorGameController, whose ProcessCursorContext picks the cursor's look per context) source cyberpunk/UI/cursors/cursorGameController.script:111, 343. The HUD layer normally has none; input there comes as gameplay actions. Red Hot Tools makes the topmost layer (inkWatermarksLayer) interactive with its own game cursor (spawned from base\gameplay\gui\widgets\cursors\default_cursor.inkwidget, input context UINotifications) to pick widgets under the pointer source InkWidgetCollector.cpp:462-496. That is a native route to an ink "interact mode" without opening a menu; it needs RED4ext.
  • Keys: Codeware's callback system delivers raw Input/Key and Input/Axis events, and Input Loader registers new input actions from XML; a script listens for input actions with GameObject.RegisterInputListener(listener, name) source Codeware wiki/Home.md (callback events), scripts/Callback/Events/KeyInputEvent.reds; 2.31 core/entity/gameObject.script:152.
  • Text entry: Codeware's TextInput/HubTextInput components source scripts/UI/TextInput.

2.6 Performance

  • Ink is retained: a static widget costs layout and draw only when invalidated; text changes re-shape one string. Changing text, tint or translation every frame on a few dozen widgets is what the game's own nameplates do [source; cost unmeasured].
  • Controllers spawned from libraries load their .inkwidget resources (async where the Async spawn is used); script-built widgets load nothing but fonts and atlases already resident source.
  • inkCacheWidget renders a static subtree once; the world map, scanner and damage digits use projected controllers so the engine does the per-frame placement source.
  • The XF overlay: one native call (XFBridge_Hud, a string of a few hundred bytes) every sixth tick (about 5 Hz), and for nameplates three ProjectPoint calls plus one per visible head per tick [offline design; cost in game unmeasured].

3. Injecting into the standard screens

The general pattern: @wrapMethod the screen's controller OnInitialize (or the function that fills it), call the game first, then add widgets under this.GetRootCompoundWidget() or a named container; add a scenario event with @addMethod(MenuScenario_…) protected cb func On…() to switch to your own menu; tear down in OnUninitialize. Controllers below are source 2.31 unless noted; the "Mods" column names installed or cloned mods that hook them source.

ScreenController(s)Attach point and hookRisksMods doing it
HUD (always-on modules)inkHUDGameController subclasses: healthbarWidgetGameController, MinimapContainerController, QuestTrackerGameController, hudCarController, buffListGameController, OnscreenMessageGameController…Codeware: the HUD layer's Root; vanilla: a wrapped HUD controller's root. Forced visibility of HUD entries: UISystem.SetHudEntryForcedVisibility(entryName, worlduiEntryVisibility)Entries respawn per session; widgets under Root may outlive the session (check by name first); HUD hidden in menus, photo mode, scenesDark Future (DFHUDSystem.reds), VNS (wraps MinimapContainerController.InitializePlayer), Let There Be Flight (hudCarController value wraps)
Projected HUD (world-anchored)inkProjectedHUDGameController: NpcNameplateGameController, DamageDigitsGameController, MappinsContainerController, BaseSubtitlesGameController, scannerBorderGameControllerRegisterScreenProjection(inkScreenProjectionData) (entity + slot, or staticWorldPosition, fixedWorldOffset, userData) inside such a controller; OnScreenProjectionUpdate places the widgetsRegisterScreenProjection is protected: only code in the controller's class (an @addMethod) can call it, and the controller's update handler must be taught to skip oursnone found
Pause menuPauseMenuGameController (gameuiMenuItemListGameController), scenario MenuScenario_PauseMenuwrap PopulateMenuItemList and call AddMenuItem(label, n"OnXF…"); handle it with @addMethod(MenuScenario_PauseMenu) protected cb func OnXF…() → SwitchMenu(n"…") or your own actionMod Settings replaces PopulateMenuItemList (a wrap still composes on top); order of added items across modsMod Settings (Extensions/PauseMenuGameController.reds, MenuScenario_PauseMenu.reds); Character Customization Anywhere (the pause menu's event)
Main menuSingleplayerMenuGameController (MainMenuGameController), MenuScenario_SingleplayerMenuas the pause menupre-game: no player, no game systems; scripts may still runMod Settings (MenuScenario_SingleplayerMenu.reds)
Hub menu (the inventory/map/journal tabs)MenuHubGameController, HubMenuUtilsCodeware's HubMenuButton/HubLinkButton components, or wraps of the hub's button setupthe hub's layout is fixed width; more tabs may overflowEquipment-EX, Virtual Atelier (via Browser Extension)
MapWorldMapMenuGameController, BaseWorldMapMappinController, WorldMapTooltipControllerwrap UpdateIcon per pin, WorldMapTooltipController.SetData for the tooltip; the 3D map is inkWorldMapPreviewGameController (camera modes, zoom, yaw, pitch)the map's fast-travel action keys on FastTravelVariant (worldMap.script:811, 1041): never give a custom pin that variantSheng Noods Ramenshop, Virtual Atelier Delivery, Ripperdoc Vendor UI Enhancements (tooltips); 8 mods wrap WorldMapTooltipController.SetData (mod survey)
Inventory and paperdollgameuiInventoryGameController, BackpackMainGameController, inkInventoryPuppetPreviewGameController; the puppet comes from gameuiInGameMenuGameController.OnPuppetReady(sceneName, puppet) ("inventory")wraps of OnInitialize/refresh functions; the puppet is a normal entity for equipment and component changesEquipment-EX replaces several inventory functions; order mattersEquipment-EX, Revised Backpack, Stash and Backpack Search, Inventory Zoom
Character creatorcharacterCreationBodyMorphMenu, characterCreationSummaryMenu (BaseCharacterCreationController), inkCharacterCreationPuppetPreviewGameController; scenarios MenuScenario_CharacterCustomization*wraps of the menu's option setup and row controllers (photo mode §3, creator presentation)CCXL and Character Customization Anywhere hook the same menus; the creator is a pre-game-style menu with its own puppetCCXL (ArchiveXL), Character Customization Anywhere; the XF bridge (cc.*)
SettingsSettingsMainGameController (gameuiSettingsMenuGameController), SettingsCategoryController, SettingsSelectorController*Mod Settings wraps SettingsCategoryController.Setup and the selectors; Native Settings UI does it from CETthe two frameworks touch the same controllers (conflict list)Mod Settings, Native Settings UI, Redscript Configuration Framework
Phone, calls and messagesNewHudPhoneGameController, HudPhoneGameController, MessengerGameController, PhoneMessageNotificationsGameController, JournalNotificationQueuewraps of the phone's contact and message population; messages themselves are journal entries (terminals §3.3)phone state is quest-driven; adding contacts needs journal dataNightlyNow Core, Dark Future (phone icon checks)
JournalquestLogGameController, CodexGameController, ShardsMenuGameControllerwraps of entry population; entries are journal resources (ArchiveXL merges .journal)quest facts and journal state persist in savesquest mods through ArchiveXL
Photo modegameuiPhotoModeMenuController, PhotoModeMenuListItem, gameuiPhotoModeStickersControllerAddMenuItem(label, attributeKey, page, isAdditional), the setup callbacks (photo mode)many photo-mode mods; the bridge already drives itPhoto Mode Ex, Equipment-EX, Customisable Photo Mode UI
Vendors, crafting, perks, ripperdoc, wardrobe, time skipFullscreenVendorGameController, CraftingMainGameController, NewPerksCategoriesGameController, RipperDocGameController, WardrobeUIGameController, TimeskipGameControllerwraps of their setupfive mods wrap TimeskipGameController.OnInitialize; Equipment-EX replaces the wardrobeImmersive Timeskip, Enhanced Craft, Cyberware-EX, Thread Locker
Popups and notificationsPopupsManager, gameuiGenericNotificationGameController, Codeware InGamePopup/InMenuPopup/GenericMessageNotificationCodeware's popup components on the notifications layermodal input captureVirtual Atelier Delivery, Redscript Configuration Framework
Loading screensinkLoadingLayer, InitialLoadingScreenLogicControllerthe loading layer (Codeware can reach it)scripts are detaching and attaching: never touch game systems here (game crashes §2.3)none found

Rule for every injected screen [source, from the bridge's crash]: UI code runs on the main thread inside the screen's lifecycle, so it is safe to call game systems while the screen is live, but a callback that outlives its screen (a delay callback, an animation proxy, a listener) must be released in OnUninitialize/OnDetach.

4. The HUD in detail

  • Layers: the HUD layer (inkHUDLayer, a full-screen layer) holds entries, each a window with a name and a slot (hudEntryName, slotParams.slotID), spawned with its inkHUDGameController source Red Hot Tools InkWidgetCollector.cpp:207-221. UISystem.SetHudEntryForcedVisibility(entryName, visibility) forces an entry shown or hidden source core/systems/uiSystem.script:126.
  • Showing and hiding: inkHUDGameController.ShowRequest/HideRequest with intro and outro animations (GetIntroAnimation, GetOutroAnimation) and context changes (combat, vehicles, scenes) source core/ui/baseControllers/hudGameController.script:20-108. The whole HUD hides in menus, photo mode and the creator.
  • Positioning: HUD entries sit in safe-zone slots (the HUD safe-zone editor, gameuiHudSafezonesEditorGameController); a mod's widget under Root is placed by anchor and margin in the root's units (screen pixels hypothesis, §2.2).
  • An XF HUD module can be: widgets under Root (built, Demo A); a wrapped vanilla entry's root (couples to that entry's visibility); or a true HUD entry (needs the HUD's entry list and an .inkwidget; not researched further).

5. World-space ink

5.1 Render-to-texture on a surface (terminals, screens, holograms)

  • An entity carries a worlduiWidgetComponent (IWorldWidgetComponent): widgetResource (an .inkwidget library), itemNameToSpawn, sceneWidgetProperties, meshTargetBinding (the mesh and its UI material the window is drawn onto), screenAreaMultiplier, spawnDistanceOverride, tintColor, glitchValue, isEnabled; script reaches the spawned tree through GetWidget() and GetGameController() source 2.31 core/components/uiComponents.script:2-63, Codeware scripts/Base/Addons/IWorldWidgetComponent.reds, worlduiWidgetComponent.reds. Devices request it by name in OnRequestComponents (n"ui") source e.g. apartmentScreen.script:7, arcadeMachine.script:32. How a computer builds its screen and takes cursor input: terminals §2.
  • The world layer (inkWorldLayer) keeps every such component's window; Codeware's GetWorldWidgets() lists their controllers source Codeware src/App/UI/inkSystem.hpp:40-60.
  • Adding one to our own entity: either in the entity template (an archive change: the showroom build would add a component bound to a flat plate mesh with a UI material and a small .inkwidget), or at assembly time through Codeware's entity events (Entity/Assemble → AddComponent) source Codeware wiki "Components" ("add components at the right moment, for example when entity is assembled"). No mod was found doing the second for a world widget; Let There Be Flight's attempt is commented out source src/redscript/Extensions/VehicleObject.reds:8-20. Untested, the route of the pedestal labels' next step (design).

5.2 World-anchored, screen-space (nameplates, floating labels)

  • Engine projection: a projected HUD controller registers inkScreenProjectionData (an entity and slot, or a static world position, a fixed world offset, distance adjustment) and gets OnScreenProjectionUpdate with the placed projections; ApplyProjectionMarginOnWidget positions a widget source orphans.script:44765-44775, 51626-51647; used by NPC nameplates, damage digits and mappins.
  • Script projection: CameraSystem.ProjectPoint(worldPoint) with the active camera's transform, forward, right and up source orphans.script:26738-26740. Which screen space it answers in is not settled, so the caller detects it from a point straight ahead (the bridge's photo.frame does, tools/api/framing.ts) source. This is what Demo B uses: a tick behind the game's own projection by at most one UI tick [hypothesis: visible lag when the camera moves fast].

6. Map pins, markers and the compass

  • Registering: MappinData { mappinType: TweakDBID, variant: gamedataMappinVariant, active, debugCaption, visibleThroughWalls, scriptData }; MappinSystem.RegisterMappin(data, position), RegisterMappinWithObject(data, object, opt slotName, opt offset), SetMappinPosition, SetMappinActive, ChangeMappinVariant, UnregisterMappin, GetMappins(targetType) source orphans.script:17783-17856. The game's drop points use Mappins.DropPointStaticMappin; mods use Mappins.DefaultStaticMappin source dropPointSystem.script:384-387; AMM Modules/util.lua (a FastTravelVariant marker over a spawned object), Sheng Noods Ramenshop.
  • What a pin shows: its variant maps to an icon part (MappinUIUtils.MappinToTexturePart), a colour token (MappinToColor), a label key and a world-map filter group (GetFilterGroup(variant)) source cyberpunk/UI/mappins/mappinUtils.script. IMappin.GetDisplayName() answers the data's debugCaption for a runtime pin [source: mods rely on it; in game hypothesis for XF].
  • Where it shows: world map (BaseWorldMapMappinController), minimap (MinimapPOIMappinController, MinimapDeviceMappinController), in the world (QuestMappinController, GameplayMappinController, which clamp to the screen edge, scale with distance, show distance and a name line), and tooltips (WorldMapTooltipController.SetData) source. The compass ribbon is the minimap's container; tracked pins drive the GPS path (GetManuallyTrackedMappinID, SetMappinTrackingAlternative) source.
  • Custom icons: each controller's UpdateIcon sets the icon from the variant; a wrap that runs after it can swap the atlas and part (a mod's own .inkatlas) or hide the icon and draw ink instead; identify your pins by GetNewMappinID() or by a caption convention source Sheng Noods Ramenshop's wraps (its Nexus page credits Hepero for the map-icon script).
  • Persistence: runtime pins belong to the session's mappin system [hypothesis: gone after a load; session 7 checks]; points of interest keep saved phase and variant (GetPointOfInterestMappinSavedState) source.
  • Quest markers come from journal objectives (GetMappinFromObjective, quest mappins with phases); a real XF quest marker means a journal entry and quest phase (ArchiveXL), not a runtime pin source.

7. 3D in UI, video and render targets

  • Paperdoll: inkInventoryPuppetPreviewGameController (an inkPuppetPreviewGameController: GetGamePuppet(), rotation by axis input, AnimFeature_Paperdoll for its pose) renders the puppet the in-game menu spawns for the "inventory" scene; gameuiInGameMenuGameController.OnPuppetReady(sceneName, puppet) hands it over and Equipment-EX dresses it there source entityPreviewGameController.script:2-80; Equipment-EX. The camera setup comes from gameuiPuppetPreview_SetCameraSetupEvent source Codeware addon of that name.
  • Other 3D-in-UI: item previews (inkItemPreviewGameController.PreviewItem(itemID): weapons and clothes), crafting garment previews, the character creator's and gender selection's puppets, inkEntityPreviewGameController (vehicles), the 3D world map, the tarot preview source.
  • A holographic V with per-limb damage [hypothesis, the route]: the paperdoll puppet is an entity, so its components can be changed after OnPuppetReady (Codeware GetComponents, meshAppearance + LoadAppearance(), or chunkMask); a hologram look needs mesh appearances with a hologram material (an XF archive adding appearances to V's body meshes through ArchiveXL, or extra mesh components per limb added to the puppet only) and per-limb states as appearance switches, since runtime material parameters are per resource, not per instance. The preview scene's lighting and post-process are its own, which suits an emissive hologram. First test: switch the puppet's body appearance in the inventory and confirm the gameplay V is unchanged.
  • Video: inkVideo plays Bink (.bk2) in UI; BinkComponent plays video in the world; journal briefings and internet pages name videos source orphans.script:44099-44129; Codeware addons BinkComponent.reds, JournalBriefingVideoSection.reds.
  • Dynamic images: inkImage.RequestSetImage(inkIconReference) loads icons asynchronously; the gallery shows screenshots through its own preview controller; inkCacheWidget renders ink into a cached texture. No script-reachable way to render an arbitrary camera into a widget was found [source; searched galleryfullpreview.script, preview controllers].

8. An XF ImGui host beside CET

The full study, with the component diagram, prototype plan and test card, is ImGui overlay feasibility (30 September 2026). Grades as above; [external] marks a public issue report or vendor document.

QuestionAnswerGrade
Where does CET draw?Inside the engine's own present wrapper, GpuApi::Present (address-library hash 2468877568), hooked with MinHook from DllMain. It records ImGui into its own command list and submits it on the engine's direct queue, before calling the original; it never hooks DXGI or ExecuteCommandLists. It also hooks GpuApi::ResizeBackbuffer (239671909), dropping its back-buffer references before the original runs, and an unnamed render shutdown (3192982283)source CET 9a8522f src/d3d12/D3D12_Hooks.cpp:11-111, D3D12_Functions.cpp:380-424
Where do the swap chain and queue come from?CET uses a hand-written struct with hard-coded offsets; the RED4ext SDK now decodes the same memory as GpuApi::GetDeviceData() (swap chains with their HDR mode, fullscreen flag and window; the device; the direct queue), identical in the tag the bridge pinssource CET src/reverse/RenderContext.h; SDK GpuApi/SwapChain.hpp:12-54, GpuApi/DeviceData.hpp:72,82-96
What sits below that point?Swap-chain proxies (ReShade's dxgi.dll, Streamline's interposer for DLSS frame generation, FSR frame generation) and the OS overlays. An engine-level overlay is drawn before all of them: ReShade's effects apply to it, and frame generation, which warps a hudless buffer and may recompose a tagged UI buffer, knows nothing of its pixels, so it may ghost or flickersource ReShade 7bf9de8 source/dxgi/dxgi_swapchain.cpp:254-256,962-1030; Streamline docs/ProgrammingGuideDLSS_G.md §5.1, §12.0; effect hypothesis
Why not hook DXGI or the queue?They are the proxies' territory, DLSS-G presents from another thread, and a second queue is fatal with some overlays: ReShade records that DLSS-G uses its own presentation queue and that the GOG Galaxy overlay, guessing the queue from the first ExecuteCommandLists, gets the device removed when it guesses wrongsource ReShade source/dxgi/dxgi_swapchain.hpp:115-119
Can two hooks share GpuApi::Present?Yes. RED4ext attaches plugin hooks with Detours after CET's DllMain MinHook, so an XF detour runs first and chains into CET's; Detours relocates CET's leading jump. The danger is unhooking out of order, so an XF detour is never detached at run time (a flag makes it a pass-through) and RED4ext's exit detach runs before CET'ssource RED4ext c52c8d8 src/dll/Hooks/WinMain.cpp:14-31, src/dll/Systems/HookingSystem.cpp:14-77; Detours 9764ceb src/detours.cpp:390-450
Does CET offer a native draw callback?No: no exports, only Lua's onDraw, whose windows take input only while CET's overlay is opensource src/scripting/ScriptContext.cpp:58-63
How does CET take input?A window-procedure subclass of the W2ViewportClass window; while its overlay is open it swallows every mouse, keyboard and WM_INPUT message and shows the cursor with the engine's ForceCursor(reason "ImGui") (hash 2130646213). Its hotkeys are read from raw keyboard recordssource src/window/window.cpp:11-95, src/d3d12/D3D12.cpp:10-55, src/VKBindings.cpp:537-551,696-730
How would an XF interact mode work?A hotkey (default F10, unbound in the 2.31 defaults) toggles a mode that swallows only the mouse's Raw Input, shows the cursor with an XF reason and leaves the keyboard to the game (V keeps moving, time runs) unless an XF text field has focus; key-ups whose key-downs reached the game always pass, so nothing sticks; focus loss, the kill switch and CET's overlay opening (reported by the bridge's CET layer) drop it to offdesign; key-up rule source ReShade source/input_windows.cpp:218,252; keys resource r6/config/inputUserMappings.xml
HDR?The game has SDR, PQ and scRGB outputs. CET draws with an R8G8B8A8_UNORM pipeline whatever the format, and its overlay is "excessively bright in HDR" (open issue #955). ReShade's overlay encodes its colours to scRGB or PQ at a paper-white level in its pixel shader, the model for an XF renderersource CET D3D12_Functions.cpp:324-326; [external] CET #955; source ReShade res/shaders/imgui_ps_4_0.hlsl:13-30
Fonts and uploads?Dear ImGui 1.92's DX12 backend uploads new glyphs inside the draw call and waits on a fence with no timeout, on the render thread: pre-bake glyphs, then take texture updates out of the draw callsource Dear ImGui v1.92.9-docking backends/imgui_impl_dx12.cpp:615-645
Anti-cheat?None in the single-player game (no EasyAntiCheat or BattlEye files in the install)resource
Frame generation risks?Beyond ghosting, CET has open crash-on-exit reports with FSR 3.1 frame generation enabled (2.3 and 2.31)[external] CET #1025, #1047

Verdict: feasible, recommended for development tools (dense, immediate-mode panels, live values, gizmos) beside, not instead of, the ink route for player-facing UI. Confidence is high for drawing, medium for interact mode and medium-low for frame generation and HDR until a session. First prototype: draw-only, off by default, refusing in HDR (about 4 days plus a session), then interact mode (about 3 days), then an own HDR-aware renderer and the panel kit (about 5 days).

9. The XF Runtime Bridge's ink demos (0.5.3, temporary test features)

DemoWhatRouteStatus
A: XF HUD panelThe bridge's state and the coordinator's ui.message lines in the game's font and colour tokens, a dark panel with a red edge, top right by default; ui.hud shows, hides, moves, scales it and picks its layer; the CET label stays behind [ui] cet_labelCodeware's HUD-layer Root; the overlay pulls a tab-separated frame from the plugin (XFBridge_Hud, core/InkUi.hpp) about 5 times a second; a looping ink animation is its tick; the plugin never calls into it (no RB-76 exposure)[offline] unit tests and self-test; [unverified] in game
B: pedestal nameplatesEach showroom head's number and preset name on its pedestal's front, scaled and faded with distance, up to 15 mScript projection (§5.2) on the same tick; world queries only while the frame's script gate says live 1[unverified]
C: XF map pinsworld.pin at a point, above a showroom head (bound to it) or above V; an ink-drawn XF badge and label on the world map, minimap and in the world, the tooltip names it; world.pin.clear, the kill switch and a load clear themMappinSystem with Mappins.DefaultStaticMappin and an allowlisted variant (never fast travel); UpdateIcon wraps on four controllers and the tooltip[offline] parameter checks and simulated pins; [unverified] in game

Code: redscript/XFRuntimeBridgeInk.reds, redscript/XFRuntimeBridgePins.reds, native/src/core/InkUi.cpp in projects/xf-runtime-bridge.

Open questions

  1. Is the HUD layer's Root in screen pixels (the overlay's uniform scale assumes it), and does a widget left under it survive a load?
  2. Which layers stay visible in photo mode and menus: inkGameNotificationsLayer, inkWatermarksLayer? Is the watermarks layer drawn while it is not "active"?
  3. Does an infinite looping ink animation keep ticking while the HUD is hidden, in menus and in photo mode?
  4. Which screen space does CameraSystem.ProjectPoint answer in (shared with runtime access, question 9)?
  5. Which controller draws a DefaultStaticMappin in the world for each variant, and where on its root does a centred child sit relative to the icon?
  6. Do runtime mappins vanish with a load, and does RegisterMappinWithObject follow a Codeware static entity?
  7. Can a worlduiWidgetComponent be added at assembly time to a spawned entity and draw on a plate mesh (§5.1)?
  8. Does switching the inventory puppet's appearance affect only the preview (§7)?
  9. For an XF ImGui host (§8): does the game register Raw Input with legacy messages off; does ForceCursor keep a second reason independent of CET's; do XF pixels ghost or flicker under DLSS or FSR frame generation; is the exit order of the two present hooks safe? (feasibility §9)

Terminals, browser and arcade · Photo mode · Runtime access · Game crashes · In-game UI design · Mod ecosystem survey · Runtime bridge design · ImGui overlay feasibility

Corrections and credit

This page is generated from knowledge/ink-ui.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.